├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── data └── .empty ├── iso.xml ├── psn00bsdk-setup.mk ├── scripts ├── makeiso.bat └── makeiso.sh ├── src ├── adpcm.c ├── adpcm.h ├── cd.c ├── cd.h ├── game.h ├── gfx.c ├── gfx.h ├── main.c ├── mem.s ├── menu.c ├── menu.h ├── music.c ├── music.h ├── pad.c ├── pad.h ├── res.c ├── res.h ├── snd.c ├── snd.h ├── spu.s ├── tables.c ├── tables.h ├── types.h ├── unpack.c ├── unpack.h ├── util.c ├── util.h ├── vm.c └── vm.h └── system.cnf /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | data/* 3 | !data/.empty 4 | *.o 5 | *.elf 6 | *.exe 7 | *.iso 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 fgsfds 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include psn00bsdk-setup.mk 2 | 3 | # Project target name 4 | TARGET = rawpsx 5 | 6 | # Searches for C, C++ and S (assembler) files in specified directory 7 | SRCDIR = src 8 | CFILES = $(notdir $(wildcard $(SRCDIR)/*.c)) 9 | CPPFILES = $(notdir $(wildcard $(SRCDIR)/*.cpp)) 10 | AFILES = $(notdir $(wildcard $(SRCDIR)/*.s)) 11 | 12 | # Create names for object files 13 | OFILES = $(addprefix build/,$(CFILES:.c=.o)) \ 14 | $(addprefix build/,$(CPPFILES:.cpp=.o)) \ 15 | $(addprefix build/,$(AFILES:.s=.o)) 16 | 17 | # Project specific include and library directories 18 | # (use -I for include dirs, -L for library dirs) 19 | INCLUDE += 20 | LIBDIRS += 21 | 22 | # Libraries to link 23 | LIBS = -lpsxgpu -lpsxspu -lpsxetc -lpsxapi -lpsxcd -lc 24 | 25 | # C compiler flags 26 | CFLAGS = -g -O2 -fno-builtin -fdata-sections -ffunction-sections 27 | 28 | # C++ compiler flags 29 | CPPFLAGS = $(CFLAGS) -fno-exceptions 30 | 31 | # Assembler flags 32 | AFLAGS = -g 33 | 34 | # Linker flags (-Ttext specifies the program text address) 35 | LDFLAGS = -g -Ttext=0x80010000 -gc-sections \ 36 | -T $(GCC_BASE)/$(PREFIX)/lib/ldscripts/elf32elmip.x 37 | 38 | all: $(TARGET).exe 39 | 40 | iso: $(TARGET).iso 41 | 42 | $(TARGET).iso: $(TARGET).exe 43 | mkpsxiso -y -q iso.xml 44 | 45 | $(TARGET).exe: $(OFILES) 46 | $(LD) $(LDFLAGS) $(LIBDIRS) $(OFILES) $(LIBS) -o $(TARGET).elf 47 | elf2x -q $(TARGET).elf 48 | 49 | build/%.o: $(SRCDIR)/%.c 50 | @mkdir -p $(dir $@) 51 | $(CC) $(CFLAGS) $(INCLUDE) -c $< -o $@ 52 | 53 | build/%.o: $(SRCDIR)/%.cpp 54 | @mkdir -p $(dir $@) 55 | $(CXX) $(AFLAGS) $(INCLUDE) -c $< -o $@ 56 | 57 | build/%.o: $(SRCDIR)/%.s 58 | @mkdir -p $(dir $@) 59 | $(CC) $(AFLAGS) $(INCLUDE) -c $< -o $@ 60 | 61 | clean: 62 | rm -rf build $(TARGET).elf $(TARGET).exe 63 | 64 | .PHONY: all iso clean 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rawpsx 2 | 3 | PlayStation re-implementation of the engine used in the game Another World (Out of This World), 4 | based on [rawgl](https://github.com/cyxx/rawgl), 5 | rewritten in C using [PSn00bSDK](https://github.com/Lameguy64/PSn00bSDK). 6 | At the moment rawpsx only supports running the full DOS version of the game, 7 | but eventually I will probably add support for the Amiga version and the DOS demo as well. 8 | 9 | The `sw` branch renders the graphics in software mode and uploads the resulting page to VRAM, and 10 | is the primary branch since it looks like the entire game works fine this way, with little to no 11 | performance issues. 12 | 13 | The `hw` branch renders everything using the GPU, but has palette swapping issues and is outdated. 14 | 15 | ## Running pre-built releases 16 | 17 | 1. Obtain the latest GitHub release, if there are any. 18 | 2. Put the data files from the DOS version of Another World/Out of This World into the `data` folder. 19 | You only need the files `BANKxx` and `MEMLIST.BIN`. 20 | 3. [Get mkpsxiso](https://github.com/Lameguy64/mkpsxiso/releases/latest) and ensure it is in `PATH` 21 | or in the same folder as `rawpsx.exe`. 22 | 4. Run `makeiso.bat` or `makeiso.sh`. This will produce `rawpsx.iso`. 23 | 5. Write the ISO image to a CD-R and play it on your PlayStation 24 | using a modchip or some sort of other protection bypass. 25 | 26 | ## Building 27 | 28 | 1. [Set up PSn00bSDK](https://github.com/Lameguy64/PSn00bSDK#obtaining-psn00bsdk) and ensure it is in `PATH`. 29 | 2. [Get mkpsxiso](https://github.com/Lameguy64/mkpsxiso/releases/latest) and ensure it is in `PATH`. 30 | You can put it into the `tools/bin` folder of your PSn00bSDK install. 31 | 2. Put the data files from the DOS version of Another World/Out of This World into the `data` folder. 32 | You only need the files `BANKxx` and `MEMLIST.BIN`. 33 | 3. Run `make iso`. This will produce `rawpsx.iso` and `rawpsx.exe`. 34 | 4. Write the ISO image to a CD-R and play it on your PlayStation using a modchip 35 | or some sort of other protection bypass. 36 | 37 | ## Credits 38 | * Lameguy64 for PSn00bSDK; 39 | * cyxx for raw/rawgl; 40 | * Giuseppe Gatta (?) for PSXSDK; 41 | * Fabien Sanglard for his Another World article series. 42 | -------------------------------------------------------------------------------- /data/.empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fgsfdsfgs/rawpsx/3dd4ba9e76054b76cc565d99e5163981b7ed0142/data/.empty -------------------------------------------------------------------------------- /iso.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /psn00bsdk-setup.mk: -------------------------------------------------------------------------------- 1 | # PSn00bSDK project setup file 2 | # Part of the PSn00bSDK Project 3 | # 2019 - 2020 Lameguy64 / Meido-Tek Productions 4 | # 5 | # This file may be copied for use with your projects, see the template 6 | # directory for a makefile template 7 | 8 | ifndef PREFIX 9 | 10 | PREFIX = mipsel-unknown-elf 11 | 12 | endif # PREFIX 13 | 14 | ifndef GCC_VERSION 15 | 16 | GCC_VERSION = 7.4.0 17 | 18 | endif # GCC_VERSION 19 | 20 | # PSn00bSDK library/include path setup 21 | ifndef PSN00BSDK_LIBS 22 | 23 | # Default assumes PSn00bSDK is in the same parent dir as this project 24 | 25 | LIBDIRS = -L../psn00bsdk/libpsn00b 26 | INCLUDE = -I../psn00bsdk/libpsn00b/include 27 | 28 | else 29 | 30 | LIBDIRS = -L$(PSN00BSDK_LIBS) 31 | INCLUDE = -I$(PSN00BSDK_LIBS)/include 32 | 33 | endif # PSN00BSDK_LIBS 34 | 35 | # PSn00bSDK toolchain path setup 36 | ifndef GCC_BASE 37 | 38 | ifndef PSN00BSDK_TC 39 | 40 | # Default assumes GCC toolchain is in root of C drive or /usr/local 41 | 42 | ifeq "$(OS)" "Windows_NT" 43 | 44 | GCC_BASE = /c/mipsel-unknown-elf 45 | GCC_BIN = 46 | 47 | else 48 | 49 | GCC_BASE = /usr/local/mipsel-unknown-elf 50 | GCC_BIN = 51 | 52 | endif 53 | 54 | else 55 | 56 | GCC_BASE = $(PSN00BSDK_TC) 57 | GCC_BIN = $(PSN00BSDK_TC)/bin/ 58 | 59 | endif # PSN00BSDK_TC 60 | 61 | endif # GCC_BASE 62 | 63 | CC = $(GCC_BIN)$(PREFIX)-gcc 64 | CXX = $(GCC_BIN)$(PREFIX)-g++ 65 | AS = $(GCC_BIN)$(PREFIX)-as 66 | AR = $(GCC_BIN)$(PREFIX)-ar 67 | LD = $(GCC_BIN)$(PREFIX)-ld 68 | RANLIB = $(GCC_BIN)$(PREFIX)-ranlib 69 | -------------------------------------------------------------------------------- /scripts/makeiso.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | mkpsxiso -y -q iso.xml 3 | pause 4 | -------------------------------------------------------------------------------- /scripts/makeiso.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | mkpsxiso -y -q iso.xml 3 | -------------------------------------------------------------------------------- /src/adpcm.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "types.h" 4 | #include "adpcm.h" 5 | #include "util.h" 6 | 7 | /* taken from psxsdk, 16-bit support thrown out */ 8 | 9 | #define PCM_CHUNK_SIZE 28 // num pcm samples for one adpcm block 10 | #define PCM_BUFFER_SIZE (PCM_CHUNK_SIZE * 128) // size of conversion buffer 11 | 12 | #define ADPCM_BLOCK_SIZE 16 // size of one ADPCM block in bytes 13 | #define FLAG_LOOP_END (1 << 0) 14 | #define FLAG_LOOP_REPEAT (1 << 1) 15 | #define FLAG_LOOP_START (1 << 2) 16 | 17 | /* ADPCM block structure: 18 | * u8 shift_filter; 19 | * u8 flags; 20 | * u8 data[14]; 21 | * Flag bits (`flags` field): 22 | * bit 0: loop end - jump to address in voice->sample_repeataddr 23 | * bit 1: loop repeat - reset ADSR stuff 24 | * bit 2: loop start - save current address to voice->sample_repeataddr 25 | */ 26 | 27 | static const int factors[5][2] = { 28 | { 0, 0 }, 29 | { 60, 0 }, 30 | { 115, -52 }, 31 | { 98, -55 }, 32 | { 122, -60 } 33 | }; 34 | 35 | static s16 pcm_buffer[PCM_BUFFER_SIZE]; 36 | 37 | // TODO: get rid of these globals 38 | // they used to be as statics in their corresponding functions, 39 | // but they were never getting reset 40 | static s32 find_s1 = 0; 41 | static s32 find_s2 = 0; 42 | static s32 pack_s1 = 0; 43 | static s32 pack_s2 = 0; 44 | 45 | static inline void adpcm_find_predict(const s16 *pcm, s32 *d_samples, s32 *predict_nr, s32 *shift_factor) { 46 | register int i, j; 47 | register s32 s0, s1, s2; 48 | s32 buffer[PCM_CHUNK_SIZE][5]; 49 | s32 min = 0x7FFFFFFF; 50 | s32 max[5]; 51 | s32 ds; 52 | s32 min2; 53 | s32 shift_mask; 54 | 55 | for (i = 0; i < 5; i++) { 56 | max[i] = 0.0; 57 | s1 = find_s1; 58 | s2 = find_s2; 59 | for (j = 0; j < PCM_CHUNK_SIZE; j ++) { 60 | s0 = (s32)pcm[j]; 61 | if (s0 > 32767) 62 | s0 = 32767; 63 | if (s0 < - 32768) 64 | s0 = -32768; 65 | ds = s0 + s1 * factors[i][0] + s2 * factors[i][1]; 66 | buffer[j][i] = ds; 67 | if (ds > max[i]) 68 | max[i] = ds; 69 | s2 = s1; 70 | s1 = s0; 71 | } 72 | if (max[i] <= min) { 73 | min = max[i]; 74 | *predict_nr = i; 75 | } 76 | if (min <= 7) { 77 | *predict_nr = 0; 78 | break; 79 | } 80 | } 81 | 82 | find_s1 = s1; 83 | find_s2 = s2; 84 | 85 | for ( i = 0; i < PCM_CHUNK_SIZE; i++ ) 86 | d_samples[i] = buffer[i][*predict_nr]; 87 | 88 | // if (min > 32767) 89 | // min = 32767; 90 | 91 | min2 = ( int ) min; 92 | shift_mask = 0x4000; 93 | *shift_factor = 0; 94 | 95 | while (*shift_factor < 12) { 96 | if (shift_mask & (min2 + (shift_mask >> 3))) 97 | break; 98 | (*shift_factor)++; 99 | shift_mask = shift_mask >> 1; 100 | } 101 | } 102 | 103 | static inline void adpcm_do_pack(const s32 *d_samples, s16 *four_bit, const s32 predict_nr, const s32 shift_factor) { 104 | register int i; 105 | register s32 s0, di, ds; 106 | for (i = 0; i < PCM_CHUNK_SIZE; ++i) { 107 | s0 = d_samples[i] + pack_s1 * factors[predict_nr][0] + pack_s2 * factors[predict_nr][1]; 108 | ds = s0 * (s32)(1 << shift_factor); 109 | di = ((s32) ds + 0x800) & 0xFFFFF000; 110 | if (di > 32767) 111 | di = 32767; 112 | if (di < -32768) 113 | di = -32768; 114 | four_bit[i] = (s32)di; 115 | di = di >> shift_factor; 116 | pack_s2 = pack_s1; 117 | pack_s1 = (s32)di - s0; 118 | } 119 | } 120 | 121 | int adpcm_pack_mono_s8(u8 *out, int out_size, const s8 *pcm, int pcm_size, int loopstart, int loopend) { 122 | register int i, j, k; 123 | register s16 *inptr; 124 | register u8 *outptr; 125 | register u8 d; 126 | register int pcmpos = 0; 127 | s32 d_samples[PCM_CHUNK_SIZE]; 128 | s16 four_bit[PCM_CHUNK_SIZE]; 129 | s32 predict_nr; 130 | s32 shift_factor; 131 | int flags = 0; 132 | const int doloop = (loopstart >= 0 && loopend > loopstart && loopend <= pcm_size); 133 | 134 | // convert to chunk numbers 135 | if (doloop) { 136 | loopstart /= PCM_CHUNK_SIZE; 137 | loopend /= PCM_CHUNK_SIZE; 138 | } 139 | 140 | // reset globals 141 | pack_s1 = pack_s2 = 0; 142 | find_s1 = find_s2 = 0; 143 | 144 | outptr = out; 145 | while (pcm_size > 0) { 146 | // refill buffer 147 | int size = (pcm_size >= PCM_BUFFER_SIZE) ? PCM_BUFFER_SIZE : pcm_size; 148 | for (i = 0; i < size; ++i) { 149 | pcm_buffer[i] = *pcm++; 150 | pcm_buffer[i] <<= 8; 151 | } 152 | // round up to chunk size 153 | i = size / PCM_CHUNK_SIZE; 154 | j = size % PCM_CHUNK_SIZE; 155 | if (j) { 156 | // fill the rest of the last chunk with silence 157 | for (; j < PCM_CHUNK_SIZE; ++j) 158 | pcm_buffer[i * PCM_CHUNK_SIZE + j] = 0; 159 | ++i; 160 | } 161 | // check if it's going to fit 162 | if (outptr + i * ADPCM_BLOCK_SIZE > out + out_size) 163 | goto _err_too_big; 164 | // write out the blocks 165 | for (j = 0; j < i; ++j) { 166 | inptr = pcm_buffer + j * PCM_CHUNK_SIZE; 167 | adpcm_find_predict(inptr, d_samples, &predict_nr, &shift_factor); 168 | adpcm_do_pack(d_samples, four_bit, predict_nr, shift_factor); 169 | if (doloop) { 170 | // TODO: can probably do this outside of the loop but eh 171 | if (loopstart >= 0 && pcmpos >= loopstart) { 172 | // reached block where the start of the loop is, set loop start flag 173 | flags = FLAG_LOOP_START; 174 | loopstart = -1; 175 | } else if (loopend >= 0 && pcmpos >= loopend) { 176 | // reached block where the end of the loop is, set loop end flag 177 | flags = FLAG_LOOP_END | FLAG_LOOP_REPEAT; 178 | loopend = -1; 179 | } 180 | } 181 | d = (predict_nr << 4) | shift_factor; 182 | *outptr++ = d; 183 | *outptr++ = flags; 184 | for (k = 0; k < PCM_CHUNK_SIZE; k += 2) { 185 | d = ((four_bit[k + 1] >> 8) & 0xF0) | ((four_bit[k] >> 12) & 0xF); 186 | *outptr++ = d; 187 | } 188 | flags = 0; 189 | ++pcmpos; // in chunks 190 | pcm_size -= PCM_CHUNK_SIZE; 191 | } 192 | } 193 | 194 | // check if we've run out of space for our extra empty block 195 | if (outptr + ADPCM_BLOCK_SIZE > out + out_size) 196 | goto _err_too_big; 197 | 198 | // loop endlessly in this null block 199 | flags = FLAG_LOOP_END | FLAG_LOOP_REPEAT; 200 | if (!doloop) flags |= FLAG_LOOP_START; 201 | *outptr++ = (predict_nr << 4) | shift_factor; 202 | *outptr++ = flags; 203 | for (i = 0; i < PCM_CHUNK_SIZE / 2; ++i) 204 | *outptr++ = 0; 205 | 206 | return outptr - out; 207 | 208 | _err_too_big: 209 | printf("adpcm_pack(out_size=%d pcm_size=%d): adpcm data too large for out\n", out_size, pcm_size); 210 | return -1; 211 | } 212 | -------------------------------------------------------------------------------- /src/adpcm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | int adpcm_pack_mono_s8(u8 *out, int out_size, const s8 *pcm, int pcm_size, int loopstart, int loopend); 6 | -------------------------------------------------------------------------------- /src/cd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "types.h" 9 | #include "cd.h" 10 | #include "util.h" 11 | 12 | // TEMPORARY CD FILE READING API WITH BUFFERS AND SHIT 13 | // copied straight from d2d-psx and converted to only use one static handle 14 | 15 | #define SECSIZE 2048 16 | #define BUFSECS 4 17 | #define BUFSIZE (BUFSECS * SECSIZE) 18 | #define MAX_FHANDLES 1 19 | 20 | static const u32 cdmode = CdlModeSpeed; 21 | 22 | struct cd_file_s { 23 | char fname[64]; 24 | CdlFILE cdf; 25 | s32 secstart, secend, seccur; 26 | s32 fp, bufp; 27 | s32 bufleft; 28 | unsigned char buf[BUFSIZE]; 29 | }; 30 | 31 | // lmao 1handle 32 | static cd_file_t fhandle; 33 | static s32 num_fhandles = 0; 34 | 35 | void cd_init(void) { 36 | CdInit(); 37 | // look alive 38 | CdControl(CdlNop, 0, 0); 39 | CdStatus(); 40 | // set hispeed mode 41 | CdControlB(CdlSetmode, (u8 *)&cdmode, 0); 42 | VSync(3); // have to do this to not explode the drive apparently 43 | } 44 | 45 | cd_file_t *cd_fopen(const char *fname, const int reopen) { 46 | // check if the same file was just open and return it if allowed 47 | if (reopen && !strncmp(fhandle.fname, fname, sizeof(fhandle.fname))) { 48 | num_fhandles++; 49 | return &fhandle; 50 | } 51 | 52 | if (num_fhandles >= MAX_FHANDLES) { 53 | printf("cd_fopen(%s): too many file handles\n", fname); 54 | return NULL; 55 | } 56 | 57 | cd_file_t *f = &fhandle; 58 | memset(f, 0, sizeof(*f)); 59 | 60 | if (CdSearchFile(&f->cdf, fname) == NULL) { 61 | printf("cd_fopen(%s): file not found\n", fname); 62 | return NULL; 63 | } 64 | 65 | // read first sector of the file 66 | CdControl(CdlSetloc, (u8 *)&f->cdf.pos, 0); 67 | CdRead(BUFSECS, (u32 *)f->buf, CdlModeSpeed); 68 | CdReadSync(0, NULL); 69 | 70 | // set fp and shit 71 | f->secstart = CdPosToInt(&f->cdf.pos); 72 | f->seccur = f->secstart; 73 | f->secend = f->secstart + (f->cdf.size + SECSIZE-1) / SECSIZE; 74 | f->fp = 0; 75 | f->bufp = 0; 76 | f->bufleft = (f->cdf.size >= BUFSIZE) ? BUFSIZE : f->cdf.size; 77 | strncpy(fhandle.fname, fname, sizeof(fhandle.fname) - 1); 78 | 79 | num_fhandles++; 80 | printf("cd_fopen(%s): size %u bufleft %d secs %d %d\n", fname, f->cdf.size, f->bufleft, f->secstart, f->secend); 81 | 82 | return f; 83 | } 84 | 85 | int cd_fexists(const char *fname) { 86 | CdlFILE cdf; 87 | if (CdSearchFile(&cdf, (char *)fname) == NULL) { 88 | printf("cd_fexists(%s): file not found\n", fname); 89 | return 0; 90 | } 91 | return 1; 92 | } 93 | 94 | void cd_fclose(cd_file_t *f) { 95 | if (!f) return; 96 | num_fhandles--; 97 | } 98 | 99 | s32 cd_fread(void *ptr, s32 size, s32 num, cd_file_t *f) { 100 | s32 rx, rdbuf; 101 | s32 fleft; 102 | CdlLOC pos; 103 | 104 | if (!f || !ptr) return -1; 105 | if (!size) return 0; 106 | 107 | size *= num; 108 | rx = 0; 109 | 110 | while (size) { 111 | // first empty the buffer 112 | rdbuf = (size > f->bufleft) ? f->bufleft : size; 113 | memcpy(ptr, f->buf + f->bufp, rdbuf); 114 | rx += rdbuf; 115 | ptr += rdbuf; 116 | f->fp += rdbuf; 117 | f->bufp += rdbuf; 118 | f->bufleft -= rdbuf; 119 | size -= rdbuf; 120 | 121 | // if we went over, load next sector 122 | if (f->bufleft == 0) { 123 | f->seccur += BUFSECS; 124 | // check if we have reached the end 125 | if (f->seccur >= f->secend) 126 | return rx; 127 | // looks like you need to seek every time when you use CdRead 128 | CdIntToPos(f->seccur, &pos); 129 | CdControl(CdlSetloc, (u8 *)&pos, 0); 130 | CdRead(BUFSECS, (u32 *)f->buf, CdlModeSpeed); 131 | CdReadSync(0, 0); 132 | fleft = f->cdf.size - f->fp; 133 | f->bufleft = (fleft >= BUFSIZE) ? BUFSIZE: fleft; 134 | f->bufp = 0; 135 | } 136 | } 137 | 138 | return rx; 139 | } 140 | 141 | void cd_freadordie(void *ptr, s32 size, s32 num, cd_file_t *f) { 142 | if (cd_fread(ptr, size, num, f) < 0) 143 | panic("cd_freadordie(%.16s, %d, %d): fucking died", f->cdf.name, size, num); 144 | } 145 | 146 | s32 cd_fseek(cd_file_t *f, s32 ofs, s32 whence) { 147 | s32 fsec, bofs; 148 | CdlLOC pos; 149 | 150 | if (!f) return -1; 151 | 152 | if (whence == SEEK_CUR) 153 | ofs = f->fp + ofs; 154 | 155 | if (f->fp == ofs) return 0; 156 | 157 | fsec = f->secstart + (ofs / BUFSIZE) * BUFSECS; 158 | bofs = ofs % BUFSIZE; 159 | 160 | // fuck SEEK_END, it's only used to get file length here 161 | 162 | if (fsec != f->seccur) { 163 | // sector changed; seek to new one and buffer it 164 | CdIntToPos(fsec, &pos); 165 | CdControl(CdlSetloc, (u8 *)&pos, 0); 166 | CdRead(BUFSECS, (u32 *)f->buf, CdlModeSpeed); 167 | CdReadSync(0, 0); 168 | f->seccur = fsec; 169 | f->bufp = -1; // hack: see below 170 | } 171 | 172 | if (bofs != f->bufp) { 173 | // buffer offset changed (or new sector loaded); reset pointers 174 | f->bufp = bofs; 175 | f->bufleft = BUFSIZE - bofs; 176 | if (f->bufleft < 0) f->bufleft = 0; 177 | } 178 | 179 | f->fp = ofs; 180 | 181 | return 0; 182 | } 183 | 184 | s32 cd_ftell(cd_file_t *f) { 185 | if (!f) return -1; 186 | return f->fp; 187 | } 188 | 189 | s32 cd_fsize(cd_file_t *f) { 190 | if (!f) return -1; 191 | return f->cdf.size; 192 | } 193 | 194 | int cd_feof(cd_file_t *f) { 195 | if (!f) return -1; 196 | return (f->seccur >= f->secend); 197 | } 198 | 199 | u8 cd_fread_u8(cd_file_t *f) { 200 | u8 res = 0; 201 | cd_freadordie(&res, 1, 1, f); 202 | return res; 203 | } 204 | 205 | u16 cd_fread_u16be(cd_file_t *f) { 206 | u16 res = 0; 207 | cd_freadordie(&res, 2, 1, f); 208 | return bswap16(res); 209 | } 210 | 211 | u32 cd_fread_u32be(cd_file_t *f) { 212 | u32 res = 0; 213 | cd_freadordie(&res, 4, 1, f); 214 | return bswap32(res); 215 | } 216 | -------------------------------------------------------------------------------- /src/cd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "types.h" 5 | 6 | typedef struct cd_file_s cd_file_t; 7 | 8 | void cd_init(void); 9 | cd_file_t *cd_fopen(const char *fname, const int reopen); 10 | int cd_fexists(const char *fname); 11 | void cd_fclose(cd_file_t *f); 12 | s32 cd_fread(void *ptr, s32 size, s32 num, cd_file_t *f); 13 | void cd_freadordie(void *ptr, s32 size, s32 num, cd_file_t *f); 14 | s32 cd_fseek(cd_file_t *f, s32 ofs, int whence); 15 | s32 cd_ftell(cd_file_t *f); 16 | s32 cd_fsize(cd_file_t *f); 17 | int cd_feof(cd_file_t *f); 18 | 19 | u8 cd_fread_u8(cd_file_t *f); 20 | u16 cd_fread_u16be(cd_file_t *f); 21 | u32 cd_fread_u32be(cd_file_t *f); 22 | -------------------------------------------------------------------------------- /src/game.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | enum game_parts_e { 4 | PART_BASE = 16000, 5 | PART_COPY_PROTECTION = 16000, 6 | PART_INTRO = 16001, 7 | PART_WATER = 16002, 8 | PART_PRISON = 16003, 9 | PART_CITE = 16004, 10 | PART_ARENE = 16005, 11 | PART_LUXE = 16006, 12 | PART_FINAL = 16007, 13 | PART_PASSWORD = 16008, 14 | PART_LAST = 16009 15 | }; 16 | 17 | #define START_PART PART_INTRO 18 | -------------------------------------------------------------------------------- /src/gfx.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "types.h" 9 | #include "res.h" 10 | #include "util.h" 11 | #include "gfx.h" 12 | 13 | #define BITMAP_PLANE_SIZE 8000 // 200 * 320 / 8 14 | 15 | #define PAL_SCREEN_W 320 16 | #define PAL_SCREEN_H 256 17 | #define NTSC_SCREEN_W 320 18 | #define NTSC_SCREEN_H 240 19 | 20 | #define NUM_PAGES 4 21 | #define NUM_BUFFERS 2 22 | #define NUM_COLORS 16 23 | 24 | #define PACKET_MAX 0x100 25 | #define PALS_MAX 32 26 | #define POINTS_MAX 50 27 | 28 | #define PSXRGB(r, g, b) ((((b) >> 3) << 10) | (((g) >> 3) << 5) | ((r) >> 3)) 29 | 30 | #define MAX(x, y) ((x) > (y) ? (x) : (y)) 31 | #define MIN(x, y) ((x) < (y) ? (x) : (y)) 32 | 33 | // psn00b lacks MoveImage but has a VRAM2VRAM primitive, which is probably 34 | // the same as PsyQ SDK's DR_MOVE? for some reason this macro is commented out 35 | #define setVram2Vram( p ) ( setlen( p, 8 ), setcode( p, 0x80 ), \ 36 | (p)->nop[0] = 0, (p)->nop[1] = 0, (p)->nop[2] = 0, (p)->nop[3] = 0 ) 37 | 38 | typedef struct { 39 | DR_TPAGE tpage; 40 | SPRT sprt; 41 | } TSPRT; 42 | 43 | typedef struct { 44 | DISPENV disp; 45 | DRAWENV draw; 46 | TSPRT tsprt[2]; 47 | } fb_t; 48 | 49 | typedef struct { 50 | s16 x; 51 | s16 y; 52 | } vert_t; 53 | 54 | static int gfx_start_mode; 55 | static int gfx_cur_mode; 56 | static int gfx_cur_width; 57 | static int gfx_cur_height; 58 | 59 | static fb_t gfx_fb[NUM_BUFFERS]; 60 | static int gfx_fb_idx; 61 | 62 | static u8 *gfx_data_base; 63 | static u8 *gfx_data; 64 | 65 | static u16 gfx_num_verts; 66 | static vert_t gfx_verts[POINTS_MAX]; 67 | 68 | static u16 gfx_pal[NUM_COLORS]; 69 | static u16 gfx_palnum; 70 | static u16 gfx_palnum_next; 71 | static u8 gfx_pal_uploaded = 0; 72 | 73 | static const u8 *gfx_font; 74 | 75 | // gotta align these to 4 bytes to use memcpy_w 76 | static u8 gfx_page[NUM_PAGES][PAGE_W * PAGE_H] __attribute__((aligned(4))); 77 | static u8 *gfx_page_front; 78 | static u8 *gfx_page_back; 79 | static u8 *gfx_page_work; 80 | 81 | static RECT gfx_buffer_rect = { PAL_SCREEN_W, 0, PAGE_W >> 1, PAGE_H }; 82 | static RECT gfx_pal_rect = { PAL_SCREEN_W, 256, NUM_COLORS, 1 }; 83 | 84 | static inline u8 gfx_fetch_u8(void) { 85 | return *(gfx_data++); 86 | } 87 | 88 | static inline u16 gfx_fetch_u16(void) { 89 | const u8 *b = gfx_data; 90 | gfx_data += 2; 91 | return (b[0] << 8) | b[1]; 92 | } 93 | 94 | static inline u8 *gfx_get_page(const int page) { 95 | if (page < NUM_PAGES) 96 | return gfx_page[page]; 97 | switch (page) { 98 | case 0xFE: return gfx_page_front; 99 | case 0xFF: return gfx_page_back; 100 | default: return gfx_page_work; /* error? */ 101 | } 102 | } 103 | 104 | int gfx_init(void) { 105 | ResetGraph(3); 106 | 107 | gfx_start_mode = gfx_cur_mode = GetVideoMode(); 108 | if (gfx_cur_mode == MODE_PAL) { 109 | gfx_cur_width = PAL_SCREEN_W; 110 | gfx_cur_height = PAL_SCREEN_H; 111 | } else { 112 | gfx_cur_width = NTSC_SCREEN_W; 113 | gfx_cur_height = NTSC_SCREEN_H; 114 | } 115 | 116 | // set up double buffer 117 | SetDefDispEnv(&gfx_fb[0].disp, 0, 0, PAGE_W, PAGE_H); 118 | SetDefDrawEnv(&gfx_fb[1].draw, 0, 0, PAGE_W, PAGE_H); 119 | SetDefDispEnv(&gfx_fb[1].disp, 0, 256, PAGE_W, PAGE_H); 120 | SetDefDrawEnv(&gfx_fb[0].draw, 0, 256, PAGE_W, PAGE_H); 121 | 122 | // offset every DISPENV downward on the screen to account for 320x200 pages 123 | gfx_fb[0].disp.screen.y = gfx_fb[1].disp.screen.y = (gfx_cur_height - PAGE_H) / 2; 124 | 125 | // clear screen ASAP 126 | // need two FILL primitives because h=512 doesn't work correctly 127 | FILL fill = { 0 }; 128 | setFill(&fill); 129 | fill.w = 512; 130 | fill.h = 256; 131 | DrawPrim(&fill); 132 | fill.y0 = 256; 133 | DrawPrim(&fill); 134 | DrawSync(0); 135 | 136 | // we're going to be blitting the screen texture by drawing two SPRTs with parts of it 137 | const u16 btpage1 = getTPage(1, 0, gfx_buffer_rect.x, gfx_buffer_rect.y); 138 | // offset by 128 and not 256 because screen texture is 8-bit 139 | const u16 btpage2 = getTPage(1, 0, gfx_buffer_rect.x + 128, gfx_buffer_rect.y); 140 | for (int i = 0; i < NUM_BUFFERS; ++i) { 141 | TSPRT *t1 = &gfx_fb[i].tsprt[0]; 142 | TSPRT *t2 = &gfx_fb[i].tsprt[1]; 143 | setDrawTPage(&t1->tpage, 1, 0, btpage1); 144 | setDrawTPage(&t2->tpage, 1, 0, btpage2); 145 | setSprt(&t1->sprt); 146 | setSprt(&t2->sprt); 147 | setSemiTrans(&t1->sprt, 0); 148 | setSemiTrans(&t2->sprt, 0); 149 | t1->sprt.clut = t2->sprt.clut = getClut(gfx_pal_rect.x, gfx_pal_rect.y); 150 | t1->sprt.r0 = t2->sprt.r0 = 0x80; 151 | t1->sprt.g0 = t2->sprt.g0 = 0x80; 152 | t1->sprt.b0 = t2->sprt.b0 = 0x80; 153 | t1->sprt.h = t2->sprt.h = PAGE_H; 154 | t1->sprt.w = 256; 155 | t2->sprt.w = PAGE_W - 256; 156 | t2->sprt.x0 = 256; 157 | } 158 | 159 | // initialize page pointers 160 | gfx_page_back = gfx_get_page(1); 161 | gfx_page_front = gfx_get_page(2); 162 | gfx_page_work = gfx_get_page(0); 163 | 164 | gfx_palnum = gfx_palnum_next = 0xFF; 165 | gfx_num_verts = 0; 166 | gfx_set_font(fnt_default); 167 | 168 | // set default front and work buffer 169 | gfx_fb_idx = 0; 170 | PutDispEnv(&gfx_fb[0].disp); 171 | PutDrawEnv(&gfx_fb[0].draw); 172 | 173 | printf("gfx_init(): start mode %d, current mode %d\n", gfx_start_mode, GetVideoMode()); 174 | 175 | // enable output 176 | SetDispMask(1); 177 | 178 | return 0; 179 | } 180 | 181 | void gfx_set_databuf(u8 *seg, const u16 ofs) { 182 | gfx_data_base = seg; 183 | gfx_data = seg + ofs; 184 | } 185 | 186 | static inline void gfx_load_palette(const u8 n) { 187 | register u16 *out = gfx_pal; 188 | register const u8 *p = res_seg_video_pal + n * NUM_COLORS * sizeof(u16); 189 | register u16 c; 190 | for (register int i = 0; i < NUM_COLORS; ++i, p += 2, ++out) { 191 | c = read16be(p); // BGR444 192 | // convert to RGB555X 193 | c = ((c & 0xF) << 11) | (((c >> 4) & 0xF) << 6) | (((c >> 8) & 0xF) << 1); 194 | *out = c ? c : 0x8000; // replace black with PSX non-transparent black 195 | } 196 | } 197 | 198 | void gfx_set_palette(const u8 palnum) { 199 | if (palnum >= PALS_MAX || palnum == gfx_palnum) 200 | return; 201 | gfx_load_palette(palnum); 202 | gfx_palnum = palnum; 203 | gfx_pal_uploaded = 0; 204 | } 205 | 206 | void gfx_set_next_palette(const u8 palnum) { 207 | gfx_palnum_next = palnum; 208 | } 209 | 210 | void gfx_invalidate_palette(void) { 211 | gfx_palnum = 0xFF; 212 | } 213 | 214 | u16 gfx_get_current_palette(void) { 215 | return gfx_palnum; 216 | } 217 | 218 | void gfx_update_display(const int page) { 219 | DrawSync(0); 220 | 221 | if (page != 0xFE) { 222 | if (page == 0xFF) { 223 | u8 *tmp = gfx_page_front; 224 | gfx_page_front = gfx_page_back; 225 | gfx_page_back = tmp; 226 | } else { 227 | gfx_page_front = gfx_get_page(page); 228 | } 229 | } 230 | 231 | if (gfx_palnum_next != 0xFF) { 232 | gfx_set_palette(gfx_palnum_next); 233 | gfx_palnum_next = 0xFF; 234 | } 235 | 236 | // upload palette to vram if needed 237 | if (!gfx_pal_uploaded) { 238 | LoadImage(&gfx_pal_rect, (u32 *)gfx_pal); 239 | gfx_pal_uploaded = 1; 240 | } 241 | // upload front page to the screen buffer 242 | LoadImage(&gfx_buffer_rect, (u32 *)gfx_page_front); 243 | // draw framebuffer in two parts, since it's larger than 256x256 244 | TSPRT *tsprt = gfx_fb[gfx_fb_idx].tsprt; 245 | for (int i = 0; i < 2; ++i) { 246 | DrawPrim(&tsprt[i].tpage); 247 | DrawPrim(&tsprt[i].sprt); 248 | } 249 | // now we can swap buffers 250 | gfx_fb_idx ^= 1; 251 | PutDispEnv(&gfx_fb[gfx_fb_idx].disp); 252 | PutDrawEnv(&gfx_fb[gfx_fb_idx].draw); 253 | } 254 | 255 | void gfx_set_work_page(const int page) { 256 | u8 *new = gfx_get_page(page); 257 | gfx_page_work = new; 258 | } 259 | 260 | static inline void gfx_draw_point(u8 color, s16 x, s16 y) { 261 | register const u32 ofs = y * PAGE_W + x; 262 | switch (color) { 263 | case COL_ALPHA: gfx_page_work[ofs] |= 8; break; 264 | case COL_PAGE: gfx_page_work[ofs] = gfx_page[0][ofs]; break; 265 | default: gfx_page_work[ofs] = color; break; 266 | } 267 | } 268 | 269 | static void gfx_draw_line_color(const u16 ofs, const u16 w, const u8 color) { 270 | memset(gfx_page_work + ofs, color, w); 271 | } 272 | 273 | static void gfx_draw_line_copy(const u16 ofs, const u16 w, const u8 color) { 274 | memcpy(gfx_page_work + ofs, gfx_page[0] + ofs, w); 275 | } 276 | 277 | static void gfx_draw_line_alpha(const u16 ofs, const u16 w, const u8 color) { 278 | register u8 *p = gfx_page_work + ofs; 279 | register const u8 *end = p + w; 280 | for (; p < end; ++p) *p |= 8; 281 | } 282 | 283 | static inline u32 gfx_fill_polygon_get_step(const vert_t *v1, const vert_t *v2, u16 *dy) { 284 | *dy = v2->y - v1->y; 285 | const u16 delta = (*dy <= 1) ? 1 : *dy; 286 | return ((v2->x - v1->x) * (0x4000 / delta)) << 2; 287 | } 288 | 289 | static void gfx_fill_polygon(u8 color, u16 zoom, s16 x, s16 y) { 290 | const u8 *p = gfx_data; 291 | const u16 bbw = ((*p++) * zoom) >> 6; 292 | const u16 bbh = ((*p++) * zoom) >> 6; 293 | const u16 half_bbw = (bbw >> 1); 294 | const u16 half_bbh = (bbh >> 1); 295 | 296 | const s16 bx1 = x - half_bbw; 297 | const s16 bx2 = x + half_bbw; 298 | const s16 by1 = y - half_bbh; 299 | const s16 by2 = y + half_bbh; 300 | 301 | if (bx1 > 319 || bx2 < 0 || by1 > 199 || by2 < 0) 302 | return; 303 | 304 | gfx_num_verts = *p++; 305 | if ((gfx_num_verts & 1) || gfx_num_verts > POINTS_MAX) { 306 | printf("gfx_fill_polygon(): invalid number of verts %d\n", gfx_num_verts); 307 | return; 308 | } 309 | 310 | if (gfx_num_verts == 4 && bbw == 0 && bbh <= 1) { 311 | gfx_draw_point(color, x, y); 312 | return; 313 | } 314 | 315 | void (*draw_line)(const u16, const u16, const u8); 316 | switch (color) { 317 | case COL_ALPHA: 318 | draw_line = gfx_draw_line_alpha; 319 | break; 320 | case COL_PAGE: 321 | if (gfx_page_work == gfx_page[0]) 322 | return; 323 | draw_line = gfx_draw_line_copy; 324 | break; 325 | default: 326 | draw_line = gfx_draw_line_color; 327 | break; 328 | } 329 | 330 | for (u16 i = 0; i < gfx_num_verts; ++i) { 331 | gfx_verts[i].x = bx1 + (((*p++) * zoom) >> 6); 332 | gfx_verts[i].y = by1 + (((*p++) * zoom) >> 6); 333 | } 334 | 335 | s16 i = 0; 336 | s16 j = gfx_num_verts - 1; 337 | s16 x1 = gfx_verts[j].x; 338 | s16 x2 = gfx_verts[i].x; 339 | u32 cpt1 = x1 << 16; 340 | u32 cpt2 = x2 << 16; 341 | register s32 ofs = MIN(gfx_verts[i].y, gfx_verts[j].y) * PAGE_W; 342 | register u16 w = 0; 343 | register s16 xmin; 344 | register s16 xmax; 345 | for (++i, --j; gfx_num_verts; gfx_num_verts -= 2) { 346 | u16 h; 347 | const u32 step1 = gfx_fill_polygon_get_step(&gfx_verts[j + 1], &gfx_verts[j], &h); 348 | const u32 step2 = gfx_fill_polygon_get_step(&gfx_verts[i - 1], &gfx_verts[i], &h); 349 | ++i, --j; 350 | cpt1 = (cpt1 & 0xFFFF0000) | 0x7FFF; 351 | cpt2 = (cpt2 & 0xFFFF0000) | 0x8000; 352 | if (h == 0) { 353 | cpt1 += step1; 354 | cpt2 += step2; 355 | } else { 356 | while (h--) { 357 | if (ofs >= 0) { 358 | x1 = cpt1 >> 16; 359 | x2 = cpt2 >> 16; 360 | if (x1 < PAGE_W && x2 >= 0) { 361 | if (x1 < 0) x1 = 0; 362 | if (x2 >= PAGE_W) x2 = PAGE_W - 1; 363 | if (x1 > x2) { xmin = x2; xmax = x1; } 364 | else { xmin = x1; xmax = x2; } 365 | draw_line(ofs + xmin, (xmax - xmin) + 1, color); 366 | } 367 | } 368 | cpt1 += step1; 369 | cpt2 += step2; 370 | ofs += PAGE_W; 371 | if (ofs >= PAGE_W * PAGE_H) 372 | return; 373 | } 374 | } 375 | } 376 | } 377 | 378 | static void gfx_draw_shape_hierarchy(u16 zoom, s16 x, s16 y) { 379 | x -= (gfx_fetch_u8() * zoom) >> 6; 380 | y -= (gfx_fetch_u8() * zoom) >> 6; 381 | 382 | register s16 nchildren = gfx_fetch_u8(); 383 | register u16 ofs; 384 | register s16 cx, cy; 385 | 386 | for (; nchildren >= 0; --nchildren) { 387 | ofs = gfx_fetch_u16(); 388 | cx = x + ((gfx_fetch_u8() * zoom) >> 6); 389 | cy = y + ((gfx_fetch_u8() * zoom) >> 6); 390 | 391 | u16 color = 0xFF; 392 | if (ofs & 0x8000) { 393 | // TODO: sprite drawing 394 | color = (*gfx_data) & 0x7F; 395 | gfx_data += 2; 396 | ofs &= 0x7FFF; 397 | } 398 | 399 | u8 *bak = gfx_data; 400 | gfx_data = gfx_data_base + (ofs << 1); 401 | gfx_draw_shape(color, zoom, cx, cy); 402 | gfx_data = bak; 403 | } 404 | } 405 | 406 | void gfx_draw_shape(u8 color, u16 zoom, s16 x, s16 y) { 407 | u8 i = gfx_fetch_u8(); 408 | 409 | if (i >= 0xC0) { 410 | if (color & 0x80) color = i & 0x3F; 411 | gfx_fill_polygon(color, zoom, x, y); 412 | } else { 413 | i &= 0x3F; 414 | if (i == 2) 415 | gfx_draw_shape_hierarchy(zoom, x, y); 416 | } 417 | } 418 | 419 | void gfx_fill_page(const int page, u8 color) { 420 | u8 *pagedata = gfx_get_page(page); 421 | // memset_w sets 4 bytes per step, so we gotta dup our color 422 | const u32 color_w = color | (color << 8) | (color << 16) | (color << 24); 423 | memset_w(pagedata, color_w, PAGE_W * PAGE_H); 424 | } 425 | 426 | void gfx_copy_page(int src, int dst, s16 yscroll) { 427 | if (src >= 0xFE || ((src &= ~0x40) & 0x80) == 0) { 428 | // no y scroll 429 | memcpy_w(gfx_get_page(dst), gfx_get_page(src), PAGE_H * PAGE_W); 430 | } else { 431 | const u8 *srcpage = gfx_get_page(src & 3); 432 | u8 *dstpage = gfx_get_page(dst); 433 | if (srcpage != dstpage && yscroll >= -199 && yscroll <= 199) { 434 | if (yscroll < 0) 435 | memcpy_w(dstpage, srcpage - yscroll * PAGE_W, (PAGE_H + yscroll) * PAGE_W); 436 | else 437 | memcpy_w(dstpage + yscroll * PAGE_W, srcpage, (PAGE_H - yscroll) * PAGE_W); 438 | } 439 | } 440 | } 441 | 442 | void gfx_blit_bitmap(const u8 *ptr, const u32 size) { 443 | // decode; assumes amiga format 444 | register u8 *dst = gfx_page[0]; 445 | register const u8 *src = ptr; 446 | for (int y = 0; y < PAGE_H; ++y) { 447 | for (int x = 0; x < PAGE_W; x += 8) { 448 | for (int b = 0; b < 8; ++b) { 449 | const int mask = 1 << (7 - b); 450 | u8 c = 0; 451 | if (src[0 * BITMAP_PLANE_SIZE] & mask) c |= 1 << 0; 452 | if (src[1 * BITMAP_PLANE_SIZE] & mask) c |= 1 << 1; 453 | if (src[2 * BITMAP_PLANE_SIZE] & mask) c |= 1 << 2; 454 | if (src[3 * BITMAP_PLANE_SIZE] & mask) c |= 1 << 3; 455 | *dst++ = c; 456 | } 457 | ++src; 458 | } 459 | } 460 | } 461 | 462 | static inline void gfx_draw_char(const u8 color, char ch, const s16 x, const s16 y) { 463 | const u8 *fchbase = gfx_font + ((ch - 0x20) << 3); 464 | const int ofs = x + y * PAGE_W; 465 | for (int j = 0; j < 8; ++j) { 466 | const u8 fch = fchbase[j]; 467 | for (int i = 0; i < 8; ++i) { 468 | if (fch & (1 << (7 - i))) 469 | gfx_page_work[ofs + j * PAGE_W + i] = color; 470 | } 471 | } 472 | } 473 | 474 | void gfx_draw_string(const u8 col, s16 x, s16 y, const u16 strid) { 475 | const char *str = res_get_string(NULL, strid); 476 | if (!str) str = res_get_string(str_tab_demo, strid); 477 | if (!str) { 478 | printf("gfx_draw_string(%d, %d, %d, %d): unknown strid\n", (int)col, (int)x, (int)y, (int)strid); 479 | return; 480 | } 481 | 482 | const u16 startx = x; 483 | const int len = strlen(str); 484 | 485 | for (int i = 0; i < len; ++i) { 486 | if (str[i] == '\n' || str[i] == '\r') { 487 | y += 8; 488 | x = startx; 489 | } else { 490 | gfx_draw_char(col, str[i], x * 8, y); 491 | ++x; 492 | } 493 | } 494 | } 495 | 496 | void gfx_set_font(const u8 *data) { 497 | gfx_font = data; 498 | } 499 | 500 | int gfx_get_default_mode(void) { 501 | return gfx_start_mode; 502 | } 503 | 504 | int gfx_get_current_mode(void) { 505 | return gfx_cur_mode; 506 | } 507 | 508 | void gfx_show_pause(void) { 509 | VSync(0); 510 | DrawSync(0); 511 | // make a greyscale copy of the palette 512 | u16 pal[NUM_COLORS]; 513 | for (int i = 0; i < NUM_COLORS; ++i) { 514 | register const u16 c = gfx_pal[i]; 515 | if (c == 0x8000 || c == 0) { 516 | pal[i] = c; 517 | } else { 518 | const u8 r = (c ) & 0x1F; 519 | const u8 g = (c >> 5) & 0x1F; 520 | const u8 b = (c >> 10) & 0x1F; 521 | const u8 avg = ((r + g + b) / 3) & 0x1F; 522 | pal[i] = avg | (avg << 5) | (avg << 10); 523 | } 524 | } 525 | // suppress any pending palette changes 526 | const u16 palnext = gfx_palnum_next; 527 | const int palupload = gfx_pal_uploaded; 528 | gfx_palnum_next = 0xFF; 529 | gfx_pal_uploaded = 1; 530 | // upload the new palette and update the screen 531 | LoadImage(&gfx_pal_rect, (u32 *)pal); 532 | gfx_update_display(0xFE); 533 | // restore everything and reupload palette 534 | VSync(0); 535 | DrawSync(0); 536 | LoadImage(&gfx_pal_rect, (u32 *)gfx_pal); 537 | gfx_palnum_next = palnext; 538 | gfx_pal_uploaded = palupload; 539 | } 540 | -------------------------------------------------------------------------------- /src/gfx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | #define PAGE_W 320 6 | #define PAGE_H 200 7 | 8 | #define COL_ALPHA 0x10 9 | #define COL_PAGE 0x11 10 | #define COL_BMP 0xFF 11 | 12 | #define ALPHA_COLOR_INDEX 12 13 | 14 | int gfx_init(void); 15 | void gfx_update_display(const int page); 16 | void gfx_set_work_page(const int page); 17 | void gfx_set_databuf(u8 *seg, const u16 ofs); 18 | void gfx_draw_shape(u8 color, u16 zoom, s16 x, s16 y); 19 | void gfx_fill_page(const int page, u8 color); 20 | void gfx_copy_page(int src, int dst, s16 yscroll); 21 | void gfx_set_palette(const u8 palnum); 22 | void gfx_set_next_palette(const u8 palnum); 23 | void gfx_invalidate_palette(void); 24 | void gfx_blit_bitmap(const u8 *ptr, const u32 size); 25 | void gfx_draw_string(const u8 col, s16 x, s16 y, const u16 strid); 26 | void gfx_set_font(const u8 *data); 27 | void gfx_show_pause(void); 28 | u16 gfx_get_current_palette(void); 29 | int gfx_get_default_mode(void); 30 | int gfx_get_current_mode(void); 31 | -------------------------------------------------------------------------------- /src/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "types.h" 4 | #include "gfx.h" 5 | #include "snd.h" 6 | #include "music.h" 7 | #include "pad.h" 8 | #include "res.h" 9 | #include "vm.h" 10 | #include "util.h" 11 | #include "game.h" 12 | #include "menu.h" 13 | 14 | int main(int argc, const char *argv[]) { 15 | gfx_init(); 16 | res_init(); 17 | snd_init(); 18 | mus_init(); 19 | pad_init(); 20 | vm_init(); 21 | 22 | // show our own intro and menu 23 | const int start_part = menu_run(); 24 | 25 | // start the actual game 26 | vm_restart_at(start_part, 0); 27 | 28 | while (1) { 29 | vm_setup_tasks(); 30 | vm_update_input(pad_get_input()); 31 | vm_run(); 32 | snd_update(); 33 | mus_update(); 34 | } 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /src/mem.s: -------------------------------------------------------------------------------- 1 | .set noreorder 2 | 3 | .section .text 4 | 5 | # copy of memcpy from PSn00bSDK's libc, but operating on words 6 | # byte count and addresses must be a multiple of 4 7 | # Arguments: 8 | # a0 - destination address 9 | # a1 - source adress 10 | # a2 - bytes to copy 11 | .global memcpy_w 12 | .type memcpy_w, @function 13 | memcpy_w: 14 | move $v0, $a0 15 | .Lcpy_loop: 16 | blez $a2, .Lcpy_exit 17 | addi $a2, -4 18 | lw $a3, 0($a1) 19 | addiu $a1, 4 20 | sw $a3, 0($a0) 21 | b .Lcpy_loop 22 | addiu $a0, 4 23 | .Lcpy_exit: 24 | jr $ra 25 | nop 26 | 27 | # copy of memset from PSn00bSDK's libc, but operating on words 28 | # byte count and address must be a multiple of 4 29 | # Arguments: 30 | # a0 - address to buffer 31 | # a1 - value to set 32 | # a2 - bytes to set 33 | .global memset_w 34 | .type memset_w, @function 35 | memset_w: 36 | move $v0, $a0 37 | blez $a2, .Lset_exit 38 | addi $a2, -4 39 | sw $a1, 0($a0) 40 | b memset_w 41 | addiu $a0, 4 42 | .Lset_exit: 43 | jr $ra 44 | nop 45 | -------------------------------------------------------------------------------- /src/menu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "types.h" 6 | #include "game.h" 7 | #include "gfx.h" 8 | #include "res.h" 9 | #include "util.h" 10 | #include "tables.h" 11 | #include "vm.h" 12 | #include "pad.h" 13 | #include "menu.h" 14 | 15 | #define BMP_DELPHINE 0x47 16 | #define BMP_ANOTHERW 0x53 17 | #define BMP_OUTOFTHISW 0x13 18 | 19 | #define MENU_START_X 16 // in 8-pixel columns 20 | #define MENU_START_Y 120 21 | 22 | #define TEXT_PALETTE 0x1B 23 | 24 | static inline int wait_vblanks(int n, const int interrupt) { 25 | while (n--) { 26 | VSync(0); 27 | if (interrupt) { 28 | const u32 mask = pad_get_input() | pad_get_special_input(); 29 | if (mask) return 1; 30 | } 31 | } 32 | return 0; 33 | } 34 | 35 | static inline int do_fade(const int from, const int to) { 36 | const int dir = (from > to) ? -1 : 1; 37 | int pal = from; 38 | while (pal != to) { 39 | gfx_set_next_palette(pal); 40 | if (wait_vblanks(2, 1)) return 1; 41 | gfx_update_display(0x00); 42 | pal += dir; 43 | } 44 | gfx_set_next_palette(pal); 45 | gfx_update_display(0x00); 46 | return 0; 47 | } 48 | 49 | static inline void menu_init(void) { 50 | // load the banks for the copy protection screen to get the bitmaps 51 | res_setup_part(PART_COPY_PROTECTION); 52 | // set font palette 53 | gfx_set_next_palette(TEXT_PALETTE); 54 | } 55 | 56 | static inline void menu_intro(void) { 57 | // do the fade-in for the logos and stuff 58 | res_load(BMP_DELPHINE); 59 | if (do_fade(0x17, 0x0F)) goto _interrupted; 60 | if (wait_vblanks(120, 1)) goto _interrupted; // wait ~1.5 sec 61 | if (do_fade(0x0F, 0x17)) goto _interrupted; 62 | res_load(gfx_get_current_mode() == MODE_PAL ? BMP_ANOTHERW : BMP_OUTOFTHISW); 63 | if (do_fade(0x0E, 0x09)) goto _interrupted; 64 | if (wait_vblanks(150, 1)) goto _interrupted; 65 | if (do_fade(0x09, 0x0E)) goto _interrupted; 66 | // draw credits 67 | gfx_set_next_palette(TEXT_PALETTE); 68 | gfx_fill_page(0x00, 0x00); 69 | gfx_draw_string(0x02, 0x12, 0x50, 0x181); // BY 70 | gfx_draw_string(0x03, 0x0F, 0x64, 0x182); // ERIC CHAHI 71 | wait_vblanks(1, 0); 72 | gfx_update_display(0x00); 73 | if (wait_vblanks(120, 1)) goto _interrupted; 74 | gfx_fill_page(0x00, 0x00); 75 | gfx_draw_string(0x04, 0x01, 0x50, 0x183); // MUSIC AND SOUND EFFECTS 76 | gfx_draw_string(0x05, 0x14, 0x64, 0x184); // (DE) 77 | gfx_draw_string(0x06, 0x0B, 0x78, 0x185); // JEAN-FRANCOIS FREITAS 78 | gfx_update_display(0x00); 79 | if (wait_vblanks(120, 1)) goto _interrupted; 80 | /* 81 | gfx_fill_page(0x00, 0x00); 82 | gfx_draw_string(0x04, 0x0E, 0x50, 0x186); // VERSION FOR IBM PC 83 | gfx_draw_string(0x05, 0x0E, 0x64, 0x187); // BY 84 | gfx_draw_string(0x06, 0x0E, 0x78, 0x188); // DANIEL MORAIS 85 | gfx_update_display(0x00); 86 | if (wait_vblanks(120, 1)) goto _interrupted; 87 | */ 88 | _interrupted: 89 | // clear screen 90 | gfx_set_next_palette(TEXT_PALETTE); 91 | gfx_fill_page(0x00, 0x00); 92 | gfx_update_display(0x00); 93 | gfx_set_work_page(0x00); 94 | } 95 | 96 | static inline int menu_choice(const int numstr, const u16 str[]) { 97 | int sel = 0; 98 | u32 old_mask = 0xFFFFFFFF; // hack to make the first frame always render 99 | 100 | while (1) { 101 | VSync(0); 102 | 103 | const u32 mask = pad_get_input() | pad_get_special_input(); 104 | 105 | if ((mask & IN_DIR_DOWN) && !(old_mask & IN_DIR_DOWN)) { 106 | ++sel; 107 | if (sel >= numstr) sel = 0; 108 | } else if ((mask & IN_DIR_UP) && !(old_mask & IN_DIR_UP)) { 109 | --sel; 110 | if (sel < 0) sel = numstr - 1; 111 | } 112 | 113 | if (mask & (IN_ACTION | IN_PAUSE) && !(old_mask & (IN_ACTION | IN_PAUSE))) 114 | break; 115 | 116 | if (old_mask != mask) { 117 | int x = MENU_START_X; 118 | int y = MENU_START_Y; 119 | gfx_fill_page(0x00, 0x00); 120 | for (int i = 0; i < numstr; ++i) { 121 | gfx_draw_string(sel == i ? 0x06 : 0x02, x, y, str[i]); 122 | y += 8 + 2; 123 | } 124 | gfx_update_display(0x00); 125 | } 126 | 127 | old_mask = mask; 128 | } 129 | 130 | return sel; 131 | } 132 | 133 | static inline int menu_language(void) { 134 | const u16 strings[] = { 0x401, 0x402 }; // ENGLISH, FRENCH 135 | return menu_choice(2, strings); 136 | } 137 | 138 | static inline int menu_start_password(void) { 139 | const u16 strings[] = { 0x410, 0x411 }; // NEW GAME, PASSWORD 140 | return menu_choice(2, strings); 141 | } 142 | 143 | int menu_run(void) { 144 | menu_init(); 145 | res_str_tab = menu_language() ? str_tab_fr : str_tab_en; 146 | if (res_have_password) 147 | menu_intro(); // demo already has an intro in itself 148 | const int part = (res_have_password && menu_start_password()) ? 149 | PART_PASSWORD : START_PART; 150 | // clear screen and all palettes 151 | gfx_set_next_palette(0x00); 152 | gfx_fill_page(0x00, 0x00); 153 | gfx_update_display(0x00); 154 | gfx_invalidate_palette(); 155 | return part; 156 | } 157 | -------------------------------------------------------------------------------- /src/menu.h: -------------------------------------------------------------------------------- 1 | #pragma one 2 | 3 | int menu_run(void); 4 | -------------------------------------------------------------------------------- /src/music.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "types.h" 8 | #include "res.h" 9 | #include "snd.h" 10 | #include "util.h" 11 | #include "tables.h" 12 | #include "game.h" 13 | #include "gfx.h" 14 | #include "vm.h" 15 | #include "music.h" 16 | 17 | #define NUM_INST 15 18 | #define NUM_CH 4 19 | #define MAX_ORDER 0x80 20 | #define PAT_SIZE 1024 21 | 22 | typedef struct { 23 | const u8 *data; 24 | u16 vol; 25 | } mus_inst_t; 26 | 27 | typedef struct { 28 | const u8 *data; 29 | u16 pos; 30 | u8 cur_order; 31 | u8 num_order; 32 | u8 order_tab[MAX_ORDER]; 33 | mus_inst_t inst[NUM_INST]; 34 | } mus_module_t; 35 | 36 | static mus_module_t mus_mod; 37 | static u32 mus_delay = 0; 38 | static u32 mus_base_clock = 0; 39 | 40 | static volatile int mus_playing = 0; 41 | static volatile int mus_request_stop = 0; 42 | 43 | static void mus_callback(void); 44 | 45 | // https://github.com/grumpycoders/pcsx-redux/blob/main/src/mips/modplayer/modplayer.c:195 46 | static inline u32 mus_get_base_clock(void) { 47 | static const u32 hblanks_per_sec[4] = { 48 | 15734, // !mode && !bios => 262.5 * 59.940 49 | 15591, // !mode && bios => 262.5 * 59.393 50 | 15769, // mode && !bios => 312.5 * 50.460 51 | 15625, // mode && bios => 312.5 * 50.000 52 | }; 53 | const u32 is_pal_bios = gfx_get_default_mode() == MODE_PAL; 54 | const u32 is_pal_mode = gfx_get_current_mode() == MODE_PAL; 55 | const u32 clk = hblanks_per_sec[(is_pal_mode << 1) | is_pal_bios]; 56 | return is_pal_mode ? clk : (clk * 5 / 6); 57 | } 58 | 59 | static inline u32 mus_get_delay_ticks(const u32 delay) { 60 | // get our hblank clock ticks from meme amiga ticks and hope it doesn't overflow 61 | const u32 ms = delay * 60 / 7050; 62 | const u32 bpm = 60000 / ms; 63 | return mus_base_clock * 60 / bpm; 64 | } 65 | 66 | void mus_init(void) { 67 | // detect clocks per second 68 | mus_base_clock = mus_get_base_clock(); 69 | printf("mus_init(): base BPM clock: %u\n", mus_base_clock); 70 | // set up timer 71 | EnterCriticalSection(); 72 | SetRCnt(RCntCNT1, 0xFFFF, RCntMdINTR); // set it to max for now 73 | InterruptCallback(5, mus_callback); // IRQ5 is RCNT1 74 | ExitCriticalSection(); 75 | } 76 | 77 | static inline void mus_load_instruments(const u8 *p) { 78 | for (int i = 0; i < NUM_INST; ++i) { 79 | mus_inst_t *inst = mus_mod.inst + i; 80 | const u16 resid = read16be(p); p += 2; 81 | if (resid != 0) { 82 | inst->vol = read16be(p); 83 | const mementry_t *me = res_get_entry(resid); 84 | if (me && me->status == RS_LOADED && me->type == RT_SOUND) 85 | inst->data = me->bufptr; 86 | else 87 | printf("mus_load_instruments(): %04x is not a sound resource\n", resid); 88 | } 89 | p += 2; 90 | } 91 | } 92 | 93 | void mus_load(const u16 resid, const u16 delay, const u8 pos) { 94 | const mementry_t *me = res_get_entry(resid); 95 | ASSERT(me != NULL); 96 | 97 | if (me->status != RS_LOADED || me->type != RT_MUSIC) { 98 | printf("mus_load(): %04x is not a music resource\n", resid); 99 | return; 100 | } 101 | 102 | memset(&mus_mod, 0, sizeof(mus_mod)); 103 | 104 | mus_mod.cur_order = pos; 105 | mus_mod.num_order = read16be(me->bufptr + 0x3E); 106 | memcpy(mus_mod.order_tab, me->bufptr + 0x40, sizeof(mus_mod.order_tab)); 107 | 108 | if (delay == 0) 109 | mus_delay = read16be(me->bufptr); 110 | else 111 | mus_delay = delay; 112 | 113 | mus_delay = mus_get_delay_ticks(mus_delay); 114 | 115 | mus_mod.data = me->bufptr + 0xC0; 116 | 117 | printf("mus_load(%04x, %04x, %02x): loading module, delay=%u\n", resid, delay, pos, mus_delay); 118 | 119 | mus_load_instruments(me->bufptr + 0x02); 120 | } 121 | 122 | void mus_start(void) { 123 | mus_mod.pos = 0; 124 | mus_playing = 1; 125 | mus_request_stop = 0; 126 | EnterCriticalSection(); 127 | SetRCnt(RCntCNT1, mus_delay, RCntMdINTR); 128 | StartRCnt(RCntCNT1); 129 | ChangeClearRCnt(1, 0); 130 | ExitCriticalSection(); 131 | } 132 | 133 | void mus_set_delay(const u16 delay) { 134 | mus_delay = mus_get_delay_ticks(delay); 135 | // restart the timer 136 | EnterCriticalSection(); 137 | StopRCnt(RCntCNT1); 138 | SetRCnt(RCntCNT1, mus_delay, RCntMdINTR); 139 | StartRCnt(RCntCNT1); 140 | ChangeClearRCnt(1, 0); 141 | ExitCriticalSection(); 142 | } 143 | 144 | void mus_stop(void) { 145 | mus_playing = 0; 146 | mus_request_stop = 0; 147 | EnterCriticalSection(); 148 | StopRCnt(RCntCNT1); 149 | ExitCriticalSection(); 150 | // stop all channels 151 | snd_stop_all(); 152 | } 153 | 154 | void mus_update(void) { 155 | if (mus_request_stop) 156 | mus_stop(); 157 | } 158 | 159 | static inline void mus_handle_pattern(const u8 ch, const u8 *data) { 160 | const u16 note1 = read16be(data + 0); 161 | const u16 note2 = read16be(data + 2); 162 | const u8 *sndptr = NULL; 163 | s16 sndvol = 0; 164 | 165 | if (note1 == 0xFFFD) { 166 | vm_set_var(VAR_MUS_MARK, note2); 167 | return; 168 | } 169 | 170 | const u16 inst = (note2 & 0xF000) >> 12; 171 | if (inst != 0) { 172 | sndptr = mus_mod.inst[inst - 1].data; 173 | if (sndptr) { 174 | sndvol = mus_mod.inst[inst - 1].vol; 175 | const u8 effect = (note2 & 0x0F00) >> 8; 176 | if (effect == 6) { 177 | // volume down 178 | sndvol -= (note2 & 0xFF); 179 | if (sndvol < 0) sndvol = 0; 180 | } else if (effect == 5) { 181 | // volume up 182 | sndvol += (note2 & 0xFF); 183 | if (sndvol > 0x3F) sndvol = 0x3F; 184 | } 185 | snd_set_sound_vol(ch, sndvol); 186 | } 187 | } 188 | 189 | if (note1 == 0xFFFE) { 190 | snd_stop_sound(ch); 191 | } else if (note1 && sndptr) { 192 | const u16 sndfreq = 7159092 / (note1 << 1); 193 | snd_play_sound(ch, sndptr, sndfreq, sndvol); 194 | } 195 | } 196 | 197 | // interrupt callback 198 | static void mus_callback(void) { 199 | if (!mus_playing || mus_request_stop) return; 200 | 201 | u8 order = mus_mod.order_tab[mus_mod.cur_order]; 202 | const u8 *patdata = mus_mod.data + mus_mod.pos + ((u32)order * PAT_SIZE); 203 | 204 | for (u8 ch = 0; ch < NUM_CH; ++ch) { 205 | mus_handle_pattern(ch, patdata); 206 | patdata += 4; 207 | } 208 | 209 | mus_mod.pos += 4 * NUM_CH; 210 | 211 | if (mus_mod.pos >= PAT_SIZE) { 212 | mus_mod.pos = 0; 213 | order = mus_mod.cur_order + 1; 214 | if (order == mus_mod.num_order) 215 | mus_request_stop = 1; // game loop will fix the rest, since we can't fuck with events from here 216 | else 217 | mus_mod.cur_order = order; 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/music.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | void mus_init(void); 6 | void mus_load(const u16 resid, const u16 delay, const u8 pos); 7 | void mus_start(void); 8 | void mus_set_delay(const u16 delay); 9 | void mus_stop(void); 10 | void mus_update(void); 11 | -------------------------------------------------------------------------------- /src/pad.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "types.h" 5 | #include "pad.h" 6 | 7 | static PADTYPE *pad; 8 | static u8 pad_buf[2][34]; 9 | 10 | void pad_init(void) { 11 | InitPAD(pad_buf[0], sizeof(pad_buf[0]), pad_buf[1], sizeof(pad_buf[1])); 12 | StartPAD(); 13 | ChangeClearPAD(0); 14 | pad = (PADTYPE *)pad_buf[0]; 15 | } 16 | 17 | u32 pad_get_input(void) { 18 | register u32 mask = 0; 19 | if (!(pad->btn & PAD_UP)) mask |= IN_DIR_UP; 20 | if (!(pad->btn & PAD_DOWN)) mask |= IN_DIR_DOWN; 21 | if (!(pad->btn & PAD_LEFT)) mask |= IN_DIR_LEFT; 22 | if (!(pad->btn & PAD_RIGHT)) mask |= IN_DIR_RIGHT; 23 | if (!(pad->btn & PAD_CIRCLE)) mask |= IN_JUMP; 24 | if (!(pad->btn & PAD_CROSS)) mask |= IN_ACTION; 25 | return mask; 26 | } 27 | 28 | u32 pad_get_special_input(void) { 29 | static u32 old_mask = 0; 30 | register u32 mask = 0; 31 | register u32 ret = 0; 32 | // only return special buttons in the moment they're pressed 33 | if (!(pad->btn & PAD_START)) mask |= IN_PAUSE; 34 | if (!(pad->btn & PAD_SELECT)) mask |= IN_PASSWORD; 35 | if ((mask & IN_PAUSE) && !(old_mask & IN_PAUSE)) 36 | ret |= IN_PAUSE; 37 | if ((mask & IN_PASSWORD) && !(old_mask & IN_PASSWORD)) 38 | ret |= IN_PASSWORD; 39 | old_mask = mask; 40 | return ret; 41 | } 42 | -------------------------------------------------------------------------------- /src/pad.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | // matches in game masks 6 | enum input_mask_e { 7 | IN_DIR_RIGHT = 0x01, 8 | IN_DIR_LEFT = 0x02, 9 | IN_DIR_DOWN = 0x04, 10 | IN_DIR_UP = 0x08, 11 | IN_ACTION = 0x80, 12 | IN_JUMP = 1 << 5, 13 | IN_PAUSE = 1 << 6, 14 | IN_PASSWORD = 1 << 7, 15 | }; 16 | 17 | void pad_init(void); 18 | u32 pad_get_input(void); 19 | u32 pad_get_special_input(void); 20 | -------------------------------------------------------------------------------- /src/res.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "types.h" 4 | #include "res.h" 5 | #include "cd.h" 6 | #include "gfx.h" 7 | #include "util.h" 8 | #include "unpack.h" 9 | #include "tables.h" 10 | #include "snd.h" 11 | #include "game.h" 12 | 13 | u8 *res_seg_code; 14 | u8 *res_seg_video[2]; 15 | u8 *res_seg_video_pal; 16 | int res_vidseg_idx; 17 | u16 res_next_part; 18 | u16 res_cur_part; 19 | int res_have_password; 20 | const string_t *res_str_tab; 21 | 22 | static mementry_t res_memlist[NUM_MEMLIST_ENTRIES + 1]; 23 | static u16 res_memlist_num; 24 | 25 | static u8 res_mem[MEMBLOCK_SIZE]; 26 | 27 | static u8 *res_script_ptr; 28 | static u8 *res_script_membase; 29 | static u8 *res_vid_ptr; 30 | static u8 *res_vid_membase; 31 | 32 | typedef struct { 33 | u8 me_pal; 34 | u8 me_code; 35 | u8 me_vid1; 36 | u8 me_vid2; 37 | } mempart_t; 38 | 39 | static const mempart_t res_memlist_parts[] = { 40 | { 0x14, 0x15, 0x16, 0x00 }, // 16000 - protection screens 41 | { 0x17, 0x18, 0x19, 0x00 }, // 16001 - introduction 42 | { 0x1A, 0x1B, 0x1C, 0x11 }, // 16002 - water 43 | { 0x1D, 0x1E, 0x1F, 0x11 }, // 16003 - jail 44 | { 0x20, 0x21, 0x22, 0x11 }, // 16004 - 'cite' 45 | { 0x23, 0x24, 0x25, 0x00 }, // 16005 - 'arene' 46 | { 0x26, 0x27, 0x28, 0x11 }, // 16006 - 'luxe' 47 | { 0x29, 0x2A, 0x2B, 0x11 }, // 16007 - 'final' 48 | { 0x7D, 0x7E, 0x7F, 0x00 }, // 16008 - password screen 49 | { 0x7D, 0x7E, 0x7F, 0x00 } // 16009 - password screen 50 | }; 51 | 52 | void res_init(void) { 53 | cd_init(); 54 | 55 | // read memlist 56 | cd_file_t *f = cd_fopen(MEMLIST_FILENAME, 0); 57 | if (!f) panic("res_init(): could not open data files"); 58 | 59 | res_memlist_num = 0; 60 | mementry_t *me = res_memlist; 61 | while (!cd_feof(f)) { 62 | ASSERT(res_memlist_num < NUM_MEMLIST_ENTRIES + 1); 63 | cd_freadordie(me, sizeof(*me), 1, f); 64 | me->bufptr = NULL; 65 | me->bank_pos = bswap32(me->bank_pos); 66 | me->packed_size = bswap32(me->packed_size); 67 | me->unpacked_size = bswap32(me->unpacked_size); 68 | // terminating entry 69 | if (me->status == 0xFF) break; 70 | ++me; 71 | ++res_memlist_num; 72 | } 73 | 74 | cd_fclose(f); 75 | 76 | printf("res_init(): memlist_num=%d\n", (int)res_memlist_num); 77 | 78 | // check if there's a password screen 79 | const int pwnum = res_memlist_parts[PART_PASSWORD - PART_BASE].me_code; 80 | char bank[16]; 81 | ASSERT(pwnum < res_memlist_num); 82 | snprintf(bank, sizeof(bank), BANK_FILENAME, res_memlist[pwnum].bank); 83 | res_have_password = cd_fexists(bank); 84 | 85 | // set up memory work areas 86 | res_script_membase = res_script_ptr = res_mem; 87 | res_vid_membase = res_vid_ptr = res_mem + MEMBLOCK_SIZE - 0x800 * 16; 88 | 89 | // assume english 90 | res_str_tab = str_tab_en; 91 | } 92 | 93 | void res_invalidate_res(void) { 94 | for (u16 i = 0; i < res_memlist_num; ++i) { 95 | mementry_t *me = res_memlist + i; 96 | if (me->type <= RT_BITMAP || me->type > RT_BANK) 97 | me->status = RS_NULL; 98 | } 99 | res_script_ptr = res_script_membase; 100 | gfx_invalidate_palette(); 101 | snd_clear_cache(); 102 | } 103 | 104 | void res_invalidate_all(void) { 105 | for (u16 i = 0; i < res_memlist_num; ++i) 106 | res_memlist[i].status = RS_NULL; 107 | res_script_ptr = res_mem; 108 | gfx_invalidate_palette(); 109 | snd_clear_cache(); 110 | } 111 | 112 | static int res_read_bank(const mementry_t *me, u8 *out) { 113 | int ret = 0; 114 | char fname[16]; 115 | u32 count = 0; 116 | snprintf(fname, sizeof(fname), BANK_FILENAME, (int)me->bank); 117 | cd_file_t *f = cd_fopen(fname, 1); // allow reopening same handle because we fseek immediately afterwards 118 | if (f) { 119 | cd_fseek(f, me->bank_pos, SEEK_SET); 120 | count = cd_fread(out, me->packed_size, 1, f); 121 | cd_fclose(f); 122 | ret = (count == me->packed_size); 123 | if (ret && (me->packed_size != me->unpacked_size)) { 124 | printf("res_read_bank(%d, %p): unpacking %d to %d (%p)\n", me - res_memlist, out, me->packed_size, me->unpacked_size, out); 125 | ret = bytekiller_unpack(out, me->unpacked_size, out, me->packed_size); 126 | } 127 | } 128 | printf("res_read_bank(%d, %p): bank %d ofs %d count %d packed %d unpacked %d\n", me - res_memlist, out, me->bank, me->bank_pos, count, me->packed_size, me->unpacked_size); 129 | return ret; 130 | } 131 | 132 | static void res_do_load(void) { 133 | while (1) { 134 | // find pending entry with max rank 135 | mementry_t *me = NULL; 136 | u8 max_rank = 0; 137 | for (u16 i = 0; i < res_memlist_num; ++i) { 138 | mementry_t *it = res_memlist + i; 139 | if (it->status == RS_TOLOAD && it->rank >= max_rank) { 140 | me = it; 141 | max_rank = it->rank; 142 | } 143 | } 144 | if (!me) break; 145 | 146 | const int resnum = me - res_memlist; 147 | u8 *memptr = NULL; 148 | if (me->type == RT_BITMAP) { 149 | memptr = res_vid_ptr; 150 | } else { 151 | memptr = res_script_ptr; 152 | // video data seg is after the script data seg, check if they'll intersect 153 | if (me->unpacked_size > (u32)(res_vid_membase - res_script_ptr)) { 154 | printf("res_do_load(): not enough memory to load resource %d\n", resnum); 155 | me->status = RS_NULL; 156 | continue; 157 | } 158 | } 159 | 160 | if (me->bank == 0) { 161 | printf("res_do_load(): res %d has NULL banknum\n", resnum); 162 | me->status = RS_NULL; 163 | } else { 164 | if (res_read_bank(me, memptr)) { 165 | printf("res_do_load(): read res %d (type %d) from bank %d\n", me - res_memlist, me->type, me->bank); 166 | if (me->type == RT_BITMAP) { 167 | gfx_blit_bitmap(res_vid_ptr, me->unpacked_size); 168 | me->status = RS_NULL; 169 | } else { 170 | me->bufptr = memptr; 171 | me->status = RS_LOADED; 172 | res_script_ptr += me->unpacked_size; 173 | if (me->type == RT_SOUND) { 174 | printf("res_do_load(): precaching sound %d size %d\n", resnum, me->unpacked_size); 175 | snd_cache_sound(me->bufptr, me->unpacked_size, SND_TYPE_PCM_WITH_HEADER); 176 | } 177 | } 178 | } else if (me->bank == 12 && me->type == RT_BANK) { 179 | // DOS demo does not have this resource, ignore it 180 | me->status = RS_NULL; 181 | continue; 182 | } else { 183 | panic("res_do_load(): could not load resource %d from bank %d", resnum, (int)me->bank); 184 | } 185 | } 186 | } 187 | } 188 | 189 | void res_setup_part(const u16 part_id) { 190 | if (part_id != res_cur_part) { 191 | if (part_id < PART_BASE || part_id > PART_LAST) 192 | panic("res_setup_part(%05d): invalid part", (int)part_id); 193 | 194 | const mempart_t part = res_memlist_parts[part_id - PART_BASE]; 195 | res_invalidate_all(); 196 | 197 | res_memlist[part.me_pal ].status = RS_TOLOAD; 198 | res_memlist[part.me_code].status = RS_TOLOAD; 199 | res_memlist[part.me_vid1].status = RS_TOLOAD; 200 | if (part.me_vid2 != 0) 201 | res_memlist[part.me_vid2].status = RS_TOLOAD; 202 | res_do_load(); 203 | 204 | res_seg_video_pal = res_memlist[part.me_pal].bufptr; 205 | res_seg_code = res_memlist[part.me_code].bufptr; 206 | res_seg_video[0] = res_memlist[part.me_vid1].bufptr; 207 | if (part.me_vid2 != 0) 208 | res_seg_video[1] = res_memlist[part.me_vid2].bufptr; 209 | 210 | res_cur_part = part_id; 211 | } 212 | 213 | res_script_membase = res_script_ptr; 214 | } 215 | 216 | void res_load(const u16 res_id) { 217 | if (res_id > PART_BASE) { 218 | res_next_part = res_id; 219 | return; 220 | } 221 | mementry_t *me = res_memlist + res_id; 222 | if (me->status == RS_NULL) { 223 | me->status = RS_TOLOAD; 224 | res_do_load(); 225 | } 226 | } 227 | 228 | const mementry_t *res_get_entry(const u16 res_id) { 229 | if (res_id >= PART_BASE) 230 | return NULL; 231 | return res_memlist + res_id; 232 | } 233 | 234 | const char *res_get_string(const string_t *strtab, const u16 str_id) { 235 | if (strtab == NULL) strtab = res_str_tab; 236 | if (strtab == NULL) return NULL; 237 | for (u16 i = 0; i < 0x100 && strtab[i].id != 0xFFFF; ++i) { 238 | if (strtab[i].id == str_id) 239 | return strtab[i].str; 240 | } 241 | return NULL; 242 | } 243 | -------------------------------------------------------------------------------- /src/res.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | #include "tables.h" 5 | 6 | #define NUM_MEMLIST_ENTRIES 146 7 | #define MEMLIST_FILENAME "\\DATA\\MEMLIST.BIN;1" 8 | #define BANK_FILENAME "\\DATA\\BANK%02X;1" 9 | #define MEMBLOCK_SIZE 1 * 1024 * 1024 10 | 11 | #pragma pack(push, 1) 12 | 13 | typedef struct { 14 | u8 status; // 0x0 15 | u8 type; // 0x1 16 | u8 *bufptr; // 0x2 17 | u8 rank; // 0x6 18 | u8 bank; // 0x7 19 | u32 bank_pos; // 0x8 20 | u32 packed_size; // 0xC 21 | u32 unpacked_size; // 0x12 22 | } mementry_t; 23 | 24 | #pragma pack(pop) 25 | 26 | enum res_type_e { 27 | RT_SOUND = 0, 28 | RT_MUSIC = 1, 29 | RT_BITMAP = 2, // full screen 4bpp video buffer, size=200*320/2 30 | RT_PALETTE = 3, // palette (1024=vga + 1024=ega), size=2048 31 | RT_BYTECODE = 4, 32 | RT_SHAPE = 5, 33 | RT_BANK = 6, // common part shapes (bank2.mat) 34 | }; 35 | 36 | enum res_status_e { 37 | RS_NULL = 0, 38 | RS_LOADED = 1, 39 | RS_TOLOAD = 2, 40 | }; 41 | 42 | extern u8 *res_seg_code; 43 | extern u8 *res_seg_video[2]; 44 | extern u8 *res_seg_video_pal; 45 | extern int res_vidseg_idx; 46 | extern u16 res_next_part; 47 | extern u16 res_cur_part; 48 | extern const string_t *res_str_tab; 49 | extern int res_have_password; 50 | 51 | void res_init(void); 52 | void res_invalidate_res(void); 53 | void res_invalidate_all(void); 54 | void res_setup_part(const u16 part_id); 55 | void res_load(const u16 res_id); 56 | const char *res_get_string(const string_t *strtab, const u16 str_id); 57 | const mementry_t *res_get_entry(const u16 res_id); 58 | -------------------------------------------------------------------------------- /src/snd.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "types.h" 8 | #include "util.h" 9 | #include "adpcm.h" 10 | #include "snd.h" 11 | 12 | #define SPU_MEM_MAX 0x80000 13 | #define SPU_MEM_START 0x1100 14 | 15 | #define SPU_VOL_MAX +0x3FFF 16 | #define SPU_VOL_MIN -0x4000 17 | #define SPU_VOL_RANGE (SPU_VOL_MAX - SPU_VOL_MIN) 18 | 19 | #define CH_SOUND_BASE 4 20 | #define CH_MUSIC_BASE 0 21 | #define CH_SOUND_COUNT 4 22 | #define CH_MUSIC_COUNT 4 23 | 24 | #define VAG_DATA_OFFSET 48 25 | #define PCM_DATA_OFFSET 8 26 | 27 | #define SND_CVTBUF_SIZE (64 * 1024) 28 | 29 | #define SPU_VOICE_BASE ((volatile u16 *)(0x1F801C00)) 30 | #define SPU_KEY_ON_LO ((volatile u16 *)(0x1F801D88)) 31 | #define SPU_KEY_ON_HI ((volatile u16 *)(0x1F801D8A)) 32 | #define SPU_KEY_OFF_LO ((volatile u16 *)(0x1F801D8C)) 33 | #define SPU_KEY_OFF_HI ((volatile u16 *)(0x1F801D8E)) 34 | 35 | struct spu_voice { 36 | volatile s16 vol_left; 37 | volatile s16 vol_right; 38 | volatile u16 sample_rate; 39 | volatile u16 sample_startaddr; 40 | volatile u16 attack_decay; 41 | volatile u16 sustain_release; 42 | volatile u16 vol_current; 43 | volatile u16 sample_repeataddr; 44 | }; 45 | #define SPU_VOICE(x) (((volatile struct spu_voice *)SPU_VOICE_BASE) + (x)) 46 | 47 | struct sound { 48 | const u8 *addr; 49 | s32 spuaddr; 50 | s32 size; 51 | }; 52 | 53 | static u8 snd_cvtbuf[SND_CVTBUF_SIZE] __attribute__((aligned(64))); 54 | 55 | static sound_t snd_cache[MAX_SOUNDS]; 56 | static u32 snd_cache_num = 0; 57 | static s32 snd_spu_ptr = 0; // current SPU mem address 58 | 59 | static u32 snd_key_mask = 0; 60 | 61 | static inline u16 freq2pitch(const u32 hz) { 62 | return (hz << 12) / 44100; 63 | } 64 | 65 | static inline s32 spu_alloc(s32 size) { 66 | // SPU likes 8-byte alignment 67 | size = ALIGN(size, 8); 68 | ASSERT(snd_spu_ptr + size <= SPU_MEM_MAX); 69 | const s32 ptr = snd_spu_ptr; 70 | snd_spu_ptr += size; 71 | return ptr; 72 | } 73 | 74 | static inline void spu_key_on(const u32 mask) { 75 | SpuWait(); // TODO: is this advisable to do in an interrupt handler? 76 | *SPU_KEY_ON_LO = mask; 77 | *SPU_KEY_ON_HI = mask >> 16; 78 | } 79 | 80 | static inline void spu_key_off(const u32 mask) { 81 | SpuWait(); // TODO: is this advisable to do in an interrupt handler? 82 | *SPU_KEY_OFF_LO = mask; 83 | *SPU_KEY_OFF_HI = mask >> 16; 84 | } 85 | 86 | static inline void spu_clear_voice(const u32 v) { 87 | SPU_VOICE(v)->vol_left = 0; 88 | SPU_VOICE(v)->vol_right = 0; 89 | SPU_VOICE(v)->sample_rate = 0; 90 | SPU_VOICE(v)->sample_startaddr = 0; 91 | SPU_VOICE(v)->sample_repeataddr = 0; 92 | SPU_VOICE(v)->attack_decay = 0x000F; 93 | SPU_VOICE(v)->sustain_release = 0x0000; 94 | SPU_VOICE(v)->vol_current = 0; 95 | } 96 | 97 | // unfortunately the psn00bsdk function for this is bugged: 98 | // it checks against 0x1000..0xffff instead of 0x1000..0x7ffff 99 | // fortunately, the address is stored in a global variable 100 | // unfortunately, reading it from C requires GP-relative addressing 101 | // so we have to implement the function in assembly (see spu.s) 102 | extern u32 spu_set_transfer_addr(const u32 addr); 103 | 104 | void snd_init(void) { 105 | SpuInit(); 106 | snd_clear_cache(); 107 | snd_stop_all(); 108 | for (u32 v = 0; v < 24; ++v) 109 | spu_clear_voice(v); 110 | } 111 | 112 | void snd_clear_cache(void) { 113 | for (int i = 0; i < MAX_SOUNDS; ++i) { 114 | // mark as unloaded 115 | snd_cache[i].spuaddr = -1; 116 | snd_cache[i].addr = NULL; 117 | snd_cache[i].size = 0; 118 | } 119 | // reset allocator 120 | snd_spu_ptr = SPU_MEM_START; 121 | snd_cache_num = 0; 122 | } 123 | 124 | static inline sound_t *snd_cache_find(const u8 *ptr) { 125 | for (int i = 0; i < MAX_SOUNDS; ++i) 126 | if (snd_cache[i].addr == ptr) 127 | return snd_cache + i; 128 | return NULL; 129 | } 130 | 131 | static u16 snd_convert_pcm(u8 *out, u32 outsize, const u8 *in, u32 insize, int loop0, int loop1) { 132 | const s32 adpcm_size = adpcm_pack_mono_s8(out, outsize, (const s8 *)in, insize, loop0, loop1); 133 | ASSERT(adpcm_size >= 0); 134 | return adpcm_size; 135 | } 136 | 137 | sound_t *snd_cache_sound(const u8 *data, u16 size, const int type) { 138 | sound_t *snd = snd_cache_find(data); 139 | if (snd) { 140 | printf("snd_cache_sound(%p): already cached as %d\n", data, snd - snd_cache); 141 | return snd; 142 | } 143 | 144 | u8 *cvtbuf = NULL; // in case we need to convert the sound 145 | 146 | snd = &snd_cache[snd_cache_num++]; 147 | snd->addr = data; 148 | if (size == 0) { 149 | // NULL sound 150 | snd->spuaddr = 0; 151 | snd->size = 0; 152 | return snd; 153 | } else if (type == SND_TYPE_VAG) { 154 | // sound is already in VAG format, just load it in 155 | snd->spuaddr = spu_alloc(size); 156 | snd->size = size - VAG_DATA_OFFSET; // skip header 157 | data += VAG_DATA_OFFSET; // skip header 158 | } else { 159 | int loopstart = -1; // loop start position, in PCM samples 160 | int loopend = -1; // loop end position 161 | // there might be a header 162 | if (type == SND_TYPE_PCM_WITH_HEADER) { 163 | const s32 lstart = read16be(data) << 1; 164 | const s32 lsize = read16be(data + 2) << 1; 165 | if (lsize) { 166 | // there's a loop point; the ADPCM converter will take care of that 167 | loopstart = lstart; 168 | loopend = (s32)lstart + lsize; 169 | } 170 | size = lstart + lsize; 171 | data += PCM_DATA_OFFSET; // skip header 172 | } 173 | // need to convert it, output will be at most the same size 174 | // SPU transfers are done in blocks of 64, so we'll just align all sizes to that 175 | const u32 alignedsize = ALIGN(size, 64); 176 | ASSERT(alignedsize <= sizeof(snd_cvtbuf)); 177 | snd->size = snd_convert_pcm(snd_cvtbuf, sizeof(snd_cvtbuf), data, size, loopstart, loopend); 178 | snd->size = ALIGN(snd->size, 64); 179 | snd->spuaddr = spu_alloc(snd->size); 180 | data = snd_cvtbuf; 181 | } 182 | 183 | SpuSetTransferMode(SPU_TRANSFER_BY_DMA); 184 | spu_set_transfer_addr(snd->spuaddr); 185 | SpuWrite((void *)data, snd->size); 186 | SpuWait(); // wait for transfer to complete 187 | 188 | return snd; 189 | } 190 | 191 | void snd_play_sound(const u8 ch, const u8 *data, const u16 freq, const u8 vol) { 192 | const sound_t *snd = snd_cache_find(data); 193 | if (!snd) { 194 | // FIXME: this will explode if reached from the music timer handler 195 | printf("snd_play_sound(%p): unknown sound\n", data); 196 | return; 197 | } 198 | const u32 chmask = SPU_VOICECH((u32)ch); 199 | if (snd->size && snd->spuaddr >= 0) { 200 | const s16 vvol = (s16)vol << 8; 201 | SPU_VOICE(ch)->vol_left = vvol; 202 | SPU_VOICE(ch)->vol_right = vvol; 203 | SPU_VOICE(ch)->sample_rate = freq2pitch(freq); 204 | SPU_VOICE(ch)->sample_startaddr = ((u32)snd->spuaddr >> 3); 205 | spu_key_on(chmask); // this restarts the channel on the new address 206 | snd_key_mask |= chmask; 207 | } 208 | } 209 | 210 | void snd_stop_sound(const u8 ch) { 211 | snd_key_mask &= SPU_VOICECH((u32)ch); 212 | // just kill the volume, using keyoff produces noticeable pops and delays 213 | snd_set_sound_vol(ch, 0); 214 | } 215 | 216 | void snd_stop_all(void) { 217 | spu_key_off(0xFFFFFF); // kill all voices 218 | } 219 | 220 | void snd_set_sound_vol(const u8 ch, const u8 vol) { 221 | const s16 vvol = (s16)vol << 8; 222 | SPU_VOICE(ch)->vol_left = vvol; 223 | SPU_VOICE(ch)->vol_right = vvol; 224 | } 225 | 226 | void snd_update(void) { 227 | 228 | } 229 | -------------------------------------------------------------------------------- /src/snd.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | #define MAX_SOUNDS 160 // ~110 sounds in game + MOD instruments 6 | 7 | enum sound_type { 8 | SND_TYPE_RAW_PCM, 9 | SND_TYPE_PCM_WITH_HEADER, 10 | SND_TYPE_VAG, 11 | }; 12 | 13 | typedef struct sound sound_t; 14 | 15 | void snd_init(void); 16 | void snd_play_sound(const u8 ch, const u8 *data, const u16 freq, const u8 vol); 17 | void snd_stop_sound(const u8 ch); 18 | void snd_stop_all(void); 19 | void snd_set_sound_vol(const u8 ch, const u8 vol); 20 | void snd_update(void); 21 | 22 | void snd_clear_cache(void); 23 | sound_t *snd_cache_sound(const u8 *data, u16 size, const int type); 24 | -------------------------------------------------------------------------------- /src/spu.s: -------------------------------------------------------------------------------- 1 | .set noreorder 2 | 3 | .include "hwregs_a.h" 4 | 5 | .section .text 6 | 7 | # this is a copy of the function from psn00b that fixes a bug 8 | # where the function tests for 0xffff instead of 0x7ffff 9 | .global spu_set_transfer_addr 10 | .type spu_set_transfer_addr, @function 11 | spu_set_transfer_addr: 12 | li $v0, 0x1000 # Check if value is valid 13 | blt $a0, $v0, .Lbad_value 14 | nop 15 | li $v0, 0x7ffff 16 | bgt $a0, $v0, .Lbad_value 17 | nop 18 | 19 | la $v1, _spu_transfer_addr 20 | srl $v0, $a0, 3 # Set transfer destination address 21 | sh $v0, 0($v1) 22 | 23 | jr $ra 24 | move $v0, $a0 25 | 26 | .Lbad_value: 27 | jr $ra 28 | move $v0, $0 29 | -------------------------------------------------------------------------------- /src/tables.c: -------------------------------------------------------------------------------- 1 | #include "types.h" 2 | #include "tables.h" 3 | 4 | const u8 fnt_default[] = { 5 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x10, 0x00, 6 | 0x28, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x7E, 0x24, 0x24, 0x7E, 0x24, 0x00, 7 | 0x08, 0x3E, 0x48, 0x3C, 0x12, 0x7C, 0x10, 0x00, 0x42, 0xA4, 0x48, 0x10, 0x24, 0x4A, 0x84, 0x00, 8 | 0x60, 0x90, 0x90, 0x70, 0x8A, 0x84, 0x7A, 0x00, 0x08, 0x08, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 9 | 0x06, 0x08, 0x10, 0x10, 0x10, 0x08, 0x06, 0x00, 0xC0, 0x20, 0x10, 0x10, 0x10, 0x20, 0xC0, 0x00, 10 | 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x10, 0x10, 0x7C, 0x10, 0x10, 0x00, 0x00, 11 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0x20, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00, 0x00, 12 | 0x00, 0x00, 0x00, 0x00, 0x10, 0x28, 0x10, 0x00, 0x00, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 0x00, 13 | 0x78, 0x84, 0x8C, 0x94, 0xA4, 0xC4, 0x78, 0x00, 0x10, 0x30, 0x50, 0x10, 0x10, 0x10, 0x7C, 0x00, 14 | 0x78, 0x84, 0x04, 0x08, 0x30, 0x40, 0xFC, 0x00, 0x78, 0x84, 0x04, 0x38, 0x04, 0x84, 0x78, 0x00, 15 | 0x08, 0x18, 0x28, 0x48, 0xFC, 0x08, 0x08, 0x00, 0xFC, 0x80, 0xF8, 0x04, 0x04, 0x84, 0x78, 0x00, 16 | 0x38, 0x40, 0x80, 0xF8, 0x84, 0x84, 0x78, 0x00, 0xFC, 0x04, 0x04, 0x08, 0x10, 0x20, 0x40, 0x00, 17 | 0x78, 0x84, 0x84, 0x78, 0x84, 0x84, 0x78, 0x00, 0x78, 0x84, 0x84, 0x7C, 0x04, 0x08, 0x70, 0x00, 18 | 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x00, 0x18, 0x18, 0x00, 0x10, 0x10, 0x60, 19 | 0x04, 0x08, 0x10, 0x20, 0x10, 0x08, 0x04, 0x00, 0x00, 0x00, 0xFE, 0x00, 0x00, 0xFE, 0x00, 0x00, 20 | 0x20, 0x10, 0x08, 0x04, 0x08, 0x10, 0x20, 0x00, 0x7C, 0x82, 0x02, 0x0C, 0x10, 0x00, 0x10, 0x00, 21 | 0x30, 0x18, 0x0C, 0x0C, 0x0C, 0x18, 0x30, 0x00, 0x78, 0x84, 0x84, 0xFC, 0x84, 0x84, 0x84, 0x00, 22 | 0xF8, 0x84, 0x84, 0xF8, 0x84, 0x84, 0xF8, 0x00, 0x78, 0x84, 0x80, 0x80, 0x80, 0x84, 0x78, 0x00, 23 | 0xF8, 0x84, 0x84, 0x84, 0x84, 0x84, 0xF8, 0x00, 0x7C, 0x40, 0x40, 0x78, 0x40, 0x40, 0x7C, 0x00, 24 | 0xFC, 0x80, 0x80, 0xF0, 0x80, 0x80, 0x80, 0x00, 0x7C, 0x80, 0x80, 0x8C, 0x84, 0x84, 0x7C, 0x00, 25 | 0x84, 0x84, 0x84, 0xFC, 0x84, 0x84, 0x84, 0x00, 0x7C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x7C, 0x00, 26 | 0x04, 0x04, 0x04, 0x04, 0x84, 0x84, 0x78, 0x00, 0x8C, 0x90, 0xA0, 0xE0, 0x90, 0x88, 0x84, 0x00, 27 | 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0xFC, 0x00, 0x82, 0xC6, 0xAA, 0x92, 0x82, 0x82, 0x82, 0x00, 28 | 0x84, 0xC4, 0xA4, 0x94, 0x8C, 0x84, 0x84, 0x00, 0x78, 0x84, 0x84, 0x84, 0x84, 0x84, 0x78, 0x00, 29 | 0xF8, 0x84, 0x84, 0xF8, 0x80, 0x80, 0x80, 0x00, 0x78, 0x84, 0x84, 0x84, 0x84, 0x8C, 0x7C, 0x03, 30 | 0xF8, 0x84, 0x84, 0xF8, 0x90, 0x88, 0x84, 0x00, 0x78, 0x84, 0x80, 0x78, 0x04, 0x84, 0x78, 0x00, 31 | 0x7C, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x84, 0x84, 0x84, 0x84, 0x84, 0x84, 0x78, 0x00, 32 | 0x84, 0x84, 0x84, 0x84, 0x84, 0x48, 0x30, 0x00, 0x82, 0x82, 0x82, 0x82, 0x92, 0xAA, 0xC6, 0x00, 33 | 0x82, 0x44, 0x28, 0x10, 0x28, 0x44, 0x82, 0x00, 0x82, 0x44, 0x28, 0x10, 0x10, 0x10, 0x10, 0x00, 34 | 0xFC, 0x04, 0x08, 0x10, 0x20, 0x40, 0xFC, 0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 35 | 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 36 | 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFE, 37 | 0x3C, 0x30, 0x30, 0x30, 0x30, 0x30, 0x3C, 0x00, 0x00, 0x00, 0x38, 0x04, 0x3C, 0x44, 0x3C, 0x00, 38 | 0x40, 0x40, 0x78, 0x44, 0x44, 0x44, 0x78, 0x00, 0x00, 0x00, 0x3C, 0x40, 0x40, 0x40, 0x3C, 0x00, 39 | 0x04, 0x04, 0x3C, 0x44, 0x44, 0x44, 0x3C, 0x00, 0x00, 0x00, 0x38, 0x44, 0x7C, 0x40, 0x3C, 0x00, 40 | 0x38, 0x44, 0x40, 0x60, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x3C, 0x44, 0x44, 0x3C, 0x04, 0x78, 41 | 0x40, 0x40, 0x58, 0x64, 0x44, 0x44, 0x44, 0x00, 0x10, 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 42 | 0x02, 0x00, 0x02, 0x02, 0x02, 0x02, 0x42, 0x3C, 0x40, 0x40, 0x46, 0x48, 0x70, 0x48, 0x46, 0x00, 43 | 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0xEC, 0x92, 0x92, 0x92, 0x92, 0x00, 44 | 0x00, 0x00, 0x78, 0x44, 0x44, 0x44, 0x44, 0x00, 0x00, 0x00, 0x38, 0x44, 0x44, 0x44, 0x38, 0x00, 45 | 0x00, 0x00, 0x78, 0x44, 0x44, 0x78, 0x40, 0x40, 0x00, 0x00, 0x3C, 0x44, 0x44, 0x3C, 0x04, 0x04, 46 | 0x00, 0x00, 0x4C, 0x70, 0x40, 0x40, 0x40, 0x00, 0x00, 0x00, 0x3C, 0x40, 0x38, 0x04, 0x78, 0x00, 47 | 0x10, 0x10, 0x3C, 0x10, 0x10, 0x10, 0x0C, 0x00, 0x00, 0x00, 0x44, 0x44, 0x44, 0x44, 0x78, 0x00, 48 | 0x00, 0x00, 0x44, 0x44, 0x44, 0x28, 0x10, 0x00, 0x00, 0x00, 0x82, 0x82, 0x92, 0xAA, 0xC6, 0x00, 49 | 0x00, 0x00, 0x44, 0x28, 0x10, 0x28, 0x44, 0x00, 0x00, 0x00, 0x42, 0x22, 0x24, 0x18, 0x08, 0x30, 50 | 0x00, 0x00, 0x7C, 0x08, 0x10, 0x20, 0x7C, 0x00, 0x60, 0x90, 0x20, 0x40, 0xF0, 0x00, 0x00, 0x00, 51 | 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0x00, 0x38, 0x44, 0xBA, 0xA2, 0xBA, 0x44, 0x38, 0x00, 52 | 0x38, 0x44, 0x82, 0x82, 0x44, 0x28, 0xEE, 0x00, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA 53 | }; 54 | 55 | const u16 freq_tab[] = { 56 | 0x0CFF, 0x0DC3, 0x0E91, 0x0F6F, 0x1056, 0x114E, 0x1259, 0x136C, 57 | 0x149F, 0x15D9, 0x1726, 0x1888, 0x19FD, 0x1B86, 0x1D21, 0x1EDE, 58 | 0x20AB, 0x229C, 0x24B3, 0x26D7, 0x293F, 0x2BB2, 0x2E4C, 0x3110, 59 | 0x33FB, 0x370D, 0x3A43, 0x3DDF, 0x4157, 0x4538, 0x4998, 0x4DAE, 60 | 0x5240, 0x5764, 0x5C9A, 0x61C8, 0x6793, 0x6E19, 0x7485, 0x7BBD 61 | }; 62 | 63 | const string_t str_tab_fr[] = { 64 | { 0x001, "P E A N U T 3000" }, 65 | { 0x002, "Copyright } 1990 Peanut Computer, Inc.\nAll rights reserved.\n\nCDOS Version 5.01" }, 66 | { 0x003, "2" }, 67 | { 0x004, "3" }, 68 | { 0x005, "." }, 69 | { 0x006, "A" }, 70 | { 0x007, "@" }, 71 | { 0x008, "PEANUT 3000" }, 72 | { 0x00A, "R" }, 73 | { 0x00B, "U" }, 74 | { 0x00C, "N" }, 75 | { 0x00D, "P" }, 76 | { 0x00E, "R" }, 77 | { 0x00F, "O" }, 78 | { 0x010, "J" }, 79 | { 0x011, "E" }, 80 | { 0x012, "C" }, 81 | { 0x013, "T" }, 82 | { 0x014, "Shield 9A.5f Ok" }, 83 | { 0x015, "Flux % 5.0177 Ok" }, 84 | { 0x016, "CDI Vector ok" }, 85 | { 0x017, " %%%ddd ok" }, 86 | { 0x018, "Race-Track ok" }, 87 | { 0x019, "SYNCHROTRON" }, 88 | { 0x01A, "E: 23%\ng: .005\n\nRK: 77.2L\n\nopt: g+\n\n Shield:\n1: OFF\n2: ON\n3: ON\n\nP~: 1\n" }, 89 | { 0x01B, "ON" }, 90 | { 0x01C, "-" }, 91 | { 0x021, "|" }, 92 | { 0x022, "--- Etude theorique ---" }, 93 | { 0x023, " L'EXPERIENCE DEBUTERA DANS SECONDES." }, 94 | { 0x024, "20" }, 95 | { 0x025, "19" }, 96 | { 0x026, "18" }, 97 | { 0x027, "4" }, 98 | { 0x028, "3" }, 99 | { 0x029, "2" }, 100 | { 0x02A, "1" }, 101 | { 0x02B, "0" }, 102 | { 0x02C, "L E T ' S G O" }, 103 | { 0x031, "- Phase 0:\nINJECTION des particules\ndans le synchrotron" }, 104 | { 0x032, "- Phase 1:\nACCELERATION des particules." }, 105 | { 0x033, "- Phase 2:\nEJECTION des particules\nsur le bouclier." }, 106 | { 0x034, "A N A L Y S E" }, 107 | { 0x035, "- RESULTAT:\nProbabilites de creer de:\n ANTI-MATIERE: 91.V %\n NEUTRINO 27: 0.04 %\n NEUTRINO 424: 18 %\n" }, 108 | { 0x036, "Verification par la pratique O/N ?" }, 109 | { 0x037, "SUR ?" }, 110 | { 0x038, "MODIFICATION DES PARAMETRES\nRELATIFS A L'ACCELERATEUR\nDE PARTICULES (SYNCHROTRON)." }, 111 | { 0x039, "SIMULATION DE L'EXPERIENCE ?" }, 112 | { 0x03C, "t---t" }, 113 | { 0x03D, "000 ~" }, 114 | { 0x03E, ".20x14dd" }, 115 | { 0x03F, "gj5r5r" }, 116 | { 0x040, "tilgor 25%" }, 117 | { 0x041, "12% 33% checked" }, 118 | { 0x042, "D=4.2158005584" }, 119 | { 0x043, "d=10.00001" }, 120 | { 0x044, "+" }, 121 | { 0x045, "*" }, 122 | { 0x046, "% 304" }, 123 | { 0x047, "gurgle 21" }, 124 | { 0x048, "{{{{" }, 125 | { 0x049, "Delphine Software" }, 126 | { 0x04A, "By Eric Chahi" }, 127 | { 0x04B, "5" }, 128 | { 0x04C, "17" }, 129 | { 0x12C, "0" }, 130 | { 0x12D, "1" }, 131 | { 0x12E, "2" }, 132 | { 0x12F, "3" }, 133 | { 0x130, "4" }, 134 | { 0x131, "5" }, 135 | { 0x132, "6" }, 136 | { 0x133, "7" }, 137 | { 0x134, "8" }, 138 | { 0x135, "9" }, 139 | { 0x136, "A" }, 140 | { 0x137, "B" }, 141 | { 0x138, "C" }, 142 | { 0x139, "D" }, 143 | { 0x13A, "E" }, 144 | { 0x13B, "F" }, 145 | { 0x13C, " CODE D'ACCES:" }, 146 | { 0x13D, "PRESSEZ LE BOUTON POUR CONTINUER" }, 147 | { 0x13E, " ENTRER LE CODE D'ACCES" }, 148 | { 0x13F, "MOT DE PASSE INVALIDE !" }, 149 | { 0x140, "ANNULER" }, 150 | { 0x141, " INSEREZ LA DISQUETTE ?\n\n\n\n\n\n\n\n\nPRESSEZ UNE TOUCHE POUR CONTINUER" }, 151 | { 0x142, "SELECTIONNER LES SYMBOLES CORRESPONDANTS\nA LA POSITION\nDE LA ROUE DE PROTECTION" }, 152 | { 0x143, "CHARGEMENT..." }, 153 | { 0x144, " ERREUR" }, 154 | { 0x15E, "LDKD" }, 155 | { 0x15F, "HTDC" }, 156 | { 0x160, "CLLD" }, 157 | { 0x161, "FXLC" }, 158 | { 0x162, "KRFK" }, 159 | { 0x163, "XDDJ" }, 160 | { 0x164, "LBKG" }, 161 | { 0x165, "KLFB" }, 162 | { 0x166, "TTCT" }, 163 | { 0x167, "DDRX" }, 164 | { 0x168, "TBHK" }, 165 | { 0x169, "BRTD" }, 166 | { 0x16A, "CKJL" }, 167 | { 0x16B, "LFCK" }, 168 | { 0x16C, "BFLX" }, 169 | { 0x16D, "XJRT" }, 170 | { 0x16E, "HRTB" }, 171 | { 0x16F, "HBHK" }, 172 | { 0x170, "JCGB" }, 173 | { 0x171, "HHFL" }, 174 | { 0x172, "TFBB" }, 175 | { 0x173, "TXHF" }, 176 | { 0x174, "JHJL" }, 177 | { 0x181, "PAR" }, 178 | { 0x182, "ERIC CHAHI" }, 179 | { 0x183, " MUSIQUES ET BRUITAGES" }, 180 | { 0x184, "DE" }, 181 | { 0x185, "JEAN-FRANCOIS FREITAS" }, 182 | { 0x186, "VERSION IBM PC" }, 183 | { 0x187, " PAR" }, 184 | { 0x188, " DANIEL MORAIS" }, 185 | { 0x18B, "PUIS PRESSER LE BOUTON" }, 186 | { 0x18C, "POSITIONNER LE JOYSTICK EN HAUT A GAUCHE" }, 187 | { 0x18D, " POSITIONNER LE JOYSTICK AU CENTRE" }, 188 | { 0x18E, " POSITIONNER LE JOYSTICK EN BAS A DROITE" }, 189 | { 0x258, " Conception ..... Eric Chahi" }, 190 | { 0x259, " Programmation ..... Eric Chahi" }, 191 | { 0x25A, " Graphismes ....... Eric Chahi" }, 192 | { 0x25B, "Musique de ...... Jean-francois Freitas" }, 193 | { 0x25C, " Bruitages" }, 194 | { 0x25D, " Jean-Francois Freitas\n Eric Chahi" }, 195 | { 0x263, " Merci a" }, 196 | { 0x264, " Jesus Martinez\n\n Daniel Morais\n\n Frederic Savoir\n\n Cecile Chahi\n\n Philippe Delamarre\n\n Philippe Ulrich\n\nSebastien Berthet\n\nPierre Gousseau" }, 197 | { 0x265, "Now Go Back To Another Earth" }, 198 | { 0x190, "Bonsoir professeur." }, 199 | { 0x191, "Je vois que Monsieur a pris\nsa Ferrari." }, 200 | { 0x192, "IDENTIFICATION" }, 201 | { 0x193, "Monsieur est en parfaite sante." }, 202 | { 0x194, "O" }, 203 | { 0x193, "AU BOULOT !!!\n" }, 204 | { 0x401, "ENGLISH" }, 205 | { 0x402, "FRENCH" }, 206 | { 0x410, "NEW GAME" }, 207 | { 0x411, "PASSWORD" }, 208 | { 0xFFFF, 0 } 209 | }; 210 | 211 | const string_t str_tab_en[] = { 212 | { 0x001, "P E A N U T 3000" }, 213 | { 0x002, "Copyright } 1990 Peanut Computer, Inc.\nAll rights reserved.\n\nCDOS Version 5.01" }, 214 | { 0x003, "2" }, 215 | { 0x004, "3" }, 216 | { 0x005, "." }, 217 | { 0x006, "A" }, 218 | { 0x007, "@" }, 219 | { 0x008, "PEANUT 3000" }, 220 | { 0x00A, "R" }, 221 | { 0x00B, "U" }, 222 | { 0x00C, "N" }, 223 | { 0x00D, "P" }, 224 | { 0x00E, "R" }, 225 | { 0x00F, "O" }, 226 | { 0x010, "J" }, 227 | { 0x011, "E" }, 228 | { 0x012, "C" }, 229 | { 0x013, "T" }, 230 | { 0x014, "Shield 9A.5f Ok" }, 231 | { 0x015, "Flux % 5.0177 Ok" }, 232 | { 0x016, "CDI Vector ok" }, 233 | { 0x017, " %%%ddd ok" }, 234 | { 0x018, "Race-Track ok" }, 235 | { 0x019, "SYNCHROTRON" }, 236 | { 0x01A, "E: 23%\ng: .005\n\nRK: 77.2L\n\nopt: g+\n\n Shield:\n1: OFF\n2: ON\n3: ON\n\nP~: 1\n" }, 237 | { 0x01B, "ON" }, 238 | { 0x01C, "-" }, 239 | { 0x021, "|" }, 240 | { 0x022, "--- Theoretical study ---" }, 241 | { 0x023, " THE EXPERIMENT WILL BEGIN IN SECONDS" }, 242 | { 0x024, " 20" }, 243 | { 0x025, " 19" }, 244 | { 0x026, " 18" }, 245 | { 0x027, " 4" }, 246 | { 0x028, " 3" }, 247 | { 0x029, " 2" }, 248 | { 0x02A, " 1" }, 249 | { 0x02B, " 0" }, 250 | { 0x02C, "L E T ' S G O" }, 251 | { 0x031, "- Phase 0:\nINJECTION of particles\ninto synchrotron" }, 252 | { 0x032, "- Phase 1:\nParticle ACCELERATION." }, 253 | { 0x033, "- Phase 2:\nEJECTION of particles\non the shield." }, 254 | { 0x034, "A N A L Y S I S" }, 255 | { 0x035, "- RESULT:\nProbability of creating:\n ANTIMATTER: 91.V %\n NEUTRINO 27: 0.04 %\n NEUTRINO 424: 18 %\n" }, 256 | { 0x036, " Practical verification Y/N ?" }, 257 | { 0x037, "SURE ?" }, 258 | { 0x038, "MODIFICATION OF PARAMETERS\nRELATING TO PARTICLE\nACCELERATOR (SYNCHROTRON)." }, 259 | { 0x039, " RUN EXPERIMENT ?" }, 260 | { 0x03C, "t---t" }, 261 | { 0x03D, "000 ~" }, 262 | { 0x03E, ".20x14dd" }, 263 | { 0x03F, "gj5r5r" }, 264 | { 0x040, "tilgor 25%" }, 265 | { 0x041, "12% 33% checked" }, 266 | { 0x042, "D=4.2158005584" }, 267 | { 0x043, "d=10.00001" }, 268 | { 0x044, "+" }, 269 | { 0x045, "*" }, 270 | { 0x046, "% 304" }, 271 | { 0x047, "gurgle 21" }, 272 | { 0x048, "{{{{" }, 273 | { 0x049, "Delphine Software" }, 274 | { 0x04A, "By Eric Chahi" }, 275 | { 0x04B, " 5" }, 276 | { 0x04C, " 17" }, 277 | { 0x12C, "0" }, 278 | { 0x12D, "1" }, 279 | { 0x12E, "2" }, 280 | { 0x12F, "3" }, 281 | { 0x130, "4" }, 282 | { 0x131, "5" }, 283 | { 0x132, "6" }, 284 | { 0x133, "7" }, 285 | { 0x134, "8" }, 286 | { 0x135, "9" }, 287 | { 0x136, "A" }, 288 | { 0x137, "B" }, 289 | { 0x138, "C" }, 290 | { 0x139, "D" }, 291 | { 0x13A, "E" }, 292 | { 0x13B, "F" }, 293 | { 0x13C, " ACCESS CODE:" }, 294 | { 0x13D, "PRESS BUTTON OR RETURN TO CONTINUE" }, 295 | { 0x13E, " ENTER ACCESS CODE" }, 296 | { 0x13F, " INVALID PASSWORD !" }, 297 | { 0x140, "ANNULER" }, 298 | { 0x141, " INSERT DISK ?\n\n\n\n\n\n\n\n\nPRESS ANY KEY TO CONTINUE" }, 299 | { 0x142, " SELECT SYMBOLS CORRESPONDING TO\n THE POSITION\n ON THE CODE WHEEL" }, 300 | { 0x143, " LOADING..." }, 301 | { 0x144, " ERROR" }, 302 | { 0x15E, "LDKD" }, 303 | { 0x15F, "HTDC" }, 304 | { 0x160, "CLLD" }, 305 | { 0x161, "FXLC" }, 306 | { 0x162, "KRFK" }, 307 | { 0x163, "XDDJ" }, 308 | { 0x164, "LBKG" }, 309 | { 0x165, "KLFB" }, 310 | { 0x166, "TTCT" }, 311 | { 0x167, "DDRX" }, 312 | { 0x168, "TBHK" }, 313 | { 0x169, "BRTD" }, 314 | { 0x16A, "CKJL" }, 315 | { 0x16B, "LFCK" }, 316 | { 0x16C, "BFLX" }, 317 | { 0x16D, "XJRT" }, 318 | { 0x16E, "HRTB" }, 319 | { 0x16F, "HBHK" }, 320 | { 0x170, "JCGB" }, 321 | { 0x171, "HHFL" }, 322 | { 0x172, "TFBB" }, 323 | { 0x173, "TXHF" }, 324 | { 0x174, "JHJL" }, 325 | { 0x181, " BY" }, 326 | { 0x182, "ERIC CHAHI" }, 327 | { 0x183, " MUSIC AND SOUND EFFECTS" }, 328 | { 0x184, " " }, 329 | { 0x185, "JEAN-FRANCOIS FREITAS" }, 330 | { 0x186, "IBM PC VERSION" }, 331 | { 0x187, " BY" }, 332 | { 0x188, " DANIEL MORAIS" }, 333 | { 0x18B, " THEN PRESS FIRE" }, 334 | { 0x18C, " PUT THE PADDLE ON THE UPPER LEFT CORNER" }, 335 | { 0x18D, "PUT THE PADDLE IN CENTRAL POSITION" }, 336 | { 0x18E, "PUT THE PADDLE ON THE LOWER RIGHT CORNER" }, 337 | { 0x258, " Designed by ..... Eric Chahi" }, 338 | { 0x259, " Programmed by...... Eric Chahi" }, 339 | { 0x25A, " Artwork ......... Eric Chahi" }, 340 | { 0x25B, "Music by ........ Jean-francois Freitas" }, 341 | { 0x25C, " Sound effects" }, 342 | { 0x25D, " Jean-Francois Freitas\n Eric Chahi" }, 343 | { 0x263, " Thanks To" }, 344 | { 0x264, " Jesus Martinez\n\n Daniel Morais\n\n Frederic Savoir\n\n Cecile Chahi\n\n Philippe Delamarre\n\n Philippe Ulrich\n\nSebastien Berthet\n\nPierre Gousseau" }, 345 | { 0x265, "Now Go Out Of This World" }, 346 | { 0x190, "Good evening professor." }, 347 | { 0x191, "I see you have driven here in your\nFerrari." }, 348 | { 0x192, "IDENTIFICATION" }, 349 | { 0x193, "Monsieur est en parfaite sante." }, 350 | { 0x194, "Y\n" }, 351 | { 0x193, "AU BOULOT !!!\n" }, 352 | { 0x401, "ENGLISH" }, 353 | { 0x402, "FRENCH" }, 354 | { 0x410, "NEW GAME" }, 355 | { 0x411, "PASSWORD" }, 356 | { 0xFFFF, 0 } 357 | }; 358 | 359 | const string_t str_tab_demo[] = { 360 | { 0x1F4, "Over Two Years in the Making" }, 361 | { 0x1F5, " A New, State\nof the Art, Polygon\n Graphics System" }, 362 | { 0x1F6, " Comes to the\nComputer With Full\n Screen Graphics" }, 363 | { 0x1F7, "While conducting a nuclear fission\nexperiment at your local\nparticle accelerator ..." }, 364 | { 0x1F8, "Nature decides to put a little\n extra spin on the ball" }, 365 | { 0x1F9, "And sends you ..." }, 366 | { 0x1FA, " Out of this World\nA Cinematic Action Adventure\n from Interplay Productions\n \n By Eric CHAHI \n\n IBM version : D.MORAIS\n" }, 367 | { 0xFFFF, 0 } 368 | }; 369 | -------------------------------------------------------------------------------- /src/tables.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | extern const u16 freq_tab[]; 6 | extern const u8 fnt_default[]; 7 | extern const string_t str_tab_fr[]; 8 | extern const string_t str_tab_en[]; 9 | extern const string_t str_tab_demo[]; 10 | -------------------------------------------------------------------------------- /src/types.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #ifndef NULL 6 | #define NULL ((void *)0) 7 | #endif 8 | 9 | typedef unsigned char u8; 10 | typedef signed char s8; 11 | typedef unsigned short u16; 12 | typedef signed short s16; 13 | typedef unsigned int u32; 14 | typedef signed int s32; 15 | 16 | typedef struct { 17 | u16 id; 18 | const char *str; 19 | } string_t; 20 | -------------------------------------------------------------------------------- /src/unpack.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "types.h" 3 | #include "util.h" 4 | #include "unpack.h" 5 | 6 | typedef struct { 7 | int size; 8 | u32 crc; 9 | u32 bits; 10 | u8 *dst; 11 | const u8 *src; 12 | } unpack_ctx_t; 13 | 14 | static int next_bit(unpack_ctx_t *uc) { 15 | int carry = (uc->bits & 1) != 0; 16 | uc->bits >>= 1; 17 | if (uc->bits == 0) { // getnextlwd 18 | uc->bits = read32be(uc->src); uc->src -= 4; 19 | uc->crc ^= uc->bits; 20 | carry = (uc->bits & 1) != 0; 21 | uc->bits = (1 << 31) | (uc->bits >> 1); 22 | } 23 | return carry; 24 | } 25 | 26 | static int get_bits(unpack_ctx_t *uc, int count) { // rdd1bits 27 | int bits = 0; 28 | for (int i = 0; i < count; ++i) { 29 | bits <<= 1; 30 | if (next_bit(uc)) { 31 | bits |= 1; 32 | } 33 | } 34 | return bits; 35 | } 36 | 37 | static void copy_literal(unpack_ctx_t *uc, int num_bits, int len) { // getd3chr 38 | int count = get_bits(uc, num_bits) + len + 1; 39 | uc->size -= count; 40 | if (uc->size < 0) { 41 | count += uc->size; 42 | uc->size = 0; 43 | } 44 | for (int i = 0; i < count; ++i) { 45 | *(uc->dst - i) = (u8)get_bits(uc, 8); 46 | } 47 | uc->dst -= count; 48 | } 49 | 50 | static void copy_reference(unpack_ctx_t *uc, int num_bits, int count) { // copyd3bytes 51 | uc->size -= count; 52 | if (uc->size < 0) { 53 | count += uc->size; 54 | uc->size = 0; 55 | } 56 | const int offset = get_bits(uc, num_bits); 57 | for (int i = 0; i < count; ++i) { 58 | *(uc->dst - i) = *(uc->dst - i + offset); 59 | } 60 | uc->dst -= count; 61 | } 62 | 63 | int bytekiller_unpack(u8 *dst, int dstsize, const u8 *src, int srcsize) { 64 | unpack_ctx_t uc; 65 | uc.src = src + srcsize - 4; 66 | uc.size = read32be(uc.src); uc.src -= 4; 67 | if (uc.size > dstsize) { 68 | printf("unpack(%p, %d, %p, %d): invalid unpack size %d, buffer size %d", 69 | dst, dstsize, src, srcsize, uc.size, dstsize); 70 | return 0; 71 | } 72 | uc.dst = dst + uc.size - 1; 73 | uc.crc = read32be(uc.src); uc.src -= 4; 74 | uc.bits = read32be(uc.src); uc.src -= 4; 75 | uc.crc ^= uc.bits; 76 | do { 77 | if (!next_bit(&uc)) { 78 | if (!next_bit(&uc)) { 79 | copy_literal(&uc, 3, 0); 80 | } else { 81 | copy_reference(&uc, 8, 2); 82 | } 83 | } else { 84 | const int code = get_bits(&uc, 2); 85 | switch (code) { 86 | case 3: 87 | copy_literal(&uc, 8, 8); 88 | break; 89 | case 2: 90 | copy_reference(&uc, 12, get_bits(&uc, 8) + 1); 91 | break; 92 | case 1: 93 | copy_reference(&uc, 10, 4); 94 | break; 95 | case 0: 96 | copy_reference(&uc, 9, 3); 97 | break; 98 | } 99 | } 100 | } while (uc.size > 0); 101 | return uc.crc == 0; 102 | } 103 | 104 | -------------------------------------------------------------------------------- /src/unpack.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | int bytekiller_unpack(u8 *dst, int dstsize, const u8 *src, int srcsize); 6 | -------------------------------------------------------------------------------- /src/util.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | extern void abort(void) __attribute__((noreturn)); 6 | 7 | // while our libc has a declaration for assert(), it does not actually provide it 8 | 9 | void do_assert(const int expr, const char *strexpr, const char *file, const int line) { 10 | if (!expr) { 11 | printf("ASSERTION FAILED:\n`%s` at %s:%d\n", strexpr, file, line); 12 | abort(); 13 | } 14 | } 15 | 16 | void panic(const char *fmt, ...) { 17 | char msg[256]; 18 | va_list args; 19 | va_start(args, fmt); 20 | vsnprintf(msg, sizeof(msg), fmt, args); 21 | va_end(args); 22 | printf("FATAL ERROR: %s\n", msg); 23 | abort(); 24 | } 25 | -------------------------------------------------------------------------------- /src/util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | #define ALIGN(x, align) (((x) + ((align) - 1)) & ~((align) - 1)) 6 | #define ASSERT(x) do_assert((x), #x, __FILE__, __LINE__) 7 | 8 | void panic(const char *fmt, ...) __attribute__((noreturn)); 9 | void do_assert(const int, const char *, const char *, const int); 10 | 11 | static inline u16 bswap16(u16 x) { 12 | return (x >> 8) | (x << 8); 13 | } 14 | 15 | static inline u32 bswap32(u32 x) { 16 | return ((x >> 24) | ((x & 0x00FF0000) >> 8) | ((x & 0x0000FF00) << 8) | (x << 24)); 17 | } 18 | 19 | static inline u32 read32be(const u8 *p) { 20 | return p[3] | (p[2] << 8) | (p[1] << 16) | (p[0] << 24); 21 | } 22 | 23 | static inline u16 read16be(const u8 *p) { 24 | return p[1] | (p[0] << 8); 25 | } 26 | 27 | static inline u16 read16le(const u8 *p) { 28 | return p[0] | (p[1] << 8); 29 | } 30 | 31 | // memcpy and memset operating on words (see mem.s) 32 | // addresses and byte count must be multiples of 4 33 | extern void *memcpy_w(void *dst, const void *src, int n); 34 | extern void *memset_w(void *dst, const u32 set, int n); 35 | -------------------------------------------------------------------------------- /src/vm.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "types.h" 6 | #include "vm.h" 7 | #include "util.h" 8 | #include "gfx.h" 9 | #include "snd.h" 10 | #include "music.h" 11 | #include "pad.h" 12 | #include "res.h" 13 | #include "tables.h" 14 | #include "game.h" 15 | 16 | #define VM_NUM_VARS 0x100 17 | #define VM_STACK_DEPTH 0x40 18 | #define VM_NUM_TASKS 0x40 19 | #define VM_NUM_OPCODES 27 20 | 21 | typedef void (* op_func_t)(void); 22 | 23 | static struct { 24 | u8 halt; 25 | s16 vars[VM_NUM_VARS]; 26 | u16 callstack[VM_STACK_DEPTH]; 27 | u16 script_pos[2][VM_NUM_TASKS]; 28 | u8 script_paused[2][VM_NUM_TASKS]; 29 | u8 *pc; 30 | u8 sp; 31 | } vm; 32 | 33 | static u32 time_now; 34 | static u32 time_start; 35 | 36 | static inline u8 vm_fetch_u8(void) { 37 | return *(vm.pc++); 38 | } 39 | 40 | static inline u16 vm_fetch_u16(void) { 41 | const u8 *b = vm.pc; 42 | vm.pc += 2; 43 | return (b[0] << 8) | b[1]; 44 | } 45 | 46 | static void op_mov_const(void) { 47 | const u8 i = vm_fetch_u8(); 48 | const s16 x = vm_fetch_u16(); 49 | vm.vars[i] = x; 50 | } 51 | 52 | static void op_mov(void) { 53 | const u8 i = vm_fetch_u8(); 54 | const u8 j = vm_fetch_u8(); 55 | vm.vars[i] = vm.vars[j]; 56 | } 57 | 58 | static void op_add(void) { 59 | const u8 i = vm_fetch_u8(); 60 | const u8 j = vm_fetch_u8(); 61 | vm.vars[i] += vm.vars[j]; 62 | } 63 | 64 | static void op_add_const(void) { 65 | const u8 i = vm_fetch_u8(); 66 | const s16 x = vm_fetch_u16(); 67 | vm.vars[i] += x; 68 | } 69 | 70 | static void op_call(void) { 71 | const u16 ofs = vm_fetch_u16(); 72 | vm.callstack[vm.sp++] = vm.pc - res_seg_code; 73 | vm.pc = res_seg_code + ofs; 74 | } 75 | 76 | static void op_ret(void) { 77 | vm.pc = res_seg_code + vm.callstack[--vm.sp]; 78 | } 79 | 80 | static void op_break(void) { 81 | vm.halt = 1; 82 | } 83 | 84 | static void op_jmp(void) { 85 | const u16 ofs = vm_fetch_u16(); 86 | vm.pc = res_seg_code + ofs; 87 | } 88 | 89 | static void op_set_script_slot(void) { 90 | const u8 i = vm_fetch_u8(); 91 | const u16 val = vm_fetch_u16(); 92 | vm.script_pos[1][i] = val; 93 | } 94 | 95 | static void op_jnz(void) { 96 | const u8 i = vm_fetch_u8(); 97 | --vm.vars[i]; 98 | if (vm.vars[i]) 99 | op_jmp(); 100 | else 101 | vm_fetch_u16(); 102 | } 103 | 104 | static void op_condjmp(void) { 105 | const u8 op = vm_fetch_u8(); 106 | const s16 b = vm.vars[vm_fetch_u8()]; 107 | const u8 c = vm_fetch_u8(); 108 | s16 a; 109 | if (op & 0x80) 110 | a = vm.vars[c]; 111 | else if (op & 0x40) 112 | a = c * 256 + vm_fetch_u8(); 113 | else 114 | a = c; 115 | int expr = 0; 116 | switch (op & 7) { 117 | case 0: expr = (b == a); break; // jz 118 | case 1: expr = (b != a); break; // jnz 119 | case 2: expr = (b > a); break; // jg 120 | case 3: expr = (b >= a); break; // jge 121 | case 4: expr = (b < a); break; // jl 122 | case 5: expr = (b <= a); break; // jle 123 | default: break; 124 | } 125 | if (expr) 126 | op_jmp(); 127 | else 128 | vm_fetch_u16(); 129 | } 130 | 131 | static void op_set_palette(void) { 132 | const u16 p = vm_fetch_u16() >> 8; 133 | gfx_set_next_palette(p); 134 | } 135 | 136 | static void op_reset_script(void) { 137 | const u8 j = vm_fetch_u8(); 138 | const u8 i = vm_fetch_u8(); 139 | register s8 n = (i & 0x3F) - j; 140 | if (n < 0) { 141 | printf("op_reset_script(): n=%d < 0\n", n); 142 | return; 143 | } 144 | ++n; 145 | const u8 a = vm_fetch_u8(); 146 | if (a == 2) { 147 | register u16 *p = &vm.script_pos[1][j]; 148 | while (n--) *p++ = 0xFFFE; 149 | } else if (a < 2) { 150 | register u8 *p = &vm.script_paused[1][j]; 151 | while (n--) *p++ = a; 152 | } 153 | } 154 | 155 | static void op_select_page(void) { 156 | const u8 p = vm_fetch_u8(); 157 | gfx_set_work_page(p); 158 | } 159 | 160 | static void op_fill_page(void) { 161 | const u8 screen = vm_fetch_u8(); 162 | const u8 color = vm_fetch_u8(); 163 | gfx_fill_page(screen, color); 164 | } 165 | 166 | static void op_copy_page(void) { 167 | const u8 src = vm_fetch_u8(); 168 | const u8 dst = vm_fetch_u8(); 169 | gfx_copy_page(src, dst, vm.vars[VAR_SCROLL_Y]); 170 | } 171 | 172 | static void op_update_display(void) { 173 | static u32 tstamp = 0; 174 | 175 | const u8 page = vm_fetch_u8(); 176 | 177 | vm_handle_special_input(pad_get_special_input()); 178 | 179 | if (res_cur_part == 0x3E80 && vm.vars[0x67] == 1) 180 | vm.vars[0xDC] = 0x21; 181 | 182 | const s32 delay = VSync(-1) - tstamp; 183 | s32 pause = vm.vars[VAR_PAUSE_SLICES] - delay; 184 | for (; pause > 0; --pause) VSync(0); 185 | tstamp = VSync(-1); 186 | 187 | vm.vars[0xF7] = 0; 188 | 189 | gfx_update_display(page); 190 | } 191 | 192 | static void op_halt(void) { 193 | vm.pc = res_seg_code + 0xFFFF; 194 | vm.halt = 1; 195 | } 196 | 197 | static void op_draw_string(void) { 198 | const u16 strid = vm_fetch_u16(); 199 | const u8 x = vm_fetch_u8(); 200 | const u8 y = vm_fetch_u8(); 201 | const u8 col = vm_fetch_u8(); 202 | gfx_draw_string(col, x, y, strid); 203 | } 204 | 205 | static void op_sub(void) { 206 | const u8 i = vm_fetch_u8(); 207 | const u8 j = vm_fetch_u8(); 208 | vm.vars[i] -= vm.vars[j]; 209 | } 210 | 211 | static void op_and(void) { 212 | const u8 i = vm_fetch_u8(); 213 | const u16 x = vm_fetch_u16(); 214 | vm.vars[i] = (u16)vm.vars[i] & x; 215 | } 216 | 217 | static void op_or(void) { 218 | const u8 i = vm_fetch_u8(); 219 | const u16 x = vm_fetch_u16(); 220 | vm.vars[i] = (u16)vm.vars[i] | x; 221 | } 222 | 223 | static void op_shl(void) { 224 | const u8 i = vm_fetch_u8(); 225 | const u16 x = vm_fetch_u16(); 226 | vm.vars[i] = (u16)vm.vars[i] << x; 227 | } 228 | 229 | static void op_shr(void) { 230 | const u8 i = vm_fetch_u8(); 231 | const u16 x = vm_fetch_u16(); 232 | vm.vars[i] = (u16)vm.vars[i] >> x; 233 | } 234 | 235 | static void op_update_memlist(void) { 236 | const u16 num = vm_fetch_u16(); 237 | if (num == 0) { 238 | mus_stop(); 239 | snd_stop_all(); 240 | res_invalidate_res(); 241 | } else { 242 | res_load(num); 243 | } 244 | } 245 | 246 | static void op_play_sound(void) { 247 | const u16 res = vm_fetch_u16(); 248 | const u8 freq = vm_fetch_u8(); 249 | u8 vol = vm_fetch_u8(); 250 | const u8 channel = vm_fetch_u8(); 251 | 252 | if (vol > 63) { 253 | vol = 63; 254 | } else if (vol == 0) { 255 | snd_stop_sound(channel); 256 | return; 257 | } 258 | 259 | const mementry_t *me = res_get_entry(res); 260 | if (me && me->status == RS_LOADED) { 261 | ASSERT(freq < 40); 262 | snd_play_sound(channel & 3, me->bufptr, freq_tab[freq], vol); 263 | } 264 | } 265 | 266 | static void op_play_music(void) { 267 | const u16 res = vm_fetch_u16(); 268 | const u16 delay = vm_fetch_u16(); 269 | const u8 pos = vm_fetch_u8(); 270 | 271 | if (res != 0) { 272 | mus_load(res, delay, pos); 273 | mus_start(); 274 | } else if (delay != 0) { 275 | mus_set_delay(delay); 276 | } else { 277 | mus_stop(); 278 | } 279 | } 280 | 281 | static op_func_t vm_op_table[] = { 282 | /* 0x00 */ 283 | &op_mov_const, 284 | &op_mov, 285 | &op_add, 286 | &op_add_const, 287 | /* 0x04 */ 288 | &op_call, 289 | &op_ret, 290 | &op_break, 291 | &op_jmp, 292 | /* 0x08 */ 293 | &op_set_script_slot, 294 | &op_jnz, 295 | &op_condjmp, 296 | &op_set_palette, 297 | /* 0x0C */ 298 | &op_reset_script, 299 | &op_select_page, 300 | &op_fill_page, 301 | &op_copy_page, 302 | /* 0x10 */ 303 | &op_update_display, 304 | &op_halt, 305 | &op_draw_string, 306 | &op_sub, 307 | /* 0x14 */ 308 | &op_and, 309 | &op_or, 310 | &op_shl, 311 | &op_shr, 312 | /* 0x18 */ 313 | &op_play_sound, 314 | &op_update_memlist, 315 | &op_play_music 316 | }; 317 | 318 | int vm_init(void) { 319 | memset(vm.vars, 0, sizeof(vm.vars)); 320 | vm.vars[0xE4] = 0x14; // copy protection checks this 321 | // 0x01 == "Another World", 0x81 == "Out of This World" 322 | vm.vars[0x54] = gfx_get_current_mode() == MODE_PAL ? 0x01 : 0x81; 323 | vm.vars[VAR_RANDOM_SEED] = 0x1337; 324 | #ifndef KEEP_COPY_PROTECTION 325 | // if the game was built to start at the intro, set all the copy protection related shit 326 | vm.vars[0xBC] = 0x10; 327 | vm.vars[0xC6] = 0x80; 328 | vm.vars[0xDC] = 0x21; 329 | vm.vars[0xF2] = 4000; // this is for DOS, Amiga wants 6000 330 | #endif 331 | } 332 | 333 | void vm_restart_at(const u16 part_id, const u16 pos) { 334 | mus_stop(); 335 | snd_stop_all(); 336 | res_setup_part(part_id); 337 | memset(vm.script_pos, 0xFF, sizeof(vm.script_pos)); 338 | memset(vm.script_paused, 0, sizeof(vm.script_paused)); 339 | vm.script_pos[0][0] = 0; 340 | if (pos >= 0) vm.vars[0] = pos; 341 | time_now = time_start = 0; // get_timestamp() 342 | } 343 | 344 | void vm_setup_tasks(void) { 345 | if (res_next_part) { 346 | printf("vm_setup_tasks(): transitioning to part %05u\n", res_next_part); 347 | vm_restart_at(res_next_part, 0); 348 | res_next_part = 0; 349 | } 350 | for (int i = 0; i < VM_NUM_TASKS; ++i) { 351 | vm.script_paused[0][i] = vm.script_paused[1][i]; 352 | const u16 pos = vm.script_pos[1][i]; 353 | if (pos != 0xFFFF) { 354 | vm.script_pos[0][i] = (pos == 0xFFFE) ? 0xFFFF : pos; 355 | vm.script_pos[1][i] = 0xFFFF; 356 | } 357 | } 358 | } 359 | 360 | static void vm_run_task(void) { 361 | while (!vm.halt) { 362 | const u8 op = vm_fetch_u8(); 363 | if (op & 0x80) { 364 | res_vidseg_idx = 0; 365 | const u16 ofs = ((op << 8) | vm_fetch_u8()) << 1; 366 | s16 x = vm_fetch_u8(); 367 | s16 y = vm_fetch_u8(); 368 | const s16 h = y - 199; 369 | if (h > 0) { 370 | y = 199; 371 | x += h; 372 | } 373 | gfx_set_databuf(res_seg_video[0], ofs); 374 | gfx_draw_shape(0xFF, 0x40, x, y); 375 | } else if (op & 0x40) { 376 | res_vidseg_idx = 0; 377 | const u16 ofs = vm_fetch_u16() << 1; 378 | s16 x = vm_fetch_u8(); 379 | if ((op & 0x20) == 0) { 380 | if ((op & 0x10) == 0) 381 | x = (x << 8) | vm_fetch_u8(); 382 | else 383 | x = vm.vars[x]; 384 | } else if (op & 0x10) { 385 | x += 0x100; 386 | } 387 | s16 y = vm_fetch_u8(); 388 | if ((op & 8) == 0) { 389 | if ((op & 4) == 0) 390 | y = (y << 8) | vm_fetch_u8(); 391 | else 392 | y = vm.vars[y]; 393 | } 394 | u16 zoom = 0x40; 395 | if ((op & 2) == 0) { 396 | if (op & 1) 397 | zoom = vm.vars[vm_fetch_u8()]; 398 | } else if (op & 1) { 399 | res_vidseg_idx = 1; 400 | } else { 401 | zoom = vm_fetch_u8(); 402 | } 403 | gfx_set_databuf(res_seg_video[res_vidseg_idx], ofs); 404 | gfx_draw_shape(0xFF, zoom, x, y); 405 | } else if (op < VM_NUM_OPCODES) { 406 | vm_op_table[op](); 407 | } else { 408 | printf("vm_run_task(pc=%p): invalid opcode %02x\n", vm.pc, op); 409 | } 410 | } 411 | } 412 | 413 | void vm_run(void) { 414 | for (int i = 0; i < VM_NUM_TASKS; ++i) { 415 | if (vm.script_paused[0][i] == 0) { 416 | const u16 pos = vm.script_pos[0][i]; 417 | if (pos != 0xFFFF) { 418 | vm.pc = res_seg_code + pos; 419 | vm.sp = 0; 420 | vm.halt = 0; 421 | vm_run_task(); 422 | vm.script_pos[0][i] = vm.pc - res_seg_code; 423 | } 424 | } 425 | } 426 | } 427 | 428 | void vm_set_var(const u8 i, const s16 val) { 429 | vm.vars[i] = val; 430 | } 431 | 432 | s16 vm_get_var(const u8 i) { 433 | return vm.vars[i]; 434 | } 435 | 436 | void vm_handle_special_input(u32 mask) { 437 | if (mask & IN_PAUSE) { 438 | if (res_cur_part != PART_COPY_PROTECTION && res_cur_part != PART_INTRO) { 439 | mask &= ~IN_PAUSE; 440 | int paused = 1; 441 | // plot PAUSED right onto the front buffer 442 | gfx_show_pause(); 443 | do { 444 | VSync(0); 445 | mask = pad_get_special_input(); 446 | if ((mask & IN_PASSWORD) && res_have_password && res_cur_part != PART_PASSWORD) { 447 | res_next_part = PART_PASSWORD; 448 | paused = 0; 449 | } else if (mask & IN_PAUSE) { 450 | paused = 0; 451 | } 452 | } while (paused); 453 | } 454 | } 455 | } 456 | 457 | void vm_update_input(u32 mask) { 458 | s16 lr = 0; 459 | s16 m = 0; 460 | s16 ud = 0; 461 | s16 jd = 0; 462 | 463 | if (mask & IN_DIR_RIGHT) 464 | lr = 1, m |= 1; 465 | if (mask & IN_DIR_LEFT) 466 | lr = -1, m |= 2; 467 | if (mask & IN_DIR_DOWN) 468 | ud = jd = 1, m |= 4; 469 | if (mask & (IN_DIR_UP | IN_JUMP)) 470 | ud = jd = -1, m |= 8; 471 | 472 | vm.vars[VAR_HERO_POS_UP_DOWN] = ud; 473 | vm.vars[VAR_HERO_POS_JUMP_DOWN] = jd; 474 | vm.vars[VAR_HERO_POS_LEFT_RIGHT] = lr; 475 | vm.vars[VAR_HERO_POS_MASK] = m; 476 | 477 | s16 action = 0; 478 | if (mask & (IN_ACTION)) 479 | action = 1, m |= 0x80; 480 | 481 | vm.vars[VAR_HERO_ACTION] = action; 482 | vm.vars[VAR_HERO_ACTION_POS_MASK] = m; 483 | } 484 | -------------------------------------------------------------------------------- /src/vm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "types.h" 4 | 5 | enum { 6 | VAR_RANDOM_SEED = 0x3C, 7 | VAR_LAST_KEYCHAR = 0xDA, 8 | VAR_HERO_POS_UP_DOWN = 0xE5, 9 | VAR_MUS_MARK = 0xF4, 10 | VAR_SCROLL_Y = 0xF9, 11 | VAR_HERO_ACTION = 0xFA, 12 | VAR_HERO_POS_JUMP_DOWN = 0xFB, 13 | VAR_HERO_POS_LEFT_RIGHT = 0xFC, 14 | VAR_HERO_POS_MASK = 0xFD, 15 | VAR_HERO_ACTION_POS_MASK = 0xFE, 16 | VAR_PAUSE_SLICES = 0xFF 17 | }; 18 | 19 | int vm_init(void); 20 | void vm_setup_scripts(void); 21 | void vm_run(void); 22 | void vm_set_var(const u8 i, const s16 val); 23 | s16 vm_get_var(const u8 i); 24 | void vm_restart_at(const u16 part_id, const u16 pos); 25 | void vm_setup_tasks(void); 26 | void vm_run(void); 27 | void vm_update_input(u32 mask); 28 | void vm_handle_special_input(u32 mask); 29 | -------------------------------------------------------------------------------- /system.cnf: -------------------------------------------------------------------------------- 1 | BOOT=cdrom:\rawpsx.exe;1 2 | TCB=4 3 | EVENT=10 4 | STACK=801FFFF0 5 | --------------------------------------------------------------------------------