3 * (C) 2018 by Andreas Eversberg <jolly@eversberg.eu>
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.
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.
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/>.
25 #include "../libsdl/print.h"
26 #include "../libcpu/m68k.h"
27 #include "../libcpu/m68kcpu.h"
28 #include "../libcpu/execute.h"
29 #include "mercenary.h"
31 #define INITIAL_STACK 0x7fffa
32 #define RESET_VECTOR 0x59484
34 static int info_walking = 0;
36 /* VR heading to alter direction we walk to */
37 static struct vr_move vr_move;
39 /* interrupt CPU execution at special break points and tell emulation what to do */
40 const struct cpu_stop mercenary_stop_at[] = {
41 { 0x59a4e, STOP_AT_WAIT_VBL }, /* done with rendering, waiting for VBL */
42 { 0x54c26, STOP_AT_WAIT_INPUT }, /* after pressing 'HELP' key before showing menu line on benson */
43 { 0x55438, STOP_AT_WAIT_INPUT }, /* waiting for menu command */
44 { 0x54c2e, STOP_AT_WAIT_INPUT }, /* after pressing 'HELP' key while showing menu line on benson */
45 { 0x55446, STOP_AT_WAIT_INPUT }, /* after pressing 'RETURN' while game waits for other key to resume */
46 { 0x51620, STOP_AT_WAIT_VBL }, /* after dying, waiting for VBL to fade out palette */
47 { 0x596BC, 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) */
48 { 0x596F4, STOP_AT_CLEAR_SCREEN1 }, /* walking above ground */
49 { 0x59720, STOP_AT_CLEAR_SCREEN1 }, /* unknown */
50 { 0x598A8, STOP_AT_CLEAR_SCREEN1 }, /* space craft on ground */
51 { 0x598E6, STOP_AT_CLEAR_SCREEN2 }, /* special case where we use color index 15 when we are flying (no raster split for ground color) */
52 { 0x59982, STOP_AT_CLEAR_SCREEN3 }, /* special case where we are in universe and do not have any ground */
53 { 0x55F2E, STOP_AT_DRAW_GROUND }, /* the ground is rendered. the color index is captured at CLEAR_SCREEN */
54 { 0x596CE, 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 */
55 { 0x59710, 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 */
56 { 0x531EA, STOP_AT_INFO_OBJECT_MOVING }, /* get ID and position of next object */
57 /* note: there a no fix objects in this game (no taxi/bus/intercity) */
58 { 0x534EA, STOP_AT_TAG_IS_OBJECT_1 }, /* indicates that the next tag is an object */
59 { 0x534EE, STOP_AT_TAG_IS_OBJECT_0 }, /* indicates that the tag of object was rendered */
60 { 0x5346E, STOP_AT_COORD_OBJECT }, /* object coordinates are ready */
61 { 0x534F6, STOP_AT_POLY_OBJECT_M2 }, /* object polygon is rendered */
62 { 0x534F0, STOP_AT_LINE_OBJECT }, /* object line is rendered */
63 { 0x5324A, STOP_AT_COORD_BEACON }, /* beacon's point coordinates are ready */
64 { 0x53284, STOP_AT_POINT_BEACON }, /* becon point is rendered */
65 { 0x53A00, STOP_AT_COORD_BUILDING_EXTERIOR }, /* building (house) coordinates are ready */
66 { 0x53A5C, STOP_AT_POLY_BUILDING_EXTERIOR }, /* building polygons are rendered */
67 { 0x53A54, STOP_AT_LINE_BUILDING_EXTERIOR }, /* lines of building, like radio tower on icarus */
68 { 0x5AA10, STOP_AT_COORD_BUILDING_INTERIOR }, /* building coordinates for interrior */
69 { 0x5B218, STOP_AT_POLY_BUILDING_INTERIOR1 }, /* floor of building will be rendered */
70 { 0x5B1BE, STOP_AT_POLY_BUILDING_INTERIOR2 }, /* ceiling of building will be rendered */
71 { 0x5B154, STOP_AT_POLY_BUILDING_INTERIOR3 }, /* ceiling of window/door will be rendered */
72 { 0x5B0E0, STOP_AT_POLY_BUILDING_INTERIOR4 }, /* floor of window will be rendered */
73 { 0x5B2DE, STOP_AT_POLY_BUILDING_INTERIOR1to4 }, /* ceiling/floor of building is rendered */
74 { 0x5AFF4, STOP_AT_POLY_BUILDING_INTERIOR5 }, /* part above window/door will be rendered */
75 { 0x5AF8A, STOP_AT_POLY_BUILDING_INTERIOR6 }, /* part below window will be rendered */
76 { 0x5B0BE, STOP_AT_POLY_BUILDING_INTERIOR5to6 }, /* part below/above window/door of building is rendered */
77 { 0x5B332, STOP_AT_WALL_BUILDING }, /* a wall (between floor and ceiling/window/door) is rendered) */
78 { 0x4F462, STOP_AT_COORD_COMET }, /* comet's coordinates are ready */
79 { 0x4F496, STOP_AT_MATRIX_COMET }, /* what rotation matrix to use */
80 { 0x4F4B8, STOP_AT_POLY_COMET }, /* comet's horizontal polygon (without culling no need to render 0x4F4BC) */
81 { 0x4F4C0, STOP_AT_POLY_COMET }, /* comet's vertival polygon (without culling no need to render 0x4F4C4) */
82 { 0x54634, STOP_AT_COORD_LINE_ROADS }, /* road's line coordinates are ready */
83 { 0x54676, STOP_AT_LINE_ROADS }, /* road's and ground surface's polygon */
84 { 0x5469C, STOP_AT_LINE_ROADS },
85 { 0x546B2, STOP_AT_LINE_ROADS },
86 { 0x56496, STOP_AT_LINE_ROADS_CENTER }, /* center line of roads */
87 { 0x56442, STOP_AT_COORD_POLY_ROADS }, /* road's and ground surface's coordinates are ready */
88 { 0x5649C, STOP_AT_POLY_ROADS }, /* road's and ground surface's polygon */
89 { 0x53CB6, STOP_AT_COORD_TAGS }, /* coordinates for tags, like key's marking are ready */
90 /* note: there are no coordinates for large tags in this game (no faces) */
91 /* note: there are no STOP_AT_LINE_TAGS1 and STOP_AT_POLY_TAGS1 in this game(given color) */
92 { 0x53CFA, STOP_AT_LINE_TAGS2 }, /* tag's line is rendered (use last color) */
93 { 0x53CF0, STOP_AT_POLY_TAGS2 }, /* tag's polygon is rendered (use last color) */
94 { 0x52BF4, STOP_AT_COORD_PLANET }, /* planet's coordinates are ready (viewed from ground) */
95 { 0x52C1A, STOP_AT_MATRIX_PLANET }, /* what rotation matrix to use */
96 { 0x52756, STOP_AT_COORD_PLANET }, /* planet's coordinates are ready (viewed from universe) */
97 { 0x5277C, STOP_AT_MATRIX_PLANET }, /* what rotation matrix to use */
98 { 0x52C70, STOP_AT_DRAW_PLANET }, /* planet's sphere is rendered, D0 is color (viewed from ground) */
99 { 0x52B36, STOP_AT_DRAW_PLANET }, /* planet's sphere is rendered, D0 is color (viewed from universe) */
100 { 0x4F4E6, STOP_AT_DRAW_COMET }, /* comet's sphere is rendered */
101 { 0x50006, STOP_AT_DRAW_STARS_SPACE }, /* stars are rendered (viewed from universe) */
102 { 0x4FF4C + 4, STOP_AT_DRAW_STARS_GROUND }, /* stars are rendered (viewed from ground) */ // + 4 because the actual adress is set by patching, which does not allow to break
103 { 0x4FE24, STOP_AT_DRAW_STARS_FLYING }, /* stars are rendered (viewed from planet when flying) */
104 { 0x4FCAC, STOP_AT_DRAW_STARS_FLYING2 }, /* same as above, but stars when upside down (above zenit) */
105 { 0x50FC2, STOP_AT_DRAW_STARS_INTERSTELLAR }, /* interstellar star flight */
106 { 0x50BF4, STOP_AT_DRAW_SUN_INTERSTELLAR }, /* draw sun dot while flying interstellar */
107 { 0x50BB8, STOP_AT_CLEAR_SCREEN3 }, /* clear while flying interstellar */
108 { 0x563B8, STOP_AT_COORD_ISLANDS }, /* island's coordinates are ready */
109 { 0x56416, STOP_AT_POLY_ISLANDS }, /* island's polygon is rendered */
110 { 0x56410, STOP_AT_LINE_ISLANDS },
111 { 0x511FE, STOP_AT_DRAW_SIGHTS }, /* when sights are rendered */
112 { 0x5351A, STOP_AT_COORD_EXPLOSION }, /* explosion debris coordinate */
113 { 0x5356C, STOP_AT_DRAW_EXPLOSION }, /* explosion debris render */
114 { 0x4C514, STOP_AT_COORD_EXPLOSION },
115 { 0x4C6F0, STOP_AT_DRAW_EXPLOSION },
116 { 0x52C42, STOP_AT_PATCH_RENDER }, /* patch away planet check (behind observer) */
117 { 0x528C4, STOP_AT_PATCH_RENDER }, /* patch away planet check (behind observer) */
118 { 0x45806, STOP_AT_PATCH_RENDER }, /* patch away planet rendering (would crash without check above) */
119 { 0x53276, STOP_AT_PATCH_RENDER }, /* patch away beacon check (not visible on screen) */
120 { 0x4FD8E, STOP_AT_PATCH_RENDER }, /* always render stars, also at day */
121 { 0x4FF4A, STOP_AT_PATCH_RENDER },
122 { 0x59622, STOP_AT_PATCH_VR }, /* we are walking/taxi */
123 { 0x597CA, STOP_AT_PATCH_VR }, /* we are on a craft */
124 { 0x597F2, STOP_AT_PATCH_VR }, /* we are ??? */
125 { 0x59944, STOP_AT_PATCH_VR }, /* we are in space */
126 { 0x58EA8, STOP_AT_PATCH_VR }, /* step that moves player on surface */
127 { 0x58060, STOP_AT_PATCH_VR }, /* soft pitch over planet */
128 { 0x58918, STOP_AT_PATCH_VR }, /* soft pitch in space */
130 { 0x53872, STOP_AT_SHADOW_BUILDING }, /* paint shadow from building ot of game data */
132 { 0x0, STOP_AT_END }, /* end */
135 extern const uint32_t mercenary2_hex[];
136 extern int mercenary2_hex_size;
138 void mercenary_load(void)
142 memset(&vr_move, 0, sizeof(vr_move));
146 /* load game binary from constant to volatile memory */
147 for (i = 0; i < mercenary2_hex_size; i += 4) {
148 m68k_write_memory_32(i, mercenary2_hex[i / 4]);
152 void mercenary_patch(void)
157 m68k_write_memory_32(0x00000, INITIAL_STACK);
160 m68k_write_memory_32(0x00004, RESET_VECTOR);
162 /* remove function that checks what stars are rendered when flying above plante
163 * instead of just rendering the necessary parts, both parts are always rendered:
164 * 1. stars from horizont up to zenith
165 * 2. stars from zenith up to horizon (upside down)
166 * we need that, so opengl rendering can use wider FOV without missing stars.
167 * the game will actually have no problem with it, except that it requires more cpu cycles
169 for (address = 0x4FD90; address < 0x4FDB6; address += 2)
170 m68k_write_memory_16(address, 0x4e71); /* nop */
172 /* remove wait for VBL */
173 m68k_write_memory_16(0x59a54, 0x4e71); /* nop */
175 /* reduce loop that waits for disk stepper to move */
176 if (m68k_read_memory_32(0x55398) != 0x0000091b) {
177 print_error("expecting loop counter of 0x0000091b here, please fix!\n");
180 m68k_write_memory_32(0x55398, 1);
181 /* reduce loop that waits for disk side change */
182 if (m68k_read_memory_32(0x54ffc) != 0x00000d02) {
183 print_error("expecting loop counter of 0x00000d02 here, please fix!\n");
186 m68k_write_memory_32(0x54ffc, 1);
189 /* skip certain parts when rendering improved graphics */
190 void mercenary_patch_render(int shadows)
193 case 0x52C42: /* take that branch, so planets get rendered behind obersver */
196 case 0x528C4: /* take that branch, so planets get rendered behind obersver */
199 case 0x45806: /* just RTS to avoid crashing of rendering functions when planets are behind obersver */
202 case 0x53276: /* skip that branch, so beacons get rendered outside screen */
205 case 0x53284: /* skip point render, because projected coordinates may be invalid outside screen */
208 case 0x4FD8E: /* render stars at day, required for shadow calculation */
217 /* patch execution for VR improvement */
218 void mercenary_patch_vr(void)
221 case 0x59622: /* we are walking/taxi */
224 case 0x597CA: /* we are on a craft */
227 case 0x597F2: /* we are ??? */
230 case 0x59944: /* we are in space */
233 case 0x58EA8: /* at this point we process one step of the player walking on the ground */
234 if (vr_move.override && vr_move.index < 4) {
235 REG_D[2] = vr_move.east[vr_move.index];
236 REG_D[3] = vr_move.north[vr_move.index];
240 case 0x58060: /* soft pitch over surface */
241 REG_D[1] = ((int16_t)REG_D[1] * vr_move.pitch / 256) & 0xfff;
246 uint32_t mercenary_palette_view(void)
248 return m68k_read_memory_32(0x007c14);
251 uint32_t mercenary_palette_render(void)
253 return m68k_read_memory_32(0x007c18);
256 uint32_t mercenary_palette_predefined(void)
258 return m68k_read_memory_32(0x007a0e);
261 uint32_t mercenary_palette_stars(void)
266 void mercenary_get_sky_colors(uint16_t *day, uint16_t *night)
268 *day = m68k_read_memory_16(0x7946);
269 *night = m68k_read_memory_16(0x7948);
272 void mercenary_get_orientation(double *roll, double *pitch, double *yaw)
276 /* get observer's tilt, pitch, yaw */
277 r = (int16_t)(m68k_read_memory_16(0x007A9E) & 0x3ff);
278 *roll = (double)r / 1024.0 * 2 * M_PI;
279 r = (int16_t)((m68k_read_memory_16(0x007AA0) + 0x201) & 0x3ff); /* add one extra to make view leveled to ground */
280 *pitch = -(double)r / 1024.0 * 2 * M_PI;
281 r = (int16_t)((m68k_read_memory_16(0x007AA2) + 0x200) & 0x3ff);
282 *yaw = -(double)r / 1024.0 * 2 * M_PI;
285 void mercenary_set_orientation(double yaw)
289 /* set the oberver */
290 r = (int16_t)fmod(-yaw * 1024.0 / 2.0 / M_PI, 1024.0);
291 m68k_write_memory_16(0x007AC2, (r - 0x200) & 0x3ff);
294 void mercenary_get_orientation_raw(int16_t *pitch, uint16_t *yaw)
296 *pitch = m68k_read_memory_16(0x007AA0);
297 *yaw = m68k_read_memory_16(0x007AA2);
300 void mercenary_get_orientation_planet(double *inclination, double *azimuth, int improved)
305 /* get plant's inclination and azimuth */
306 t = m68k_read_memory_16(0x42C70) & 0x3ff;
307 *inclination = (double)t / 0x400 * 2 * M_PI;
308 t = m68k_read_memory_16(0x42C6c) & 0x3ff;
309 *azimuth = -(double)t / 0x400 * 2 * M_PI;
311 /* inclination depends on north/south position */
312 t = m68k_read_memory_32(0x007ABA) >> 2; /* get position */
315 *inclination = (double)t / 0x04000000 * 2 * M_PI;
316 /* azimuth depends on east/west position and planet's rotation */
317 t = m68k_read_memory_32(0x007AA6); /* get planet index */
318 t = m68k_read_memory_32(25322 + t); /* get rotation of planet */
319 t += m68k_read_memory_32(0x007AB2) >> 2; /* add position */
321 *azimuth = -(double)t / 0x04000000 * 2 * M_PI;
326 void mercenary_get_location(int32_t *east, int32_t *height, int32_t *north)
328 *east = (int32_t)m68k_read_memory_32(0x7a92);
329 *height = (int32_t)m68k_read_memory_32(0x7a30);
330 *north = (int32_t)m68k_read_memory_32(0x7a9a);
333 void mercenary_get_object_info(int *id, int32_t *east, int32_t *height, int32_t *north)
336 *east = (int32_t)m68k_read_memory_32(REG_A[0] + 25512);
337 *height = (int32_t)m68k_read_memory_32(REG_A[0] + 26536);
338 *north = (int32_t)m68k_read_memory_32(REG_A[0] + 27560);
341 void mercenary_get_building_exterior_info(int32_t *loc_x, int32_t *loc_z, int32_t *scale_x, int32_t *scale_y, int32_t *scale_z, uint16_t *anim_flag, uint16_t *anim_x, uint16_t *anim_y, uint16_t *anim_z, uint16_t *anim_phase, uint32_t *a4, uint32_t *a0, uint32_t *a5)
343 /* location of building relative to the observer */
344 *loc_x = (int32_t)m68k_read_memory_32(0x63A8);
345 *loc_z = (int32_t)m68k_read_memory_32(0x6BA8);
346 /* scale of building */
347 *scale_x = (int16_t)m68k_read_memory_16(0x5357E + 0);
348 *scale_y = (int16_t)m68k_read_memory_16(0x5357E + 2);
349 *scale_z = (int16_t)m68k_read_memory_16(0x5357E + 4);
350 /* animation flag (axis to rotate about) */
351 *anim_flag = m68k_read_memory_16(0x53584);
352 /* animation offset */
353 *anim_x = m68k_read_memory_16(0x53582 + 0);
354 *anim_y = m68k_read_memory_16(0x53582 + 2);
355 *anim_z = m68k_read_memory_16(0x53582 + 4);
356 /* animation angle */
357 *anim_phase = m68k_read_memory_16(0x7888);
358 /* vertex list of building's exterior */
359 *a4 = m68k_read_memory_32(0x53638);
360 /* pointer to all datasets */
362 /* primitive pointer array */
366 void mercenary_coord_building_interior(int16_t *east, int32_t *height1, int32_t *height2, int32_t *height3, int32_t *height4, int16_t *north)
368 *east = (int16_t)m68k_read_memory_16(5698+REG_A[0]) - (int16_t)REG_A[2];
369 *north = (int16_t)m68k_read_memory_16(6722+REG_A[0]) - (int16_t)REG_A[3];
370 *height1 = -(int16_t)m68k_read_memory_16(0x79AC);
371 *height2 = (int16_t)m68k_read_memory_16(0x7B26) - (int16_t)m68k_read_memory_16(0x79AC);
372 *height3 = (int16_t)m68k_read_memory_16(0x7B28) - (int16_t)m68k_read_memory_16(0x79AC);
373 *height4 = (int16_t)m68k_read_memory_16(0x7B2A) - (int16_t)m68k_read_memory_16(0x79AC);
376 int mercenary_street_color_index(void)
378 return (m68k_read_memory_16(0x7BE2) >> 5) & 0xf;
381 int mercenary_line_tags_index(void)
383 return (m68k_read_memory_16(0x7BE2) >> 5) & 0xf;
386 uint16_t mercenary_poly_tags_color(void)
388 return m68k_read_memory_16(0x7B06);
391 int mercenary_background_index(void)
393 return m68k_read_memory_16(0x7AEA) >> 2;
396 uint32_t mercenary_planet_scale_index(void)
401 uint32_t mercenary_star_table(void)
406 void mercenary_vr_move(int override, int32_t *east, int32_t *north, int yaw, int pitch)
408 vr_move.override = override;
410 memcpy(vr_move.east, east, sizeof(vr_move.east));
412 memcpy(vr_move.north, north, sizeof(vr_move.north));
414 vr_move.pitch = pitch;
418 int mercenary_get_info_walking(void)
423 const char *mercenary_name = "Mercenary II - Damocles";
424 const char *mercenary_gamesavesuffix = ".m2save";
426 extern const uint8_t mercenary2_mission_disk_1[5][0x1820 << 2];
427 extern const uint8_t mercenary2_mission_disk_2[9][0x1820 << 2];
429 const uint8_t *get_mission_disk(int disk, int mission)
433 if (mission < 1 || mission > 5)
435 return mercenary2_mission_disk_1[mission - 1];
437 if (mission < 1 || mission > 9)
439 return mercenary2_mission_disk_2[mission - 1];