├── .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 |
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 |
--------------------------------------------------------------------------------