joeyagi/agiPatch.c
2026-10-20 17:47:19 -05:00

105 lines
3.9 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 "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;
// ----- 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 }
};
// ----- 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;
}
// 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;
}