// 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 #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; }