142 lines
5.3 KiB
C
142 lines
5.3 KiB
C
// Changes to particular games, as PLAN.md's decisions have them: copy
|
|
// protection is skipped (decision 3) and the script bugs Sierra shipped are
|
|
// patched (decision 2). A game is known by its v3 file prefix (agiRes.c).
|
|
|
|
#include "agi.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include "joey/core.h"
|
|
|
|
|
|
// Gold Rush's clock: logic 0 counts v166 cycles to a second. At a change
|
|
// of speed it copies v166 from v167 (Fastest) or v165 (Fast), which the
|
|
// speed test in logic 129 sets: it counts a second of empty cycles at
|
|
// Fastest and takes a third and a quarter of them (at most 20) for the
|
|
// rate a playing cycle would run at on a 1988 PC. Here Fastest is held to
|
|
// the frame rate, playing or not, so the clock would run six times too
|
|
// fast; and the cracked copy (games/gr) lost the test with logic 129, so
|
|
// its clock stops. Both are filled with the real rates every cycle: Fast
|
|
// is one cycle a unit, Fastest one a frame (agiIdle waits for each).
|
|
#define GR_FAST_CPS_VAR 165u
|
|
#define GR_FASTEST_CPS_VAR 167u
|
|
#define GR_FAST_CPS (AGI_MS_PER_SECOND / AGI_CYCLE_UNIT_MS)
|
|
|
|
// King's Quest IV's graveyards (rooms 16 and 18): three zombies rise at
|
|
// night, each one's distance from Rosella measured into v221..v223 and its
|
|
// state kept in v155, v156, and v162 (room 16) or v158 (room 18).
|
|
#define KQ4_GRAVEYARD_A 16u
|
|
#define KQ4_GRAVEYARD_B 18u
|
|
#define KQ4_ZOMBIE_DIST 221u
|
|
#define KQ4_ZOMBIES 3u
|
|
#define KQ4_ZOMBIE_CHASING 2u
|
|
|
|
|
|
// The copy protection (decision 3): the rooms in which a game asks a
|
|
// question from its manual, and the room a right answer goes on to.
|
|
typedef struct {
|
|
AgiGameIdE game;
|
|
uint8_t first;
|
|
uint8_t last;
|
|
uint8_t after;
|
|
} QuestionRoomsT;
|
|
|
|
// Where skipping the room would skip what a right answer does, the logic
|
|
// is changed as it loads instead: a few bytes of its code, made only over
|
|
// the bytes expected, so other releases are left alone.
|
|
#define LOGIC_PATCH_BYTES 3u
|
|
|
|
typedef struct {
|
|
AgiGameIdE game;
|
|
uint8_t logic;
|
|
uint16_t offset;
|
|
uint8_t expect[LOGIC_PATCH_BYTES];
|
|
uint8_t replace[LOGIC_PATCH_BYTES];
|
|
} LogicPatchT;
|
|
|
|
|
|
// ----- Tables -----
|
|
|
|
static const uint8_t kKq4ZombieStateA[KQ4_ZOMBIES] = { 155u, 156u, 162u };
|
|
static const uint8_t kKq4ZombieStateB[KQ4_ZOMBIES] = { 155u, 156u, 158u };
|
|
|
|
static const QuestionRoomsT kQuestionRooms[] = {
|
|
// King's Quest IV asks a word from its manual in one of rooms 141 to
|
|
// 143; the title, room 96, follows.
|
|
{ AGI_GAME_KQ4, 141u, 143u, 96u },
|
|
// Gold Rush's claim jumper asks about the book "California Gold" on
|
|
// the way from the title (logic 129 to room 125, which picks one of
|
|
// rooms 126 to 128); room 73 follows. The cracked copy's logic 129 is
|
|
// the original's logic 73 and never comes here.
|
|
{ AGI_GAME_GOLD_RUSH, 125u, 125u, 73u }
|
|
};
|
|
|
|
static const LogicPatchT kLogicPatches[] = {
|
|
// Manhunter 1 asks about its Field Guide in room 159; a wrong answer
|
|
// quits. random(6,61,96), which picks the question (code offset 245),
|
|
// becomes goto 443, the right answer's path (player control, logic 90's
|
|
// keys, room 153), as the cracked copy has it.
|
|
{ AGI_GAME_MANHUNTER_1, 159u, 245u, { 0x82u, 0x06u, 0x3Du }, { 0xFEu, 0xC3u, 0x00u } }
|
|
};
|
|
|
|
|
|
// ----- Public API (alphabetical) -----
|
|
|
|
// Before each cycle's logic 0.
|
|
void agiPatchCycle(void) {
|
|
if (gAgi->game.id != AGI_GAME_GOLD_RUSH) {
|
|
return;
|
|
}
|
|
gAgi->vars[GR_FAST_CPS_VAR] = (uint8_t)GR_FAST_CPS;
|
|
gAgi->vars[GR_FASTEST_CPS_VAR] = (uint8_t)jlFrameHz();
|
|
}
|
|
|
|
|
|
// The distance the distance command reports into var. King's Quest IV's
|
|
// zombie bug (in Sierra's interpreter too): with the obsidian scarab, a
|
|
// zombie rising beside Rosella is turned away before it is up, so it
|
|
// rises and never chases. Patched as ScummVM does: until a zombie chases,
|
|
// it reads as far away.
|
|
uint8_t agiPatchDistance(uint8_t var, uint8_t distance) {
|
|
uint8_t room;
|
|
uint8_t zombie;
|
|
|
|
room = gAgi->vars[VAR_ROOM];
|
|
if (gAgi->game.id != AGI_GAME_KQ4 || (room != KQ4_GRAVEYARD_A && room != KQ4_GRAVEYARD_B) || var < KQ4_ZOMBIE_DIST || var >= KQ4_ZOMBIE_DIST + KQ4_ZOMBIES) {
|
|
return distance;
|
|
}
|
|
zombie = (uint8_t)(var - KQ4_ZOMBIE_DIST);
|
|
if (gAgi->vars[(room == KQ4_GRAVEYARD_A) ? kKq4ZombieStateA[zombie] : kKq4ZombieStateB[zombie]] != KQ4_ZOMBIE_CHASING) {
|
|
return AGI_DISTANCE_FAR;
|
|
}
|
|
return distance;
|
|
}
|
|
|
|
|
|
// A logic's code as it loads (after the header), patched in place.
|
|
void agiPatchLogic(uint8_t logicId, uint8_t *code, uint16_t length) {
|
|
const LogicPatchT *p;
|
|
uint8_t i;
|
|
|
|
for (i = 0u; i < sizeof(kLogicPatches) / sizeof(kLogicPatches[0]); i++) {
|
|
p = &kLogicPatches[i];
|
|
if (gAgi->game.id == p->game && logicId == p->logic && (uint32_t)p->offset + LOGIC_PATCH_BYTES <= length && memcmp(&code[p->offset], p->expect, LOGIC_PATCH_BYTES) == 0) {
|
|
memcpy(&code[p->offset], p->replace, LOGIC_PATCH_BYTES);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// The room new.room really goes to: a manual question is skipped.
|
|
uint8_t agiPatchRoom(uint8_t room) {
|
|
const QuestionRoomsT *q;
|
|
uint8_t i;
|
|
|
|
for (i = 0u; i < sizeof(kQuestionRooms) / sizeof(kQuestionRooms[0]); i++) {
|
|
q = &kQuestionRooms[i];
|
|
if (gAgi->game.id == q->game && room >= q->first && room <= q->last) {
|
|
return q->after;
|
|
}
|
|
}
|
|
return room;
|
|
}
|