d2ee14fd7aeb2c56f8ee79db541f56f26ea09a86
[mercenary-reloaded.git] / src / mercenary / mercenary3.c
1 /* game specials
2  *
3  * (C) 2018 by Andreas Eversberg <jolly@eversberg.eu>
4  * All Rights Reserved
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <stdio.h>
21 #include <string.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <math.h>
25 #include "../libsdl/print.h"
26 #include "../libcpu/m68k.h"
27 #include "../libcpu/m68kcpu.h"
28 #include "../libcpu/execute.h"
29 #include "mercenary.h"
30
31 #define DS_0 0x530c
32 #define DS_41 0x505E2
33 #define DS_64 0x53d34
34 #define DS_84 0x5D856
35
36 #define INITIAL_STACK   0x7fffa
37 #define RESET_VECTOR    0x5a16c
38 // #define RESET_VECTOR 0x5a1a4 /* strange reset vector that causes the player to fly around */
39
40 static int info_walking = 0;
41
42 /* VR heading to alter direction we walk to */
43 static struct vr_move vr_move;
44
45 /* interrupt CPU execution at special break points and tell emulation what to do */
46 const struct cpu_stop mercenary_stop_at[] = {
47         { 0x5a826,      STOP_AT_WAIT_VBL },                     /* done with rendering, waiting for VBL */
48         { 0x55d8c,      STOP_AT_WAIT_INPUT },                   /* after pressing 'HELP' key before showing menu line on benson */
49         { 0x56398,      STOP_AT_WAIT_INPUT },                   /* waiting for menu command */
50         { 0x55d94,      STOP_AT_WAIT_INPUT },                   /* after pressing 'HELP' key while showing menu line on benson */
51         { 0x563a6,      STOP_AT_WAIT_INPUT },                   /* after pressing 'RETURN' while game waits for other key to resume */
52         { 0x52946,      STOP_AT_WAIT_VBL },                     /* after dying, waiting for VBL to fade out palette */
53         { 0x5A456,      STOP_AT_CLEAR_SCREEN1 },                /* clear the screen inside building, no windows (here we know the color 8, this is wy we cannot do it earlier) */
54         { 0x5A48E,      STOP_AT_CLEAR_SCREEN1 },                /* walking above ground */
55         { 0x5A4BA,      STOP_AT_CLEAR_SCREEN1 },                /* unknown */
56         { 0x5A672,      STOP_AT_CLEAR_SCREEN1 },                /* space craft on ground */
57         { 0x5A6B0,      STOP_AT_CLEAR_SCREEN2 },                /* special case where we use color index 15 when we are flying (no raster split for ground color) */
58         { 0x5A770,      STOP_AT_CLEAR_SCREEN3 },                /* special case where we are in universe and do not have any ground */
59         { 0x56E00,      STOP_AT_DRAW_GROUND },                  /* the ground is rendered. the color index is captured at CLEAR_SCREEN */
60         { 0x5A468,      STOP_AT_DRAW_GROUND },                  /* at this point there is ground (inside building) rendered, because game uses raster split for ground color, but we do render opengl */
61         { 0x5A4AA,      STOP_AT_DRAW_GROUND },                  /* at this point there is ground (outside) rendered, because game uses raster split for ground color, but we do render opengl */
62         { 0x53FE2,      STOP_AT_INFO_OBJECT_MOVING },           /* get ID and position of next object */
63         { 0x53E42,      STOP_AT_INFO_OBJECT_FIX },              /* next object is FIX (taxi/bus/intercity) */
64         { 0x53E8E,      STOP_AT_INFO_OBJECT_FIX },
65         { 0x54330,      STOP_AT_TAG_IS_OBJECT_1 },              /* indicates that the next tag is an object */
66         { 0x54334,      STOP_AT_TAG_IS_OBJECT_0 },              /* indicates that the tag of object was rendered */
67         { 0x542B2,      STOP_AT_COORD_OBJECT },                 /* object coordinates are ready */
68         { 0x5431a,      STOP_AT_POLY_OBJECT_M3 },               /* object polygon is rendered */
69         { 0x54342,      STOP_AT_LINE_OBJECT },                  /* object line is rendered */
70         { 0x54042,      STOP_AT_COORD_BEACON },                 /* beacon's point coordinates are ready */
71         { 0x54088,      STOP_AT_POINT_BEACON },                 /* becon point is rendered */
72         { 0x54848,      STOP_AT_COORD_BUILDING_EXTERIOR },      /* building (house) coordinates are ready */
73         { 0x548a4,      STOP_AT_POLY_BUILDING_EXTERIOR },       /* building polygons are rendered */
74         { 0x5489C,      STOP_AT_LINE_BUILDING_EXTERIOR },       /* lines of building, like radio tower on icarus */
75         { 0x5B810,      STOP_AT_COORD_BUILDING_INTERIOR },      /* building coordinates for interrior */
76         { 0x5C01E,      STOP_AT_POLY_BUILDING_INTERIOR1 },      /* floor of building will be rendered */
77         { 0x5BFc4,      STOP_AT_POLY_BUILDING_INTERIOR2 },      /* ceiling of building will be rendered */
78         { 0x5BF5a,      STOP_AT_POLY_BUILDING_INTERIOR3 },      /* ceiling of window/door will be rendered */
79         { 0x5BEE6,      STOP_AT_POLY_BUILDING_INTERIOR4 },      /* floor of window will be rendered */
80         { 0x5C0E4,      STOP_AT_POLY_BUILDING_INTERIOR1to4 },   /* ceiling/floor of building is rendered */
81         { 0x5BDFA,      STOP_AT_POLY_BUILDING_INTERIOR5 },      /* part above window/door will be rendered */
82         { 0x5BD90,      STOP_AT_POLY_BUILDING_INTERIOR6 },      /* part below window will be rendered */
83         { 0x5BEC4,      STOP_AT_POLY_BUILDING_INTERIOR5to6 },   /* part below/above window/door of building is rendered */
84         { 0x5C138,      STOP_AT_WALL_BUILDING },                /* a wall (between floor and ceiling/window/door) is rendered) */
85         { 0x4FB08,      STOP_AT_COORD_COMET },                  /* comet's coordinates are ready */
86         { 0x4FB3C,      STOP_AT_MATRIX_COMET },                 /* what rotation matrix to use */
87         { 0x4FB5E,      STOP_AT_POLY_COMET },                   /* comet's horizontal polygon (without culling no need to render 0x4FB62) */
88         { 0x4FB66,      STOP_AT_POLY_COMET },                   /* comet's vertival polygon (without culling no need to render 0x4FB6A) */
89         { 0x5573E,      STOP_AT_COORD_LINE_ROADS },             /* road's line coordinates are ready */
90         { 0x55780,      STOP_AT_LINE_ROADS },                   /* road's and ground surface's polygon */
91         { 0x557A6,      STOP_AT_LINE_ROADS },
92         { 0x557BC,      STOP_AT_LINE_ROADS },
93         { 0x57370,      STOP_AT_LINE_ROADS_CENTER },            /* center line of roads */
94         { 0x5731C,      STOP_AT_COORD_POLY_ROADS },             /* road's and ground surface's coordinates are ready */
95         { 0x57376,      STOP_AT_POLY_ROADS },                   /* road's and ground surface's polygon */
96         { 0x54BF4,      STOP_AT_COORD_TAGS },                   /* coordinates for tags, like key's marking are ready */
97         { 0x54BAC,      STOP_AT_COORD_TAGS2 },                  /* coordinates for large tags are ready, like bill's face */
98         { 0x54C40,      STOP_AT_LINE_TAGS1 },                   /* tag's line is rendered (new color D0) */
99         { 0x54C3A,      STOP_AT_LINE_TAGS2 },                   /* tag's line is rendered (use last color) */
100         { 0x54C54,      STOP_AT_POLY_TAGS1 },                   /* tag's polygon is rendered (new color D0) */
101         { 0x54C30,      STOP_AT_POLY_TAGS2 },                   /* tag's polygon is rendered (use last color) */
102         { 0x53A1E,      STOP_AT_COORD_PLANET },                 /* planet's coordinates are ready (viewed from ground) */
103         { 0x53A44,      STOP_AT_MATRIX_PLANET },                /* what rotation matrix to use */
104         { 0x53712,      STOP_AT_COORD_PLANET },                 /* planet's coordinates are ready (viewed from universe) */
105         { 0x537A4,      STOP_AT_MATRIX_PLANET },                /* what rotation matrix to use */
106         { 0x53A9A,      STOP_AT_DRAW_PLANET },                  /* planet's sphere is rendered, D0 is color (viewed from ground) */
107         { 0x53960,      STOP_AT_DRAW_PLANET },                  /* planet's sphere is rendered, D0 is color (viewed from universe) */
108         { 0x4FB8C,      STOP_AT_DRAW_COMET },                   /* comet's sphere is rendered */
109         { 0x50524,      STOP_AT_DRAW_STARS_SPACE },             /* stars are rendered (viewed from universe) */
110         { 0x5046A,      STOP_AT_DRAW_STARS_GROUND },            /* stars are rendered (viewed from ground) */
111         { 0x50342,      STOP_AT_DRAW_STARS_FLYING },            /* stars are rendered (viewed from planet when flying) */
112         { 0x501CA,      STOP_AT_DRAW_STARS_FLYING2 },           /* same as above, but stars when upside down (above zenit) */
113         { 0x57290,      STOP_AT_COORD_ISLANDS },                /* island's coordinates are ready */
114         { 0x572EE,      STOP_AT_POLY_ISLANDS },                 /* island's polygon is rendered */
115         { 0x572E8,      STOP_AT_LINE_ISLANDS },
116         { 0x5214E,      STOP_AT_DRAW_SIGHTS },                  /* when sights are rendered */
117         { 0x4F748,      STOP_AT_POLY_UKN2 },
118         { 0x54362,      STOP_AT_COORD_EXPLOSION },              /* explosion debris coordinate */
119         { 0x543B4,      STOP_AT_DRAW_EXPLOSION },               /* explosion debris render */
120         { 0x4CA18,      STOP_AT_COORD_EXPLOSION },
121         { 0x4CBF4,      STOP_AT_DRAW_EXPLOSION },
122         { 0x53A6C,      STOP_AT_PATCH_RENDER },                 /* patch away planet check (behind observer) */
123         { 0x537B0,      STOP_AT_PATCH_RENDER },                 /* patch away planet check (behind observer) */
124         { 0x45806,      STOP_AT_PATCH_RENDER },                 /* patch away planet rendering (would crash without check above) */
125         { 0x5406E,      STOP_AT_PATCH_RENDER },                 /* patch away beacon check (not visible on screen) */
126         { 0x5A3BA,      STOP_AT_PATCH_VR },                     /* we are walking/taxi */
127         { 0x5A576,      STOP_AT_PATCH_VR },                     /* we are on a craft */
128         { 0x5A5A4,      STOP_AT_PATCH_VR },                     /* we are ??? */
129         { 0x5A71A,      STOP_AT_PATCH_VR },                     /* we are in space */
130         { 0x59A00,      STOP_AT_PATCH_VR },                     /* step that moves player on surface */
131         { 0x58F44,      STOP_AT_PATCH_VR },                     /* soft pitch over planet */
132         { 0x5946E,      STOP_AT_PATCH_VR },                     /* soft pitch in space */
133         { 0x0,          STOP_AT_END },                          /* end */
134 };
135
136 extern const uint32_t mercenary3_hex[];
137 extern int mercenary3_hex_size;
138
139 void mercenary_load(void)
140 {
141         int i;
142
143         memset(&vr_move, 0, sizeof(vr_move));
144         vr_move.yaw = 256;
145         vr_move.pitch = 256;
146
147         /* load game binary from constant to volatile memory */
148         for (i = 0; i < mercenary3_hex_size; i += 4) {
149                 m68k_write_memory_32(i, mercenary3_hex[i / 4]);
150         }
151 }
152
153 void mercenary_patch(void)
154 {
155         uint32_t address;
156
157         /* initial stack */
158         m68k_write_memory_32(0x00000, INITIAL_STACK);
159
160         /* reset vector */
161         m68k_write_memory_32(0x00004, RESET_VECTOR);
162
163         /* patch away the function call that is outside chip memory
164          * is this a copy protection????
165          */
166         m68k_write_memory_16(0x5a17c, 0x4e71); /* nop */
167         m68k_write_memory_16(0x5a17e, 0x4e71); /* nop */
168         m68k_write_memory_16(0x5a180, 0x4e71); /* nop */
169
170         /* remove function that checks what stars are rendered when flying above plante
171          * instead of just rendering the necessary parts, both parts are always rendered:
172          * 1. stars from horizont up to zenith
173          * 2. stars from zenith up to horizon (upside down)
174          * we need that, so opengl rendering can use wider FOV without missing stars.
175          * the game will actually have no problem with it, except that it requires more cpu cycles
176          */
177         for (address = 0x502AE; address < 0x502D4; address += 2)
178                 m68k_write_memory_16(address, 0x4e71); /* nop */
179
180 //m68k_write_memory_16(0x53A42, 0x2C20);
181
182 //m68k_write_memory_16(0x58388, 0x4E75); /* rts */
183 //m68k_write_memory_16(0x4FE3E, 0x4E75); /* rts */
184
185 //m68k_write_memory_16(0x53A6C, 0x6008); /* bra */
186 //m68k_write_memory_16(0x537B0, 0x6008); /* bra */
187 //m68k_write_memory_16(0x45806, 0x4E75); /* rts */
188
189 //m68k_write_memory_16(0x54342, 0x4e71); /* nop */
190 //m68k_write_memory_16(0x54344, 0x4e71); /* nop */
191 //m68k_write_memory_16(0x5059e, 0x3f0); /* nop */
192 #if 0
193 m68k_write_memory_16(0x4FB5E, 0x4e71); /* nop */
194 m68k_write_memory_16(0x4FB60, 0x4e71); /* nop */
195 m68k_write_memory_16(0x4FB62, 0x4e71); /* nop */
196 m68k_write_memory_16(0x4FB64, 0x4e71); /* nop */
197 m68k_write_memory_16(0x4FB66, 0x4e71); /* nop */
198 m68k_write_memory_16(0x4FB68, 0x4e71); /* nop */
199 m68k_write_memory_16(0x4FB6a, 0x4e71); /* nop */
200 m68k_write_memory_16(0x4FB6c, 0x4e71); /* nop */
201 #endif
202
203         /* remove wait for VBL */
204         m68k_write_memory_16(0x5a82C, 0x4e71); /* nop */
205
206         /* reduce loop that waits for disk stepper to move */
207         if (m68k_read_memory_32(0x562f8) != 0x000091b0) {
208                 print_error("expecting loop counter of 0x000091b0 here, please fix!\n");
209                 exit(0);
210         }
211         m68k_write_memory_32(0x562f8, 1);
212         /* reduce loop that waits for disk side change */
213         if (m68k_read_memory_32(0x55f5c) != 0x0000d020) {
214                 print_error("expecting loop counter of 0x0000d020 here, please fix!\n");
215                 exit(0);
216         }
217         m68k_write_memory_32(0x55f5c, 1);
218 }
219
220 /* skip certain parts when rendering improved graphics */
221 void mercenary_patch_render(void)
222 {
223         switch (REG_PC) {
224         case 0x53A6C: /* take that branch, so planets get rendered behind obersver */
225                 REG_PC += 10;
226                 break;
227         case 0x537B0: /* take that branch, so planets get rendered behind obersver */
228                 REG_PC += 10;
229                 break;
230         case 0x45806: /* just RTS to avoid crashing of rendering functions when planets are behind obersver */
231                 REG_PC -= 2;
232                 break;
233         case 0x5406E: /* skip that branch, so beacons get rendered outside screen */
234                 REG_PC += 2;
235                 break;
236         case 0x54088: /* skip point render, because projected coordinates may be invalid outside screen */
237                 REG_PC += 4;
238                 break;
239         }
240 }
241
242 /* patch execution for VR improvement */
243 void mercenary_patch_vr(void)
244 {
245         switch (REG_PC) {
246         case 0x5A3BA: /* we are walking/taxi */
247                 info_walking = 1;
248                 break;
249         case 0x5A576: /* we are on a craft */
250                 info_walking = 0;
251                 break;
252         case 0x5A5A4: /* we are ??? */
253                 info_walking = 0;
254                 break;
255         case 0x5A71A: /* we are in space */
256                 info_walking = 0;
257                 break;
258         case 0x59A00: /* at this point we process one step of the player walking on the ground */
259                 if (vr_move.override && vr_move.index < 4) {
260                         REG_D[2] = vr_move.east[vr_move.index];
261                         REG_D[3] = vr_move.north[vr_move.index];
262                         vr_move.index++;
263                 }
264                 break;
265         case 0x58F44: /* soft pitch over surface */
266                 REG_D[1] = ((int16_t)REG_D[1] * vr_move.pitch / 256) & 0xfff;
267                 break;
268 #if 0
269         case 0x5946E: /* pitch in space doesn't work! */
270                 if (vr_move.override) {
271                         REG_D[1] = ((int32_t)REG_D[1] * vr_move.pitch / 256) & 0xffffff00;
272                         printf("unten factor %d reg_d1 = %d\n", vr_move.pitch, (int32_t)REG_D[1]);
273                 }
274                 break;
275 #endif
276         }
277 }
278
279 uint32_t mercenary_palette_view(void)
280 {
281         return m68k_read_memory_32(0x0072b0);
282 }
283
284 uint32_t mercenary_palette_render(void)
285 {
286         return m68k_read_memory_32(0x0072b4);
287 }
288
289 uint32_t mercenary_palette_predefined(void)
290 {
291         return m68k_read_memory_32(0x0070a4);
292 }
293
294 uint32_t mercenary_palette_stars(void)
295 {
296         return DS_41+66;
297 }
298
299 void mercenary_get_orientation(double *roll, double *pitch, double *yaw)
300 {
301         int16_t r;
302
303         /* we could use 0x1e4*, but then "floating on the water" is not included */
304
305         /* get observer's tilt, pitch, yaw */
306         r = (int16_t)(m68k_read_memory_16(DS_0+0x1E26) & 0x3ff);
307         *roll = (double)r / 1024.0 * 2 * M_PI;
308         r = (int16_t)((m68k_read_memory_16(DS_0+0x1E28) + 0x201) & 0x3ff); /* add one extra to make view leveled to ground */
309         *pitch = -(double)r / 1024.0 * 2 * M_PI;
310         r = (int16_t)((m68k_read_memory_16(DS_0+0x1E2a) + 0x200) & 0x3ff);
311         *yaw = -(double)r / 1024.0 * 2 * M_PI;
312 }
313
314 void mercenary_set_orientation(double yaw)
315 {
316         int16_t r;
317
318         /* set the oberver */
319         r = (int16_t)fmod(-yaw * 1024.0 / 2.0 / M_PI, 1024.0);
320         m68k_write_memory_16(DS_0+0x1E4a, (r - 0x200) & 0x3ff);
321 }
322
323 void mercenary_get_orientation_raw(int16_t *pitch, uint16_t *yaw)
324 {
325         *pitch = m68k_read_memory_16(DS_0+0x1E28);
326         *yaw = m68k_read_memory_16(DS_0+0x1E2a);
327 }
328
329 void mercenary_get_orientation_planet(double *inclination, double *azimuth, int improved)
330 {
331         uint32_t t;
332
333         if (!improved) {
334                 /* get plant's inclination and azimuth */
335                 t = m68k_read_memory_16(0x42C70) & 0x3ff;
336                 *inclination = (double)t / 0x400 * 2 * M_PI;
337                 t = m68k_read_memory_16(0x42C6c) & 0x3ff;
338                 *azimuth = -(double)t / 0x400 * 2 * M_PI;
339         } else {
340                 /* inclination depends on north/south position */
341                 t = m68k_read_memory_32(DS_0+0x1E42) >> 2; /* get position */
342                 t += 0x01000000;
343                 t &= 0x03ffffff;
344                 *inclination = (double)t / 0x04000000 * 2 * M_PI;
345                 /* azimuth depends on east/west position and planet's rotation */
346                 t = m68k_read_memory_32(DS_0+0x1E2E); /* get planet index */
347                 t = m68k_read_memory_32(22362 + t); /* get rotation of planet */
348                 t += m68k_read_memory_32(DS_0+0x1E3A) >> 2; /* add position */
349                 t &= 0x03ffffff;
350                 *azimuth = -(double)t / 0x04000000 * 2 * M_PI;
351
352         }
353 }
354
355 void mercenary_get_location(int32_t *east, int32_t *height, int32_t *north)
356 {
357         *east = (int32_t)m68k_read_memory_32(DS_0+0x1E1A);
358         *height = (int32_t)m68k_read_memory_32(DS_0+0x1DBA);
359         *north = (int32_t)m68k_read_memory_32(DS_0+0x1E22);
360 }
361
362 void mercenary_get_object_info(int *id, int32_t *east, int32_t *height, int32_t *north)
363 {
364         *id = REG_A[0];
365         *east = (int32_t)m68k_read_memory_32(REG_A[0] + 22556);
366         *height = (int32_t)m68k_read_memory_32(REG_A[0] + 23580);
367         *north = (int32_t)m68k_read_memory_32(REG_A[0] + 24604);
368 }
369
370 void mercenary_coord_building_interior(int16_t *east, int32_t *height1, int32_t *height2, int32_t *height3, int32_t *height4, int16_t *north)
371 {
372         *east = (int16_t)m68k_read_memory_16(5698+REG_A[0]) - (int16_t)REG_A[2];
373         *north = (int16_t)m68k_read_memory_16(6298+REG_A[0]) - (int16_t)REG_A[3];
374         *height1 = -(int16_t)m68k_read_memory_16(DS_0+0x1D30);
375         *height2 = (int16_t)m68k_read_memory_16(DS_0+0x1EB2) - (int16_t)m68k_read_memory_16(DS_0+0x1D30);
376         *height3 = (int16_t)m68k_read_memory_16(DS_0+0x1EB4) - (int16_t)m68k_read_memory_16(DS_0+0x1D30);
377         *height4 = (int16_t)m68k_read_memory_16(DS_0+0x1EB6) - (int16_t)m68k_read_memory_16(DS_0+0x1D30);
378
379 }
380
381 int mercenary_street_color_index(void)
382 {
383         return (m68k_read_memory_16(DS_0+0x1f70) >> 5) & 0xf;
384 }
385
386 int mercenary_line_tags_index(void)
387 {
388         return (m68k_read_memory_16(DS_0+0x1f70) >> 5) & 0xf;
389 }
390
391 uint16_t mercenary_poly_tags_color(void)
392 {
393         return m68k_read_memory_16(DS_0+0x1e92);
394 }
395
396 int mercenary_background_index(void)
397 {
398         return m68k_read_memory_16(DS_0+0x1E76) >> 2;
399 }
400
401 uint32_t mercenary_planet_scale_index(void)
402 {
403         return 21584;
404 }
405
406 uint32_t mercenary_star_table(void)
407 {
408         return DS_84+0x6A;
409 }
410
411 void mercenary_vr_move(int override, int32_t *east, int32_t *north, int yaw, int pitch)
412 {
413         vr_move.override = override;
414         if (east)
415                 memcpy(vr_move.east, east, sizeof(vr_move.east));
416         if (north)
417                 memcpy(vr_move.north, north, sizeof(vr_move.north));
418         vr_move.yaw = yaw;
419         vr_move.pitch = pitch;
420         vr_move.index = 0;
421 }
422
423 int mercenary_get_info_walking(void)
424 {
425         return info_walking;
426 }
427
428 const char *mercenary_name = "Mercenary III - The Dion Crisis";
429 const char *mercenary_gamesavesuffix = ".m3save";
430
431 const uint8_t *get_mission_disk(int __attribute__((unused)) disk, int __attribute__((unused)) mission)
432 {
433         return NULL;
434 }
435