-
Notifications
You must be signed in to change notification settings - Fork 43
Add cheat code support (retro_cheat_set / retro_cheat_reset) #113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,3 +9,4 @@ | |
| .build | ||
| /.claude | ||
| test/test_blitter_simd | ||
| test/test_cheat | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,144 @@ | ||||||||||||||||||||||
| #include <string.h> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| #include "cheat.h" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| static int hex_digit(char c) | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| if (c >= '0' && c <= '9') return c - '0'; | ||||||||||||||||||||||
| if (c >= 'a' && c <= 'f') return c - 'a' + 10; | ||||||||||||||||||||||
| if (c >= 'A' && c <= 'F') return c - 'A' + 10; | ||||||||||||||||||||||
| return -1; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| bool cheat_parse_one(const char *code, | ||||||||||||||||||||||
| uint32_t *addr_out, | ||||||||||||||||||||||
| uint32_t *val_out, | ||||||||||||||||||||||
| uint8_t *size_out) | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| char buf[32]; | ||||||||||||||||||||||
| size_t n = 0; | ||||||||||||||||||||||
| size_t addr_len, val_len, i; | ||||||||||||||||||||||
| uint32_t addr = 0, val = 0; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (!code || !addr_out || !val_out || !size_out) | ||||||||||||||||||||||
| return false; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| for (; *code && n < sizeof(buf) - 1; code++) | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| if (hex_digit((char)*code) >= 0) | ||||||||||||||||||||||
| buf[n++] = *code; | ||||||||||||||||||||||
| else if (*code != ' ' && *code != '\t' && *code != ':' && | ||||||||||||||||||||||
| *code != '-' && *code != '.') | ||||||||||||||||||||||
| return false; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| buf[n] = '\0'; | ||||||||||||||||||||||
|
Comment on lines
+26
to
+34
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| /* Layout: address_hex + value_hex (concatenated after separator strip). | ||||||||||||||||||||||
| * Accepted pairings cover byte/word/long values under 24- or 32-bit | ||||||||||||||||||||||
| * nominal addresses; the address is always masked to 24 bits below. */ | ||||||||||||||||||||||
| switch (n) | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| case 8: addr_len = 6; val_len = 2; break; /* 6 + byte */ | ||||||||||||||||||||||
| case 10: addr_len = 6; val_len = 4; break; /* 6 + word */ | ||||||||||||||||||||||
| case 12: addr_len = 8; val_len = 4; break; /* 8 + word (PAR) */ | ||||||||||||||||||||||
| case 14: addr_len = 6; val_len = 8; break; /* 6 + long */ | ||||||||||||||||||||||
| case 16: addr_len = 8; val_len = 8; break; /* 8 + long */ | ||||||||||||||||||||||
| default: return false; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| for (i = 0; i < addr_len; i++) | ||||||||||||||||||||||
| addr = (addr << 4) | (uint32_t)hex_digit(buf[i]); | ||||||||||||||||||||||
| for (i = 0; i < val_len; i++) | ||||||||||||||||||||||
| val = (val << 4) | (uint32_t)hex_digit(buf[addr_len + i]); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| *addr_out = addr & 0x00FFFFFFu; | ||||||||||||||||||||||
| *val_out = val; | ||||||||||||||||||||||
| *size_out = (uint8_t)(val_len / 2); | ||||||||||||||||||||||
| return true; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| void cheat_list_remove_index(cheat_list_t *list, unsigned index) | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| unsigned i, j = 0; | ||||||||||||||||||||||
| if (!list) | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| for (i = 0; i < list->count; i++) | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| if (list->entries[i].tag == (uint8_t)(index & 0xFF)) | ||||||||||||||||||||||
| continue; | ||||||||||||||||||||||
| if (j != i) | ||||||||||||||||||||||
| list->entries[j] = list->entries[i]; | ||||||||||||||||||||||
| j++; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| list->count = j; | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| void cheat_list_reset(cheat_list_t *list) | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| if (!list) | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
| memset(list, 0, sizeof(*list)); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| void cheat_list_set(cheat_list_t *list, | ||||||||||||||||||||||
| unsigned index, | ||||||||||||||||||||||
| bool enabled, | ||||||||||||||||||||||
| const char *code) | ||||||||||||||||||||||
| { | ||||||||||||||||||||||
| const char *p; | ||||||||||||||||||||||
| const char *start; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| if (!list || !code) | ||||||||||||||||||||||
| return; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| cheat_list_remove_index(list, index); | ||||||||||||||||||||||
| if (!enabled) | ||||||||||||||||||||||
|
Comment on lines
+91
to
+95
|
||||||||||||||||||||||
| if (!list || !code) | |
| return; | |
| cheat_list_remove_index(list, index); | |
| if (!enabled) | |
| if (!list) | |
| return; | |
| cheat_list_remove_index(list, index); | |
| if (!enabled || !code) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,84 @@ | ||
| #ifndef __CHEAT_H__ | ||
| #define __CHEAT_H__ | ||
|
|
||
| /* | ||
| * Atari Jaguar cheat-code engine. | ||
| * | ||
| * Supports the Pro Action Replay / GameShark-style hex format commonly | ||
| * distributed for Jaguar games. Separators (space, colon, hyphen, dot) | ||
| * between the address and the value are optional and ignored, so the | ||
| * following all parse to the same thing: | ||
| * | ||
| * "00003D00 FFFF" (PAR) | ||
| * "00003D00:FFFF" | ||
| * "0000:3D00-FFFF" | ||
| * "00003D00FFFF" | ||
| * | ||
| * A single `retro_cheat_set` string may contain multiple codes separated | ||
| * by '+' or newlines; each is parsed and stored independently under the | ||
| * same index so the frontend can toggle them as a group. | ||
| * | ||
| * Application is modelled as a callback: the engine is independent of | ||
| * any specific memory implementation, which keeps the parser and list | ||
| * management unit-testable in isolation from the emulator. | ||
| */ | ||
|
|
||
| #include <stdint.h> | ||
| #include <stddef.h> | ||
| #include <boolean.h> | ||
|
|
||
| #ifdef __cplusplus | ||
| extern "C" { | ||
| #endif | ||
|
|
||
| #define CHEAT_MAX_ENTRIES 256 | ||
|
|
||
| typedef struct { | ||
| uint32_t address; /* 24-bit Jaguar bus address */ | ||
| uint32_t value; | ||
| uint8_t size; /* 1 byte, 2 word, 4 long */ | ||
| uint8_t tag; /* retro cheat index (for removal on toggle) */ | ||
| bool enabled; | ||
| } cheat_entry_t; | ||
|
|
||
| typedef struct { | ||
| cheat_entry_t entries[CHEAT_MAX_ENTRIES]; | ||
| unsigned count; | ||
| } cheat_list_t; | ||
|
|
||
| /* Parse a single code string into its components. Returns true iff the | ||
| * string contains a well-formed hex address/value pair (after stripping | ||
| * the ignored separators). On success, *addr is masked to 24 bits. */ | ||
| bool cheat_parse_one(const char *code, | ||
| uint32_t *addr, | ||
| uint32_t *value, | ||
| uint8_t *size); | ||
|
|
||
| /* Remove every cheat tagged with `index`. Safe to call with no matches. */ | ||
| void cheat_list_remove_index(cheat_list_t *list, unsigned index); | ||
|
|
||
| /* Parse `code` (which may contain multiple '+' or newline-separated | ||
| * entries) and install each successfully-parsed entry under `index`. | ||
| * When enabled=false, simply removes any existing entries for `index`. */ | ||
| void cheat_list_set(cheat_list_t *list, | ||
| unsigned index, | ||
| bool enabled, | ||
| const char *code); | ||
|
|
||
| /* Clear every entry. */ | ||
| void cheat_list_reset(cheat_list_t *list); | ||
|
|
||
| /* Callback used by cheat_list_apply to perform the memory write. | ||
| * `size` is 1, 2, or 4 bytes. */ | ||
| typedef void (*cheat_write_fn)(uint32_t addr, uint32_t value, | ||
| uint8_t size, void *user); | ||
|
|
||
| void cheat_list_apply(const cheat_list_t *list, | ||
| cheat_write_fn write, | ||
| void *user); | ||
|
|
||
| #ifdef __cplusplus | ||
| } | ||
| #endif | ||
|
|
||
| #endif /* __CHEAT_H__ */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This CI step builds the test binary as
./test_cheatin the repo root, while the Makefile target buildstest/test_cheat(and.gitignoreignores that path). To keep things consistent and avoid stray artifacts, consider invokingmake testhere, or at least outputting totest/test_cheatand running that.