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"
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 */
40 static int info_walking = 0;
42 /* VR heading to alter direction we walk to */
43 static struct vr_move vr_move;
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 + 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
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 { 0x502AC, STOP_AT_PATCH_RENDER }, /* always render stars, also at day */
127 { 0x50468, STOP_AT_PATCH_RENDER },
128 { 0x5A3BA, STOP_AT_PATCH_VR }, /* we are walking/taxi */
129 { 0x5A576, STOP_AT_PATCH_VR }, /* we are on a craft */
130 { 0x5A5A4, STOP_AT_PATCH_VR }, /* we are ??? */
131 { 0x5A71A, STOP_AT_PATCH_VR }, /* we are in space */
132 { 0x59A00, STOP_AT_PATCH_VR }, /* step that moves player on surface */
133 { 0x58F44, STOP_AT_PATCH_VR }, /* soft pitch over planet */
134 { 0x5946E, STOP_AT_PATCH_VR }, /* soft pitch in space */
136 { 0x546BA, STOP_AT_SHADOW_BUILDING }, /* paint shadow from building ot of game data */
138 { 0x0, STOP_AT_END }, /* end */
141 extern const uint32_t mercenary3_hex[];
142 extern int mercenary3_hex_size;
144 void mercenary_load(void)
148 memset(&vr_move, 0, sizeof(vr_move));
152 /* load game binary from constant to volatile memory */
153 for (i = 0; i < mercenary3_hex_size; i += 4) {
154 m68k_write_memory_32(i, mercenary3_hex[i / 4]);
158 void mercenary_patch(void)
163 m68k_write_memory_32(0x00000, INITIAL_STACK);
166 m68k_write_memory_32(0x00004, RESET_VECTOR);
168 /* patch away the function call that is outside chip memory
169 * is this a copy protection????
171 m68k_write_memory_16(0x5a17c, 0x4e71); /* nop */
172 m68k_write_memory_16(0x5a17e, 0x4e71); /* nop */
173 m68k_write_memory_16(0x5a180, 0x4e71); /* nop */
175 /* remove function that checks what stars are rendered when flying above plante
176 * instead of just rendering the necessary parts, both parts are always rendered:
177 * 1. stars from horizont up to zenith
178 * 2. stars from zenith up to horizon (upside down)
179 * we need that, so opengl rendering can use wider FOV without missing stars.
180 * the game will actually have no problem with it, except that it requires more cpu cycles
182 for (address = 0x502AE; address < 0x502D4; address += 2)
183 m68k_write_memory_16(address, 0x4e71); /* nop */
185 //m68k_write_memory_16(0x53A42, 0x2C20);
187 //m68k_write_memory_16(0x58388, 0x4E75); /* rts */
188 //m68k_write_memory_16(0x4FE3E, 0x4E75); /* rts */
190 //m68k_write_memory_16(0x53A6C, 0x6008); /* bra */
191 //m68k_write_memory_16(0x537B0, 0x6008); /* bra */
192 //m68k_write_memory_16(0x45806, 0x4E75); /* rts */
194 //m68k_write_memory_16(0x54342, 0x4e71); /* nop */
195 //m68k_write_memory_16(0x54344, 0x4e71); /* nop */
196 //m68k_write_memory_16(0x5059e, 0x3f0); /* nop */
198 m68k_write_memory_16(0x4FB5E, 0x4e71); /* nop */
199 m68k_write_memory_16(0x4FB60, 0x4e71); /* nop */
200 m68k_write_memory_16(0x4FB62, 0x4e71); /* nop */
201 m68k_write_memory_16(0x4FB64, 0x4e71); /* nop */
202 m68k_write_memory_16(0x4FB66, 0x4e71); /* nop */
203 m68k_write_memory_16(0x4FB68, 0x4e71); /* nop */
204 m68k_write_memory_16(0x4FB6a, 0x4e71); /* nop */
205 m68k_write_memory_16(0x4FB6c, 0x4e71); /* nop */
208 /* remove wait for VBL */
209 m68k_write_memory_16(0x5a82C, 0x4e71); /* nop */
211 /* reduce loop that waits for disk stepper to move */
212 if (m68k_read_memory_32(0x562f8) != 0x000091b0) {
213 print_error("expecting loop counter of 0x000091b0 here, please fix!\n");
216 m68k_write_memory_32(0x562f8, 1);
217 /* reduce loop that waits for disk side change */
218 if (m68k_read_memory_32(0x55f5c) != 0x0000d020) {
219 print_error("expecting loop counter of 0x0000d020 here, please fix!\n");
222 m68k_write_memory_32(0x55f5c, 1);
225 /* skip certain parts when rendering improved graphics */
226 void mercenary_patch_render(int shadows)
229 case 0x53A6C: /* take that branch, so planets get rendered behind obersver */
232 case 0x537B0: /* take that branch, so planets get rendered behind obersver */
235 case 0x45806: /* just RTS to avoid crashing of rendering functions when planets are behind obersver */
238 case 0x5406E: /* skip that branch, so beacons get rendered outside screen */
241 case 0x54088: /* skip point render, because projected coordinates may be invalid outside screen */
244 case 0x502AC: /* render stars at day, required for shadow calculation */
253 /* patch execution for VR improvement */
254 void mercenary_patch_vr(void)
257 case 0x5A3BA: /* we are walking/taxi */
260 case 0x5A576: /* we are on a craft */
263 case 0x5A5A4: /* we are ??? */
266 case 0x5A71A: /* we are in space */
269 case 0x59A00: /* at this point we process one step of the player walking on the ground */
270 if (vr_move.override && vr_move.index < 4) {
271 REG_D[2] = vr_move.east[vr_move.index];
272 REG_D[3] = vr_move.north[vr_move.index];
276 case 0x58F44: /* soft pitch over surface */
277 REG_D[1] = ((int16_t)REG_D[1] * vr_move.pitch / 256) & 0xfff;
280 case 0x5946E: /* pitch in space doesn't work! */
281 if (vr_move.override) {
282 REG_D[1] = ((int32_t)REG_D[1] * vr_move.pitch / 256) & 0xffffff00;
283 printf("unten factor %d reg_d1 = %d\n", vr_move.pitch, (int32_t)REG_D[1]);
290 uint32_t mercenary_palette_view(void)
292 return m68k_read_memory_32(0x0072b0);
295 uint32_t mercenary_palette_render(void)
297 return m68k_read_memory_32(0x0072b4);
300 uint32_t mercenary_palette_predefined(void)
302 return m68k_read_memory_32(0x0070a4);
305 uint32_t mercenary_palette_stars(void)
310 void mercenary_get_sky_colors(uint16_t *day, uint16_t *night)
312 *day = m68k_read_memory_16(0x6FD6);
313 *night = m68k_read_memory_16(0x6FD8);
316 void mercenary_get_orientation(double *roll, double *pitch, double *yaw)
320 /* we could use 0x1e4*, but then "floating on the water" is not included */
322 /* get observer's tilt, pitch, yaw */
323 r = (int16_t)(m68k_read_memory_16(DS_0+0x1E26) & 0x3ff);
324 *roll = (double)r / 1024.0 * 2 * M_PI;
325 r = (int16_t)((m68k_read_memory_16(DS_0+0x1E28) + 0x201) & 0x3ff); /* add one extra to make view leveled to ground */
326 *pitch = -(double)r / 1024.0 * 2 * M_PI;
327 r = (int16_t)((m68k_read_memory_16(DS_0+0x1E2a) + 0x200) & 0x3ff);
328 *yaw = -(double)r / 1024.0 * 2 * M_PI;
331 void mercenary_set_orientation(double yaw)
335 /* set the oberver */
336 r = (int16_t)fmod(-yaw * 1024.0 / 2.0 / M_PI, 1024.0);
337 m68k_write_memory_16(DS_0+0x1E4a, (r - 0x200) & 0x3ff);
340 void mercenary_get_orientation_raw(int16_t *pitch, uint16_t *yaw)
342 *pitch = m68k_read_memory_16(DS_0+0x1E28);
343 *yaw = m68k_read_memory_16(DS_0+0x1E2a);
346 void mercenary_get_orientation_planet(double *inclination, double *azimuth, int improved)
351 /* get plant's inclination and azimuth */
352 t = m68k_read_memory_16(0x42C70) & 0x3ff;
353 *inclination = (double)t / 0x400 * 2 * M_PI;
354 t = m68k_read_memory_16(0x42C6c) & 0x3ff;
355 *azimuth = -(double)t / 0x400 * 2 * M_PI;
357 /* inclination depends on north/south position */
358 t = m68k_read_memory_32(DS_0+0x1E42) >> 2; /* get position */
361 *inclination = (double)t / 0x04000000 * 2 * M_PI;
362 /* azimuth depends on east/west position and planet's rotation */
363 t = m68k_read_memory_32(DS_0+0x1E2E); /* get planet index */
364 t = m68k_read_memory_32(22362 + t); /* get rotation of planet */
365 t += m68k_read_memory_32(DS_0+0x1E3A) >> 2; /* add position */
367 *azimuth = -(double)t / 0x04000000 * 2 * M_PI;
372 void mercenary_get_location(int32_t *east, int32_t *height, int32_t *north)
374 *east = (int32_t)m68k_read_memory_32(DS_0+0x1E1A);
375 *height = (int32_t)m68k_read_memory_32(DS_0+0x1DBA);
376 *north = (int32_t)m68k_read_memory_32(DS_0+0x1E22);
379 void mercenary_get_object_info(int *id, int32_t *east, int32_t *height, int32_t *north)
382 *east = (int32_t)m68k_read_memory_32(REG_A[0] + 22556);
383 *height = (int32_t)m68k_read_memory_32(REG_A[0] + 23580);
384 *north = (int32_t)m68k_read_memory_32(REG_A[0] + 24604);
387 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)
389 /* location of building relative to the observer */
390 *loc_x = (int32_t)m68k_read_memory_32(0x0530C + 0x510);
391 *loc_z = (int32_t)m68k_read_memory_32(0x0530C + 0xD10);
392 /* scale of building */
393 *scale_x = (int16_t)m68k_read_memory_16(0x543C2 + 4);
394 *scale_y = (int16_t)m68k_read_memory_16(0x543C2 + 6);
395 *scale_z = (int16_t)m68k_read_memory_16(0x543C2 + 8);
396 /* animation flag (axis to rotate about) */
397 *anim_flag = m68k_read_memory_16(0x543CC);
398 /* animation offset */
399 *anim_x = m68k_read_memory_16(0x543C2 + 12);
400 *anim_y = m68k_read_memory_16(0x543C2 + 14);
401 *anim_z = m68k_read_memory_16(0x543C2 + 16);
402 /* animation angle */
403 *anim_phase = m68k_read_memory_16(0x6f38);
404 /* vertex list of building's exterior */
405 *a4 = m68k_read_memory_32(0x5447C + 4);
406 /* pointer to all datasets */
408 /* primitive pointer array */
412 void mercenary_coord_building_interior(int16_t *east, int32_t *height1, int32_t *height2, int32_t *height3, int32_t *height4, int16_t *north)
414 *east = (int16_t)m68k_read_memory_16(5698+REG_A[0]) - (int16_t)REG_A[2];
415 *north = (int16_t)m68k_read_memory_16(6298+REG_A[0]) - (int16_t)REG_A[3];
416 *height1 = -(int16_t)m68k_read_memory_16(DS_0+0x1D30);
417 *height2 = (int16_t)m68k_read_memory_16(DS_0+0x1EB2) - (int16_t)m68k_read_memory_16(DS_0+0x1D30);
418 *height3 = (int16_t)m68k_read_memory_16(DS_0+0x1EB4) - (int16_t)m68k_read_memory_16(DS_0+0x1D30);
419 *height4 = (int16_t)m68k_read_memory_16(DS_0+0x1EB6) - (int16_t)m68k_read_memory_16(DS_0+0x1D30);
422 int mercenary_street_color_index(void)
424 return (m68k_read_memory_16(DS_0+0x1f70) >> 5) & 0xf;
427 int mercenary_line_tags_index(void)
429 return (m68k_read_memory_16(DS_0+0x1f70) >> 5) & 0xf;
432 uint16_t mercenary_poly_tags_color(void)
434 return m68k_read_memory_16(DS_0+0x1e92);
437 int mercenary_background_index(void)
439 return m68k_read_memory_16(DS_0+0x1E76) >> 2;
442 uint32_t mercenary_planet_scale_index(void)
447 uint32_t mercenary_star_table(void)
452 void mercenary_vr_move(int override, int32_t *east, int32_t *north, int yaw, int pitch)
454 vr_move.override = override;
456 memcpy(vr_move.east, east, sizeof(vr_move.east));
458 memcpy(vr_move.north, north, sizeof(vr_move.north));
460 vr_move.pitch = pitch;
464 int mercenary_get_info_walking(void)
469 const char *mercenary_name = "Mercenary III - The Dion Crisis";
470 const char *mercenary_gamesavesuffix = ".m3save";
472 const uint8_t *get_mission_disk(int __attribute__((unused)) disk, int __attribute__((unused)) mission)