mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-26 23:55:40 +00:00
Merge pull request #2300 from CookiePLMonster/measured-cheevos
Measured achievements
This commit is contained in:
commit
226acdfe18
|
@ -1,18 +1,29 @@
|
|||
# v10.0.0
|
||||
|
||||
* renamed `rhash.h` to eliminate conflict with system headers, renamed `rconsoles.h` and `rurl.h` for consistency
|
||||
* add rapi sublibrary for communicating with server (eliminates need for client-side JSON parsing; client must still
|
||||
provide HTTP functionality). rurl is now deprecated
|
||||
* renamed 'rhash.h' to 'rc_hash.h' to eliminate conflict with system headers, renamed 'rconsoles.h' and 'rurl.h' for
|
||||
consistency
|
||||
* split non-runtime functions out of 'rcheevos.h' as they're not needed by most clients
|
||||
* allow ranges in rich presence lookups
|
||||
* add rc_richpresence_size_lines function to fetch line associated to error when processing rich presence script
|
||||
* add rc_runtime_invalidate_address function to disable achievements when an unknown address is queried
|
||||
* add RC_CONDITION_RESET_NEXT_IF
|
||||
* support MAXOF($) for leaderboard values using trigger syntax
|
||||
* add RC_CONDITION_SUB_HITS
|
||||
* support MAXOF operator ($) for leaderboard values using trigger syntax
|
||||
* allow RC_CONDITION_PAUSE_IF and RC_CONDITION_RESET_IF in leaderboard value expression
|
||||
* changed track parameter of rc_hash_cdreader_open_track_handler to support three virtual tracks:
|
||||
RC_HASH_CDTRACK_FIRST_DATA, RC_HASH_CDTRACK_LAST and RC_HASH_CDTRACK_LARGEST.
|
||||
* changed offset parameter of rc_hash_filereader_seek_handler and return value of rc_hash_filereader_tell_handler
|
||||
from size_t to int64_t to support files larger than 2GB when compiling in 32-bit mode.
|
||||
* reset to default cd reader if NULL is passed to rc_hash_init_custom_cdreader
|
||||
* add hash support for RC_CONSOLE_DREAMCAST
|
||||
* ignore headers for RC_CONSOLE_PC_ENGINE
|
||||
* look for unique identifier in RC_CONSOLE_SEGA_CD and RC_CONSOLE_SATURN discs
|
||||
* add hash support for RC_CONSOLE_DREAMCAST, RC_CONSOLE_PLAYSTATION_2, RC_CONSOLE_SUPERVISION, and RC_CONSOLE_TIC80
|
||||
* ignore headers when generating hashs for RC_CONSOLE_PC_ENGINE and RC_CONSOLE_ATARI_7800
|
||||
* require unique identifier when hashing RC_CONSOLE_SEGA_CD and RC_CONSOLE_SATURN discs
|
||||
* add expansion memory to RC_CONSOLE_SG1000 memory map
|
||||
* rename RC_CONSOLE_MAGNAVOX_ODYSSEY -> RC_CONSOLE_MAGNAVOX_ODYSSEY2
|
||||
* rename RC_CONSOLE_AMIGA_ST -> RC_CONSOLE_ATARI_ST
|
||||
* add RC_CONSOLE_SUPERVISION, RC_CONSOLE_SHARPX1, RC_CONSOLE_TIC80, RC_CONSOLE_THOMSONTO8
|
||||
* fix error identifying largest track when track has multiple bins
|
||||
* fix memory corruption error when cue track has more than 6 INDEXs
|
||||
* several improvements to data storage for conditions (rc_memref_t and rc_memref_value_t structures have been modified)
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
add_library(rcheevos
|
||||
include/rc_consoles.h
|
||||
include/rc_hash.h
|
||||
include/rc_url.h
|
||||
include/rcheevos.h
|
||||
include/rc_consoles.h
|
||||
include/rc_error.h
|
||||
include/rc_hash.h
|
||||
include/rc_runtime.h
|
||||
include/rc_runtime_types.h
|
||||
include/rc_url.h
|
||||
src/rcheevos/alloc.c
|
||||
src/rcheevos/compat.c
|
||||
src/rcheevos/condition.c
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
**rcheevos** is a set of C code, or a library if you will, that tries to make it easier for emulators to process [RetroAchievements](https://retroachievements.org) data, providing support for achievements and leaderboards for their players.
|
||||
|
||||
Keep in mind that **rcheevos** does *not* provide HTTP network connections or JSON parsing. Clients must get data from RetroAchievements, parse the JSON payloads and pass the results down to **rcheevos** for processing. (**TODO**: document the server API and JSON schema.)
|
||||
Keep in mind that **rcheevos** does *not* provide HTTP network connections. Clients must get data from RetroAchievements, and pass the response down to **rcheevos** for processing.
|
||||
|
||||
Not all structures defined by **rcheevos** can be created via the public API, but are exposed to allow interactions beyond just creation, destruction, and testing, such as the ones required by UI code that helps to create them.
|
||||
|
||||
|
@ -24,7 +24,9 @@ function peek(address, num_bytes, userdata)
|
|||
|
||||
## API
|
||||
|
||||
> An understanding about how achievements are developed may be useful, you can read more about it [here](http://docs.retroachievements.org/Developer-docs/).
|
||||
An understanding about how achievements are developed may be useful, you can read more about it [here](http://docs.retroachievements.org/Developer-docs/).
|
||||
|
||||
Most of the exposed APIs are documented [here](https://github.com/RetroAchievements/rcheevos/wiki)
|
||||
|
||||
### User Configuration
|
||||
|
||||
|
@ -34,7 +36,9 @@ If your platform will benefit from a different value, define a new value for it
|
|||
|
||||
### Return values
|
||||
|
||||
The functions that compute the amount of memory that something will take return the number of bytes, or a negative value from the following enumeration:
|
||||
Any function in the rcheevos library that returns a success indicator will return one of the following values.
|
||||
|
||||
These are in `rc_error.h`.
|
||||
|
||||
```c
|
||||
enum {
|
||||
|
@ -62,7 +66,9 @@ enum {
|
|||
RC_MISSING_VALUE_MEASURED = -21,
|
||||
RC_MULTIPLE_MEASURED = -22,
|
||||
RC_INVALID_MEASURED_TARGET = -23,
|
||||
RC_INVALID_COMPARISON = -24
|
||||
RC_INVALID_COMPARISON = -24,
|
||||
RC_INVALID_STATE = -25,
|
||||
RC_INVALID_JSON = -26
|
||||
};
|
||||
```
|
||||
|
||||
|
@ -75,6 +81,8 @@ const char* rc_error_str(int ret);
|
|||
|
||||
This enumeration uniquely identifies each of the supported platforms in RetroAchievements.
|
||||
|
||||
These are in `rc_consoles.h`.
|
||||
|
||||
```c
|
||||
enum {
|
||||
RC_CONSOLE_MEGA_DRIVE = 1,
|
||||
|
@ -138,339 +146,21 @@ enum {
|
|||
RC_CONSOLE_ZX_SPECTRUM = 59,
|
||||
RC_CONSOLE_GAME_AND_WATCH = 60,
|
||||
RC_CONSOLE_NOKIA_NGAGE = 61,
|
||||
RC_CONSOLE_NINTENDO_3DS = 62
|
||||
RC_CONSOLE_NINTENDO_3DS = 62,
|
||||
RC_CONSOLE_SUPERVISION = 63,
|
||||
RC_CONSOLE_SHARPX1 = 64,
|
||||
RC_CONSOLE_TIC80 = 65,
|
||||
RC_CONSOLE_THOMSONTO8 = 66
|
||||
};
|
||||
```
|
||||
|
||||
### `rc_operand_t`
|
||||
## Runtime support
|
||||
|
||||
An operand is the leaf node of RetroAchievements expressions, and can hold one of the following:
|
||||
The runtime encapsulates a set of achievements, leaderboards, and rich presence for a game and manages processing them for each frame. When important things occur, events are raised for the caller via a callback.
|
||||
|
||||
* A constant integer or floating-point value
|
||||
* A memory address of the system being emulated
|
||||
* A reference to the Lua function that will be called to provide the value
|
||||
These are in `rc_runtime.h`.
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
union {
|
||||
/* A value read from memory. */
|
||||
rc_memref_value_t* memref;
|
||||
|
||||
/* An integer value. */
|
||||
unsigned num;
|
||||
|
||||
/* A floating point value. */
|
||||
double dbl;
|
||||
|
||||
/* A reference to the Lua function that provides the value. */
|
||||
int luafunc;
|
||||
};
|
||||
|
||||
/* specifies which member of the value union is being used */
|
||||
char type;
|
||||
|
||||
/* the actual RC_MEMSIZE of the operand - memref.size may differ */
|
||||
char size;
|
||||
}
|
||||
rc_operand_t;
|
||||
```
|
||||
|
||||
The `size` field, when applicable, holds one of these values:
|
||||
|
||||
```c
|
||||
enum {
|
||||
RC_MEMSIZE_8_BITS,
|
||||
RC_MEMSIZE_16_BITS,
|
||||
RC_MEMSIZE_24_BITS,
|
||||
RC_MEMSIZE_32_BITS,
|
||||
RC_MEMSIZE_LOW,
|
||||
RC_MEMSIZE_HIGH,
|
||||
RC_MEMSIZE_BIT_0,
|
||||
RC_MEMSIZE_BIT_1,
|
||||
RC_MEMSIZE_BIT_2,
|
||||
RC_MEMSIZE_BIT_3,
|
||||
RC_MEMSIZE_BIT_4,
|
||||
RC_MEMSIZE_BIT_5,
|
||||
RC_MEMSIZE_BIT_6,
|
||||
RC_MEMSIZE_BIT_7,
|
||||
RC_MEMSIZE_BITCOUNT
|
||||
};
|
||||
```
|
||||
|
||||
The `type` field is always valid, and holds one of these values:
|
||||
|
||||
```c
|
||||
enum {
|
||||
RC_OPERAND_ADDRESS, /* The value of a live address in RAM. */
|
||||
RC_OPERAND_DELTA, /* The value last known at this address. */
|
||||
RC_OPERAND_CONST, /* A 32-bit unsigned integer. */
|
||||
RC_OPERAND_FP, /* A floating point value. */
|
||||
RC_OPERAND_LUA, /* A Lua function that provides the value. */
|
||||
RC_OPERAND_PRIOR, /* The last differing value at this address. */
|
||||
RC_OPERAND_BCD, /* The BCD-decoded value of a live address in RAM */
|
||||
RC_OPERAND_INVERTED /* The twos-complement value of a live address in RAM */
|
||||
};
|
||||
```
|
||||
|
||||
`RC_OPERAND_ADDRESS`, `RC_OPERAND_DELTA`, `RC_OPERAND_PRIOR`, `RC_OPERAND_BCD`, and `RC_OPERAND_INVERTED` mean that `memref` is active. `RC_OPERAND_CONST` means that `num` is active. `RC_OPERAND_FP` means that `dbl` is active. `RC_OPERAND_LUA` means `luafunc` is active.
|
||||
|
||||
|
||||
### `rc_condition_t`
|
||||
|
||||
A condition compares its two operands according to the defined operator. It also keeps track of other things to make it possible to code more advanced achievements.
|
||||
|
||||
```c
|
||||
typedef struct rc_condition_t rc_condition_t;
|
||||
|
||||
struct rc_condition_t {
|
||||
/* The condition's operands. */
|
||||
rc_operand_t operand1;
|
||||
rc_operand_t operand2;
|
||||
|
||||
/* Required hits to fire this condition. */
|
||||
unsigned required_hits;
|
||||
/* Number of hits so far. */
|
||||
unsigned current_hits;
|
||||
|
||||
/* The next condition in the chain. */
|
||||
rc_condition_t* next;
|
||||
|
||||
/* The type of the condition. */
|
||||
char type;
|
||||
/* The comparison operator to use. */
|
||||
char oper; /* operator is a reserved word in C++. */
|
||||
/* Set if the condition needs to processed as part of the "check if paused" pass. */
|
||||
char pause;
|
||||
/* Whether or not the condition evaluated as true on the last check. */
|
||||
char is_true;
|
||||
};
|
||||
```
|
||||
|
||||
`type` can be one of these values:
|
||||
|
||||
```c
|
||||
enum {
|
||||
RC_CONDITION_STANDARD,
|
||||
RC_CONDITION_PAUSE_IF,
|
||||
RC_CONDITION_RESET_IF,
|
||||
RC_CONDITION_ADD_SOURCE,
|
||||
RC_CONDITION_SUB_SOURCE,
|
||||
RC_CONDITION_ADD_HITS,
|
||||
RC_CONDITION_AND_NEXT,
|
||||
RC_CONDITION_MEASURED,
|
||||
RC_CONDITION_ADD_ADDRESS,
|
||||
RC_CONDITION_TRIGGER,
|
||||
RC_CONDITION_MEASURED_IF,
|
||||
RC_CONDITION_RESET_NEXT_IF,
|
||||
};
|
||||
```
|
||||
|
||||
`oper` is the comparison operator to be used when comparing the two operands:
|
||||
|
||||
```c
|
||||
enum {
|
||||
RC_OPERATOR_EQ,
|
||||
RC_OPERATOR_LT,
|
||||
RC_OPERATOR_LE,
|
||||
RC_OPERATOR_GT,
|
||||
RC_OPERATOR_GE,
|
||||
RC_OPERATOR_NE,
|
||||
RC_OPERATOR_NONE,
|
||||
RC_OPERATOR_MULT,
|
||||
RC_OPERATOR_DIV,
|
||||
RC_OPERATOR_AND
|
||||
};
|
||||
```
|
||||
|
||||
### `rc_condset_t`
|
||||
|
||||
Condition sets are an ordered collection of conditions (`rc_condition_t`), which are usually and'ed together to help build complex expressions for achievements.
|
||||
|
||||
```c
|
||||
typedef struct rc_condset_t rc_condset_t;
|
||||
|
||||
struct rc_condset_t {
|
||||
/* The next condition set in the chain. */
|
||||
rc_condset_t* next;
|
||||
|
||||
/* The list of conditions in this condition set. */
|
||||
rc_condition_t* conditions;
|
||||
|
||||
/* True if any condition in the set is a pause condition. */
|
||||
char has_pause;
|
||||
};
|
||||
```
|
||||
|
||||
### `rc_trigger_t`
|
||||
|
||||
Triggers are the basic blocks of achievements and leaderboards. In fact, achievements are just triggers with some additional information like title, description, a badge, and some state, like whether it has already been awarded or not. All the logic to test if an achievement should be awarded is encapsulated in `rc_trigger_t`.
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
/* The main condition set. */
|
||||
rc_condset_t* requirement;
|
||||
|
||||
/* The list of sub condition sets in this test. */
|
||||
rc_condset_t* alternative;
|
||||
|
||||
/* The memory references required by the trigger. */
|
||||
rc_memref_value_t* memrefs;
|
||||
}
|
||||
rc_trigger_t;
|
||||
```
|
||||
|
||||
The size in bytes of memory a trigger needs to be created is given by the `rc_trigger_size` function:
|
||||
|
||||
```c
|
||||
int rc_trigger_size(const char* memaddr);
|
||||
```
|
||||
|
||||
The return value is the size needed for the trigger described by the `memaddr` parameter, or a negative value with an [error code](#return-values).
|
||||
|
||||
Once the memory size is known, `rc_parse_trigger` can be called to actually construct a trigger in the caller-provided buffer:
|
||||
|
||||
```c
|
||||
rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
```
|
||||
|
||||
`buffer` is the caller-allocated buffer, which must have enough space for the trigger. `memaddr` describes the trigger, and must be the same one used to compute the trigger's size with `rc_trigger_size`. `L` must be a valid Lua state, and `funcs_ndx` must be an index to the current Lua stack which contains a table which is a map of names to functions. This map is used to look for operands which are Lua functions.
|
||||
|
||||
Once the trigger is created, `rc_evaluate_trigger` can be called to test whether the trigger fires or not.
|
||||
|
||||
```c
|
||||
int rc_evaluate_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L);
|
||||
```
|
||||
|
||||
`trigger` is the trigger to test. `peek` is a callback used to read bytes from the emulated memory. `ud` is an user-provided opaque value that is passed to `peek`. `L` is the Lua state in which context the Lua functions are looked for and called, if necessary.
|
||||
|
||||
`rc_peek_t`'s signature is:
|
||||
|
||||
```c
|
||||
typedef unsigned (*rc_peek_t)(unsigned address, unsigned num_bytes, void* ud);
|
||||
```
|
||||
|
||||
where `address` is the starting address to read from, `num_bytes` the number of bytes to read (1, 2, or 4, little-endian), and `ud` is the same value passed to `rc_test_trigger`.
|
||||
|
||||
> Addresses passed to `peek` do *not* map 1:1 to the emulated memory. (**TODO**: document the mapping from `peek` addresses to emulated memory for each supported system.)
|
||||
|
||||
The return value of `rc_evaluate_trigger` is one of the following:
|
||||
```c
|
||||
enum {
|
||||
RC_TRIGGER_STATE_INACTIVE, /* achievement is not being processed */
|
||||
RC_TRIGGER_STATE_WAITING, /* achievement cannot trigger until it has been false for at least one frame */
|
||||
RC_TRIGGER_STATE_ACTIVE, /* achievement is active and may trigger */
|
||||
RC_TRIGGER_STATE_PAUSED, /* achievement is currently paused and will not trigger */
|
||||
RC_TRIGGER_STATE_RESET, /* achievement hit counts were reset */
|
||||
RC_TRIGGER_STATE_TRIGGERED, /* achievement has triggered */
|
||||
RC_TRIGGER_STATE_PRIMED /* all non-Trigger conditions are true */
|
||||
};
|
||||
```
|
||||
|
||||
Finally, `rc_reset_trigger` can be used to reset the internal state of a trigger.
|
||||
|
||||
```c
|
||||
void rc_reset_trigger(rc_trigger_t* self);
|
||||
```
|
||||
|
||||
### `rc_value_t`
|
||||
|
||||
A value is a collection of conditions that result in a single RC_CONDITION_MEASURED expression. It's used to calculate the value for a leaderboard and for lookups in rich presence.
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
/* The list of conditions to evaluate. */
|
||||
rc_condset_t* conditions;
|
||||
|
||||
/* The memory references required by the value. */
|
||||
rc_memref_value_t* memrefs;
|
||||
}
|
||||
rc_value_t;
|
||||
```
|
||||
|
||||
The size in bytes needed to create a value can be computed by `rc_value_size`:
|
||||
|
||||
```c
|
||||
int rc_value_size(const char* memaddr);
|
||||
```
|
||||
|
||||
With the size at hand, the caller can allocate the necessary memory and pass it to `rc_parse_value` to create the value:
|
||||
|
||||
```c
|
||||
rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
```
|
||||
|
||||
`buffer`, `memaddr`, `L`, and `funcs_ndx` are the same as in [`rc_parse_trigger`](#rc_parse_trigger).
|
||||
|
||||
To compute the value, use `rc_evaluate_value`:
|
||||
|
||||
```c
|
||||
int rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, lua_State* L);
|
||||
```
|
||||
|
||||
`value` is the value to compute the value of, and `peek`, `ud`, and `L`, are as in [`rc_test_trigger`](#rc_test_trigger).
|
||||
|
||||
### `rc_lboard_t`
|
||||
|
||||
Leaderboards track a value over time, starting when a trigger is fired. The leaderboard can be canceled depending on the value of another trigger, and submitted to the RetroAchievements server depending on a third trigger.
|
||||
|
||||
The value submitted comes from the `value` field. Values displayed to the player come from the `progress` field unless it's `NULL`, in which case it's the same as `value`.
|
||||
|
||||
```c
|
||||
typedef struct {
|
||||
rc_trigger_t start;
|
||||
rc_trigger_t submit;
|
||||
rc_trigger_t cancel;
|
||||
rc_value_t value;
|
||||
rc_value_t* progress;
|
||||
rc_memref_value_t* memrefs;
|
||||
|
||||
char state;
|
||||
}
|
||||
rc_lboard_t;
|
||||
```
|
||||
|
||||
Leaderboards are created and parsed just the same as triggers and values:
|
||||
|
||||
```c
|
||||
int rc_lboard_size(const char* memaddr);
|
||||
rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
```
|
||||
|
||||
A leaderboard can be evaluated with the `rc_evaluate_lboard` function:
|
||||
|
||||
```c
|
||||
int rc_evaluate_lboard(rc_lboard_t* lboard, int* value, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
```
|
||||
|
||||
The function returns an action that must be performed by the caller, and `value` contains the value to be used for that action when the function returns. The action can be one of:
|
||||
|
||||
```c
|
||||
enum {
|
||||
RC_LBOARD_STATE_INACTIVE, /* leaderboard is not being processed */
|
||||
RC_LBOARD_STATE_WAITING, /* leaderboard cannot activate until the start condition has been false for at least one frame */
|
||||
RC_LBOARD_STATE_ACTIVE, /* leaderboard is active and may start */
|
||||
RC_LBOARD_STATE_STARTED, /* leaderboard attempt in progress */
|
||||
RC_LBOARD_STATE_CANCELED, /* leaderboard attempt canceled */
|
||||
RC_LBOARD_STATE_TRIGGERED /* leaderboard attempt complete, value should be submitted */
|
||||
};
|
||||
```
|
||||
|
||||
The caller must keep track of these values and do the necessary actions:
|
||||
|
||||
* `RC_LBOARD_ACTIVE` and `RC_LBOARD_INACTIVE`: just signal that the leaderboard didn't change its state.
|
||||
* `RC_LBOARD_STARTED`: indicates that the leaderboard has been started, so the caller can i.e. show a message for the player, and start showing its value in the UI.
|
||||
* `RC_LBOARD_CANCELED`: the leaderboard has been canceled, and the caller can inform the user and stop showing its value.
|
||||
* `RC_LBOARD_TRIGGERED`: the leaderboard has been finished, and the value must be submitted to the RetroAchievements server; the caller can also notify the player and stop showing the value in the UI.
|
||||
|
||||
`rc_reset_lboard` resets the leaderboard:
|
||||
|
||||
```c
|
||||
void rc_reset_lboard(rc_lboard_t* lboard);
|
||||
```
|
||||
|
||||
### `rc_runtime_t`
|
||||
|
||||
The runtime encapsulates a set of achievements and leaderboards and manages processing them for each frame. When important things occur, events are raised for the caller via a callback.
|
||||
The `rc_runtime_t` structure uses several forward-defines. If you need access to the actual contents of any of the forward-defined structures, those definitions are in `rc_runtime_types.h`
|
||||
|
||||
```c
|
||||
typedef struct rc_runtime_t {
|
||||
|
@ -483,11 +173,12 @@ typedef struct rc_runtime_t {
|
|||
unsigned lboard_capacity;
|
||||
|
||||
rc_runtime_richpresence_t* richpresence;
|
||||
char* richpresence_display_buffer;
|
||||
char richpresence_update_timer;
|
||||
|
||||
rc_memref_value_t* memrefs;
|
||||
rc_memref_value_t** next_memref;
|
||||
|
||||
rc_value_t* variables;
|
||||
rc_value_t** next_variable;
|
||||
}
|
||||
rc_runtime_t;
|
||||
```
|
||||
|
@ -506,7 +197,7 @@ int rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script,
|
|||
|
||||
The runtime should be called once per frame to evaluate the state of the active achievements/leaderboards:
|
||||
```c
|
||||
void rc_runtime_do_frame(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_peek_t peek, void* ud, lua_State* L);
|
||||
void rc_runtime_do_frame(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, lua_State* L);
|
||||
```
|
||||
|
||||
The `event_handler` is a callback function that is called for each event that occurs when processing the frame.
|
||||
|
@ -541,6 +232,10 @@ The `event.type` field will be one of the following:
|
|||
The leaderboard value has changed.
|
||||
* RC_RUNTIME_EVENT_LBOARD_TRIGGERED (id=leaderboard id, value=leaderboard value)
|
||||
The leaderboard's submit condition has been met and the user should be informed that a leaderboard attempt was successful. The value should be submitted.
|
||||
* RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED (id=achievement id)
|
||||
The achievement has been disabled by a call to `rc_invalidate_address`.
|
||||
* RC_RUNTIME_EVENT_LBOARD_DISABLED (id=leaderboard id)
|
||||
The achievement has been disabled by a call to `rc_invalidate_address`.
|
||||
|
||||
When an achievement triggers, it should be deactivated so it won't trigger again:
|
||||
```c
|
||||
|
@ -548,7 +243,13 @@ void rc_runtime_deactivate_achievement(rc_runtime_t* runtime, unsigned id);
|
|||
```
|
||||
Additionally, the unlock should be submitted to the server.
|
||||
|
||||
When a leaderboard triggers, it should not be deactivated in case the player wants to try again for a better score. The value should be submitted to the server.
|
||||
When a leaderboard triggers, it should not be deactivated in case the player wants to try again for a better score. The value should be submitted to the server.
|
||||
|
||||
For `RC_RUNTIME_EVENT_LBOARD_UPDATED` and `RC_RUNTIME_EVENT_LBOARD_TRIGGERED` events, there is a helper function to call if you wish to display the leaderboard value on screen.
|
||||
|
||||
```c
|
||||
int rc_runtime_format_lboard_value(char* buffer, int size, int value, int format);
|
||||
```
|
||||
|
||||
`rc_runtime_do_frame` also periodically updates the rich presense string (every 60 frames). To get the current value, call
|
||||
```c
|
||||
|
@ -562,73 +263,34 @@ void rc_runtime_reset(rc_runtime_t* runtime);
|
|||
|
||||
This ensures any active achievements/leaderboards are set back to their initial states and prevents unexpected triggers when the memory changes in atypical way.
|
||||
|
||||
## Server Communication
|
||||
|
||||
### Value Formatting
|
||||
**rapi** builds URLs to access many RetroAchievements web services. Its purpose it to just to free the developer from having to URL-encode parameters and build correct URLs that are valid for the server.
|
||||
|
||||
**rcheevos** includes helper functions to parse formatting strings from RetroAchievements, and format values according to them.
|
||||
**rapi** does *not* make HTTP requests.
|
||||
|
||||
`rc_parse_format` returns the format for the given string:
|
||||
NOTE: **rapi** is a replacement for **rurl**. **rurl** has been deprecated.
|
||||
|
||||
```c
|
||||
int rc_parse_format(const char* format_str);
|
||||
```
|
||||
These are in `rc_api_user.h`, `rc_api_runtime.h` and `rc_api_common.h`.
|
||||
|
||||
The returned value is one of:
|
||||
The basic process of making an **rapi** call is to initialize a params object, call a function to convert it to a URL, send that to the server, then pass the response to a function to convert it into a response object, and handle the response values.
|
||||
|
||||
```c
|
||||
enum {
|
||||
RC_FORMAT_FRAMES,
|
||||
RC_FORMAT_SECONDS,
|
||||
RC_FORMAT_CENTISECS,
|
||||
RC_FORMAT_SCORE,
|
||||
RC_FORMAT_VALUE,
|
||||
RC_FORMAT_MINUTES,
|
||||
RC_FORMAT_SECONDS_AS_MINUTES
|
||||
};
|
||||
```
|
||||
|
||||
`RC_FORMAT_VALUE` is returned if `format_str` doesn't contain a valid format.
|
||||
|
||||
`rc_format_value` can be used to format the given value into the provided buffer:
|
||||
|
||||
```c
|
||||
int rc_format_value(char* buffer, int size, int value, int format);
|
||||
```
|
||||
|
||||
`buffer` receives `value` formatted according to `format`. No more than `size` characters will be written to `buffer`. 32 characters are enough to hold any valid value with any format. The returned value is the number of characters written.
|
||||
|
||||
# **rurl**
|
||||
|
||||
**rurl** builds URLs to access many RetroAchievements web services. Its purpose it to just to free the developer from having to URL-encode parameters and build correct URL that are valid for the server.
|
||||
|
||||
**rurl** does *not* make HTTP requests.
|
||||
|
||||
## API
|
||||
|
||||
### Return values
|
||||
|
||||
All functions return `0` if successful, or `-1` in case of errors. Errors are usually because the provided buffer is too small to hold the URL. If your buffer is large and you're still receiving errors, please open an issue.
|
||||
An example can be found on the [rc_api_init_login_request](https://github.com/RetroAchievements/rcheevos/wiki/rc_api_init_login_request#example) page.
|
||||
|
||||
### Functions
|
||||
|
||||
All functions take a `buffer`, where the URL will be written into, and `size` with the size of the buffer. The other parameters are particular to the desired URL.
|
||||
Please see the [wiki](https://github.com/RetroAchievements/rcheevos/wiki) for details on the functions exposed for **rapi**.
|
||||
|
||||
## Game Identification
|
||||
|
||||
**rhash** provides logic for generating a RetroAchievements hash for a given game. There are two ways to use the API - you can pass the filename and let rhash open and process the file, or you can pass the buffered copy of the file to rhash if you've already loaded it into memory.
|
||||
|
||||
These are in `rc_hash.h`.
|
||||
|
||||
```c
|
||||
int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned cheevo_id, int hardcore);
|
||||
|
||||
int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, unsigned value, unsigned char hash[16]);
|
||||
|
||||
int rc_url_get_gameid(char* buffer, size_t size, unsigned char hash[16]);
|
||||
|
||||
int rc_url_get_patch(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid);
|
||||
|
||||
int rc_url_get_badge_image(char* buffer, size_t size, const char* badge_name);
|
||||
|
||||
int rc_url_login_with_password(char* buffer, size_t size, const char* user_name, const char* password);
|
||||
|
||||
int rc_url_login_with_token(char* buffer, size_t size, const char* user_name, const char* login_token);
|
||||
|
||||
int rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid, int hardcore);
|
||||
|
||||
int rc_url_post_playing(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid);
|
||||
int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer, size_t buffer_size);
|
||||
int rc_hash_generate_from_file(char hash[33], int console_id, const char* path);
|
||||
```
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -72,6 +72,10 @@ enum {
|
|||
RC_CONSOLE_GAME_AND_WATCH = 60,
|
||||
RC_CONSOLE_NOKIA_NGAGE = 61,
|
||||
RC_CONSOLE_NINTENDO_3DS = 62,
|
||||
RC_CONSOLE_SUPERVISION = 63,
|
||||
RC_CONSOLE_SHARPX1 = 64,
|
||||
RC_CONSOLE_TIC80 = 65,
|
||||
RC_CONSOLE_THOMSONTO8 = 66,
|
||||
|
||||
RC_CONSOLE_HUBS = 100,
|
||||
RC_CONSOLE_EVENTS = 101
|
||||
|
@ -79,6 +83,38 @@ enum {
|
|||
|
||||
const char* rc_console_name(int console_id);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Memory mapping |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_MEMORY_TYPE_SYSTEM_RAM, /* normal system memory */
|
||||
RC_MEMORY_TYPE_SAVE_RAM, /* memory that persists between sessions */
|
||||
RC_MEMORY_TYPE_VIDEO_RAM, /* memory reserved for graphical processing */
|
||||
RC_MEMORY_TYPE_READONLY, /* memory that maps to read only data */
|
||||
RC_MEMORY_TYPE_HARDWARE_CONTROLLER, /* memory for interacting with system components */
|
||||
RC_MEMORY_TYPE_VIRTUAL_RAM, /* secondary address space that maps to real memory in system RAM */
|
||||
RC_MEMORY_TYPE_UNUSED /* these addresses don't really exist */
|
||||
};
|
||||
|
||||
typedef struct rc_memory_region_t {
|
||||
unsigned start_address; /* first address of block as queried by RetroAchievements */
|
||||
unsigned end_address; /* last address of block as queried by RetroAchievements */
|
||||
unsigned real_address; /* real address for first address of block */
|
||||
char type; /* RC_MEMORY_TYPE_ for block */
|
||||
const char* description; /* short description of block */
|
||||
}
|
||||
rc_memory_region_t;
|
||||
|
||||
typedef struct rc_memory_regions_t {
|
||||
const rc_memory_region_t* region;
|
||||
unsigned num_regions;
|
||||
}
|
||||
rc_memory_regions_t;
|
||||
|
||||
const rc_memory_regions_t* rc_console_memory_regions(int console_id);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
48
dep/rcheevos/include/rc_error.h
Normal file
48
dep/rcheevos/include/rc_error.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
#ifndef RC_ERROR_H
|
||||
#define RC_ERROR_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*****************************************************************************\
|
||||
| Return values |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_OK = 0,
|
||||
RC_INVALID_LUA_OPERAND = -1,
|
||||
RC_INVALID_MEMORY_OPERAND = -2,
|
||||
RC_INVALID_CONST_OPERAND = -3,
|
||||
RC_INVALID_FP_OPERAND = -4,
|
||||
RC_INVALID_CONDITION_TYPE = -5,
|
||||
RC_INVALID_OPERATOR = -6,
|
||||
RC_INVALID_REQUIRED_HITS = -7,
|
||||
RC_DUPLICATED_START = -8,
|
||||
RC_DUPLICATED_CANCEL = -9,
|
||||
RC_DUPLICATED_SUBMIT = -10,
|
||||
RC_DUPLICATED_VALUE = -11,
|
||||
RC_DUPLICATED_PROGRESS = -12,
|
||||
RC_MISSING_START = -13,
|
||||
RC_MISSING_CANCEL = -14,
|
||||
RC_MISSING_SUBMIT = -15,
|
||||
RC_MISSING_VALUE = -16,
|
||||
RC_INVALID_LBOARD_FIELD = -17,
|
||||
RC_MISSING_DISPLAY_STRING = -18,
|
||||
RC_OUT_OF_MEMORY = -19,
|
||||
RC_INVALID_VALUE_FLAG = -20,
|
||||
RC_MISSING_VALUE_MEASURED = -21,
|
||||
RC_MULTIPLE_MEASURED = -22,
|
||||
RC_INVALID_MEASURED_TARGET = -23,
|
||||
RC_INVALID_COMPARISON = -24,
|
||||
RC_INVALID_STATE = -25,
|
||||
RC_INVALID_JSON = -26
|
||||
};
|
||||
|
||||
const char* rc_error_str(int ret);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* RC_ERROR_H */
|
|
@ -66,10 +66,10 @@ extern "C" {
|
|||
typedef void* (*rc_hash_filereader_open_file_handler)(const char* path_utf8);
|
||||
|
||||
/* moves the file pointer - standard fseek parameters */
|
||||
typedef void (*rc_hash_filereader_seek_handler)(void* file_handle, size_t offset, int origin);
|
||||
typedef void (*rc_hash_filereader_seek_handler)(void* file_handle, int64_t offset, int origin);
|
||||
|
||||
/* locates the file pointer */
|
||||
typedef size_t (*rc_hash_filereader_tell_handler)(void* file_handle);
|
||||
typedef int64_t (*rc_hash_filereader_tell_handler)(void* file_handle);
|
||||
|
||||
/* reads the specified number of bytes from the file starting at the read pointer.
|
||||
* returns the number of bytes actually read.
|
||||
|
|
146
dep/rcheevos/include/rc_runtime.h
Normal file
146
dep/rcheevos/include/rc_runtime.h
Normal file
|
@ -0,0 +1,146 @@
|
|||
#ifndef RC_RUNTIME_H
|
||||
#define RC_RUNTIME_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "rc_error.h"
|
||||
|
||||
/*****************************************************************************\
|
||||
| Forward Declarations (defined in rc_runtime_types.h) |
|
||||
\*****************************************************************************/
|
||||
|
||||
#ifndef RC_RUNTIME_TYPES_H /* prevents pedantic redefinition error */
|
||||
|
||||
typedef struct lua_State lua_State;
|
||||
|
||||
typedef struct rc_trigger_t rc_trigger_t;
|
||||
typedef struct rc_lboard_t rc_lboard_t;
|
||||
typedef struct rc_richpresence_t rc_richpresence_t;
|
||||
typedef struct rc_memref_t rc_memref_t;
|
||||
typedef struct rc_value_t rc_value_t;
|
||||
|
||||
#endif
|
||||
|
||||
/*****************************************************************************\
|
||||
| Callbacks |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Callback used to read num_bytes bytes from memory starting at address. If
|
||||
* num_bytes is greater than 1, the value is read in little-endian from
|
||||
* memory.
|
||||
*/
|
||||
typedef unsigned (*rc_runtime_peek_t)(unsigned address, unsigned num_bytes, void* ud);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Runtime |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_runtime_trigger_t {
|
||||
unsigned id;
|
||||
rc_trigger_t* trigger;
|
||||
void* buffer;
|
||||
rc_memref_t* invalid_memref;
|
||||
unsigned char md5[16];
|
||||
int serialized_size;
|
||||
char owns_memrefs;
|
||||
}
|
||||
rc_runtime_trigger_t;
|
||||
|
||||
typedef struct rc_runtime_lboard_t {
|
||||
unsigned id;
|
||||
int value;
|
||||
rc_lboard_t* lboard;
|
||||
void* buffer;
|
||||
rc_memref_t* invalid_memref;
|
||||
unsigned char md5[16];
|
||||
char owns_memrefs;
|
||||
}
|
||||
rc_runtime_lboard_t;
|
||||
|
||||
typedef struct rc_runtime_richpresence_t {
|
||||
rc_richpresence_t* richpresence;
|
||||
void* buffer;
|
||||
struct rc_runtime_richpresence_t* previous;
|
||||
char owns_memrefs;
|
||||
}
|
||||
rc_runtime_richpresence_t;
|
||||
|
||||
typedef struct rc_runtime_t {
|
||||
rc_runtime_trigger_t* triggers;
|
||||
unsigned trigger_count;
|
||||
unsigned trigger_capacity;
|
||||
|
||||
rc_runtime_lboard_t* lboards;
|
||||
unsigned lboard_count;
|
||||
unsigned lboard_capacity;
|
||||
|
||||
rc_runtime_richpresence_t* richpresence;
|
||||
|
||||
rc_memref_t* memrefs;
|
||||
rc_memref_t** next_memref;
|
||||
|
||||
rc_value_t* variables;
|
||||
rc_value_t** next_variable;
|
||||
}
|
||||
rc_runtime_t;
|
||||
|
||||
void rc_runtime_init(rc_runtime_t* runtime);
|
||||
void rc_runtime_destroy(rc_runtime_t* runtime);
|
||||
|
||||
int rc_runtime_activate_achievement(rc_runtime_t* runtime, unsigned id, const char* memaddr, lua_State* L, int funcs_idx);
|
||||
void rc_runtime_deactivate_achievement(rc_runtime_t* runtime, unsigned id);
|
||||
rc_trigger_t* rc_runtime_get_achievement(const rc_runtime_t* runtime, unsigned id);
|
||||
int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, unsigned id, unsigned* measured_value, unsigned* measured_target);
|
||||
|
||||
int rc_runtime_activate_lboard(rc_runtime_t* runtime, unsigned id, const char* memaddr, lua_State* L, int funcs_idx);
|
||||
void rc_runtime_deactivate_lboard(rc_runtime_t* runtime, unsigned id);
|
||||
rc_lboard_t* rc_runtime_get_lboard(const rc_runtime_t* runtime, unsigned id);
|
||||
int rc_runtime_format_lboard_value(char* buffer, int size, int value, int format);
|
||||
|
||||
|
||||
int rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script, lua_State* L, int funcs_idx);
|
||||
int rc_runtime_get_richpresence(const rc_runtime_t* runtime, char* buffer, unsigned buffersize, rc_runtime_peek_t peek, void* peek_ud, lua_State* L);
|
||||
|
||||
enum {
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED, /* from WAITING, PAUSED, or PRIMED to ACTIVE */
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_RESET,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED,
|
||||
RC_RUNTIME_EVENT_LBOARD_STARTED,
|
||||
RC_RUNTIME_EVENT_LBOARD_CANCELED,
|
||||
RC_RUNTIME_EVENT_LBOARD_UPDATED,
|
||||
RC_RUNTIME_EVENT_LBOARD_TRIGGERED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED,
|
||||
RC_RUNTIME_EVENT_LBOARD_DISABLED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED
|
||||
};
|
||||
|
||||
typedef struct rc_runtime_event_t {
|
||||
unsigned id;
|
||||
int value;
|
||||
char type;
|
||||
}
|
||||
rc_runtime_event_t;
|
||||
|
||||
typedef void (*rc_runtime_event_handler_t)(const rc_runtime_event_t* runtime_event);
|
||||
|
||||
void rc_runtime_do_frame(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, lua_State* L);
|
||||
void rc_runtime_reset(rc_runtime_t* runtime);
|
||||
|
||||
typedef int (*rc_runtime_validate_address_t)(unsigned address);
|
||||
void rc_runtime_validate_addresses(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_validate_address_t validate_handler);
|
||||
void rc_runtime_invalidate_address(rc_runtime_t* runtime, unsigned address);
|
||||
|
||||
int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L);
|
||||
int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L);
|
||||
int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const unsigned char* serialized, lua_State* L);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* RC_RUNTIME_H */
|
400
dep/rcheevos/include/rc_runtime_types.h
Normal file
400
dep/rcheevos/include/rc_runtime_types.h
Normal file
|
@ -0,0 +1,400 @@
|
|||
#ifndef RC_RUNTIME_TYPES_H
|
||||
#define RC_RUNTIME_TYPES_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "rc_error.h"
|
||||
|
||||
#ifndef RC_RUNTIME_H /* prevents pedantic redefiniton error */
|
||||
|
||||
typedef struct lua_State lua_State;
|
||||
|
||||
typedef struct rc_trigger_t rc_trigger_t;
|
||||
typedef struct rc_lboard_t rc_lboard_t;
|
||||
typedef struct rc_richpresence_t rc_richpresence_t;
|
||||
typedef struct rc_memref_t rc_memref_t;
|
||||
typedef struct rc_value_t rc_value_t;
|
||||
|
||||
#endif
|
||||
|
||||
/*****************************************************************************\
|
||||
| Callbacks |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Callback used to read num_bytes bytes from memory starting at address. If
|
||||
* num_bytes is greater than 1, the value is read in little-endian from
|
||||
* memory.
|
||||
*/
|
||||
typedef unsigned (*rc_peek_t)(unsigned address, unsigned num_bytes, void* ud);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Memory References |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* Sizes. */
|
||||
enum {
|
||||
RC_MEMSIZE_8_BITS,
|
||||
RC_MEMSIZE_16_BITS,
|
||||
RC_MEMSIZE_24_BITS,
|
||||
RC_MEMSIZE_32_BITS,
|
||||
RC_MEMSIZE_LOW,
|
||||
RC_MEMSIZE_HIGH,
|
||||
RC_MEMSIZE_BIT_0,
|
||||
RC_MEMSIZE_BIT_1,
|
||||
RC_MEMSIZE_BIT_2,
|
||||
RC_MEMSIZE_BIT_3,
|
||||
RC_MEMSIZE_BIT_4,
|
||||
RC_MEMSIZE_BIT_5,
|
||||
RC_MEMSIZE_BIT_6,
|
||||
RC_MEMSIZE_BIT_7,
|
||||
RC_MEMSIZE_BITCOUNT,
|
||||
RC_MEMSIZE_VARIABLE
|
||||
};
|
||||
|
||||
typedef struct rc_memref_value_t {
|
||||
/* The current value of this memory reference. */
|
||||
unsigned value;
|
||||
/* The last differing value of this memory reference. */
|
||||
unsigned prior;
|
||||
|
||||
/* The size of the value. */
|
||||
char size;
|
||||
/* True if the value changed this frame. */
|
||||
char changed;
|
||||
/* True if the reference will be used in indirection.
|
||||
* NOTE: This is actually a property of the rc_memref_t, but we put it here to save space */
|
||||
char is_indirect;
|
||||
}
|
||||
rc_memref_value_t;
|
||||
|
||||
struct rc_memref_t {
|
||||
/* The current value at the specified memory address. */
|
||||
rc_memref_value_t value;
|
||||
|
||||
/* The memory address of this variable. */
|
||||
unsigned address;
|
||||
|
||||
/* The next memory reference in the chain. */
|
||||
rc_memref_t* next;
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
| Operands |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* types */
|
||||
enum {
|
||||
RC_OPERAND_ADDRESS, /* The value of a live address in RAM. */
|
||||
RC_OPERAND_DELTA, /* The value last known at this address. */
|
||||
RC_OPERAND_CONST, /* A 32-bit unsigned integer. */
|
||||
RC_OPERAND_FP, /* A floating point value. */
|
||||
RC_OPERAND_LUA, /* A Lua function that provides the value. */
|
||||
RC_OPERAND_PRIOR, /* The last differing value at this address. */
|
||||
RC_OPERAND_BCD, /* The BCD-decoded value of a live address in RAM. */
|
||||
RC_OPERAND_INVERTED /* The twos-complement value of a live address in RAM. */
|
||||
};
|
||||
|
||||
typedef struct rc_operand_t {
|
||||
union {
|
||||
/* A value read from memory. */
|
||||
rc_memref_t* memref;
|
||||
|
||||
/* An integer value. */
|
||||
unsigned num;
|
||||
|
||||
/* A floating point value. */
|
||||
double dbl;
|
||||
|
||||
/* A reference to the Lua function that provides the value. */
|
||||
int luafunc;
|
||||
} value;
|
||||
|
||||
/* specifies which member of the value union is being used */
|
||||
char type;
|
||||
|
||||
/* the actual RC_MEMSIZE of the operand - memref.size may differ */
|
||||
char size;
|
||||
}
|
||||
rc_operand_t;
|
||||
|
||||
int rc_operand_is_memref(rc_operand_t* operand);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Conditions |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* types */
|
||||
enum {
|
||||
/* NOTE: this enum is ordered to optimize the switch statements in rc_test_condset_internal. the values may change between releases */
|
||||
|
||||
/* non-combining conditions (third switch) */
|
||||
RC_CONDITION_STANDARD, /* this should always be 0 */
|
||||
RC_CONDITION_PAUSE_IF,
|
||||
RC_CONDITION_RESET_IF,
|
||||
RC_CONDITION_MEASURED_IF,
|
||||
RC_CONDITION_TRIGGER,
|
||||
RC_CONDITION_MEASURED, /* measured also appears in the first switch, so place it at the border between them */
|
||||
|
||||
/* modifiers (first switch) */
|
||||
RC_CONDITION_ADD_SOURCE, /* everything from this point on affects the condition after it */
|
||||
RC_CONDITION_SUB_SOURCE,
|
||||
RC_CONDITION_ADD_ADDRESS,
|
||||
|
||||
/* logic flags (second switch) */
|
||||
RC_CONDITION_ADD_HITS,
|
||||
RC_CONDITION_SUB_HITS,
|
||||
RC_CONDITION_RESET_NEXT_IF,
|
||||
RC_CONDITION_AND_NEXT,
|
||||
RC_CONDITION_OR_NEXT
|
||||
};
|
||||
|
||||
/* operators */
|
||||
enum {
|
||||
RC_OPERATOR_EQ,
|
||||
RC_OPERATOR_LT,
|
||||
RC_OPERATOR_LE,
|
||||
RC_OPERATOR_GT,
|
||||
RC_OPERATOR_GE,
|
||||
RC_OPERATOR_NE,
|
||||
RC_OPERATOR_NONE,
|
||||
RC_OPERATOR_MULT,
|
||||
RC_OPERATOR_DIV,
|
||||
RC_OPERATOR_AND
|
||||
};
|
||||
|
||||
typedef struct rc_condition_t rc_condition_t;
|
||||
|
||||
struct rc_condition_t {
|
||||
/* The condition's operands. */
|
||||
rc_operand_t operand1;
|
||||
rc_operand_t operand2;
|
||||
|
||||
/* Required hits to fire this condition. */
|
||||
unsigned required_hits;
|
||||
/* Number of hits so far. */
|
||||
unsigned current_hits;
|
||||
|
||||
/* The next condition in the chain. */
|
||||
rc_condition_t* next;
|
||||
|
||||
/* The type of the condition. */
|
||||
char type;
|
||||
|
||||
/* The comparison operator to use. */
|
||||
char oper; /* operator is a reserved word in C++. */
|
||||
|
||||
/* Set if the condition needs to processed as part of the "check if paused" pass. */
|
||||
char pause;
|
||||
|
||||
/* Whether or not the condition evaluated true on the last check */
|
||||
char is_true;
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
| Condition sets |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_condset_t rc_condset_t;
|
||||
|
||||
struct rc_condset_t {
|
||||
/* The next condition set in the chain. */
|
||||
rc_condset_t* next;
|
||||
|
||||
/* The list of conditions in this condition set. */
|
||||
rc_condition_t* conditions;
|
||||
|
||||
/* True if any condition in the set is a pause condition. */
|
||||
char has_pause;
|
||||
|
||||
/* True if the set is currently paused. */
|
||||
char is_paused;
|
||||
|
||||
/* True if the set has indirect memory references. */
|
||||
char has_indirect_memrefs;
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
| Trigger |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_TRIGGER_STATE_INACTIVE, /* achievement is not being processed */
|
||||
RC_TRIGGER_STATE_WAITING, /* achievement cannot trigger until it has been false for at least one frame */
|
||||
RC_TRIGGER_STATE_ACTIVE, /* achievement is active and may trigger */
|
||||
RC_TRIGGER_STATE_PAUSED, /* achievement is currently paused and will not trigger */
|
||||
RC_TRIGGER_STATE_RESET, /* achievement hit counts were reset */
|
||||
RC_TRIGGER_STATE_TRIGGERED, /* achievement has triggered */
|
||||
RC_TRIGGER_STATE_PRIMED, /* all non-Trigger conditions are true */
|
||||
RC_TRIGGER_STATE_DISABLED /* achievement cannot be processed at this time */
|
||||
};
|
||||
|
||||
struct rc_trigger_t {
|
||||
/* The main condition set. */
|
||||
rc_condset_t* requirement;
|
||||
|
||||
/* The list of sub condition sets in this test. */
|
||||
rc_condset_t* alternative;
|
||||
|
||||
/* The memory references required by the trigger. */
|
||||
rc_memref_t* memrefs;
|
||||
|
||||
/* The current state of the MEASURED condition. */
|
||||
unsigned measured_value;
|
||||
|
||||
/* The target state of the MEASURED condition */
|
||||
unsigned measured_target;
|
||||
|
||||
/* The current state of the trigger */
|
||||
char state;
|
||||
|
||||
/* True if at least one condition has a non-zero hit count */
|
||||
char has_hits;
|
||||
|
||||
/* True if at least one condition has a non-zero required hit count */
|
||||
char has_required_hits;
|
||||
};
|
||||
|
||||
int rc_trigger_size(const char* memaddr);
|
||||
rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
int rc_evaluate_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L);
|
||||
int rc_test_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L);
|
||||
void rc_reset_trigger(rc_trigger_t* self);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Values |
|
||||
\*****************************************************************************/
|
||||
|
||||
struct rc_value_t {
|
||||
/* The current value of the variable. */
|
||||
rc_memref_value_t value;
|
||||
|
||||
/* The list of conditions to evaluate. */
|
||||
rc_condset_t* conditions;
|
||||
|
||||
/* The memory references required by the value. */
|
||||
rc_memref_t* memrefs;
|
||||
|
||||
/* The name of the variable. */
|
||||
const char* name;
|
||||
|
||||
/* The next variable in the chain. */
|
||||
rc_value_t* next;
|
||||
};
|
||||
|
||||
int rc_value_size(const char* memaddr);
|
||||
rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
int rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, lua_State* L);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Leaderboards |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* Return values for rc_evaluate_lboard. */
|
||||
enum {
|
||||
RC_LBOARD_STATE_INACTIVE, /* leaderboard is not being processed */
|
||||
RC_LBOARD_STATE_WAITING, /* leaderboard cannot activate until the start condition has been false for at least one frame */
|
||||
RC_LBOARD_STATE_ACTIVE, /* leaderboard is active and may start */
|
||||
RC_LBOARD_STATE_STARTED, /* leaderboard attempt in progress */
|
||||
RC_LBOARD_STATE_CANCELED, /* leaderboard attempt canceled */
|
||||
RC_LBOARD_STATE_TRIGGERED, /* leaderboard attempt complete, value should be submitted */
|
||||
RC_LBOARD_STATE_DISABLED /* leaderboard cannot be processed at this time */
|
||||
};
|
||||
|
||||
struct rc_lboard_t {
|
||||
rc_trigger_t start;
|
||||
rc_trigger_t submit;
|
||||
rc_trigger_t cancel;
|
||||
rc_value_t value;
|
||||
rc_value_t* progress;
|
||||
rc_memref_t* memrefs;
|
||||
|
||||
char state;
|
||||
};
|
||||
|
||||
int rc_lboard_size(const char* memaddr);
|
||||
rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
int rc_evaluate_lboard(rc_lboard_t* lboard, int* value, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
void rc_reset_lboard(rc_lboard_t* lboard);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Value formatting |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* Supported formats. */
|
||||
enum {
|
||||
RC_FORMAT_FRAMES,
|
||||
RC_FORMAT_SECONDS,
|
||||
RC_FORMAT_CENTISECS,
|
||||
RC_FORMAT_SCORE,
|
||||
RC_FORMAT_VALUE,
|
||||
RC_FORMAT_MINUTES,
|
||||
RC_FORMAT_SECONDS_AS_MINUTES
|
||||
};
|
||||
|
||||
int rc_parse_format(const char* format_str);
|
||||
int rc_format_value(char* buffer, int size, int value, int format);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Rich Presence |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_richpresence_lookup_item_t rc_richpresence_lookup_item_t;
|
||||
|
||||
struct rc_richpresence_lookup_item_t {
|
||||
unsigned first;
|
||||
unsigned last;
|
||||
rc_richpresence_lookup_item_t* left;
|
||||
rc_richpresence_lookup_item_t* right;
|
||||
const char* label;
|
||||
};
|
||||
|
||||
typedef struct rc_richpresence_lookup_t rc_richpresence_lookup_t;
|
||||
|
||||
struct rc_richpresence_lookup_t {
|
||||
rc_richpresence_lookup_item_t* root;
|
||||
rc_richpresence_lookup_t* next;
|
||||
const char* name;
|
||||
const char* default_label;
|
||||
unsigned short format;
|
||||
};
|
||||
|
||||
typedef struct rc_richpresence_display_part_t rc_richpresence_display_part_t;
|
||||
|
||||
struct rc_richpresence_display_part_t {
|
||||
rc_richpresence_display_part_t* next;
|
||||
const char* text;
|
||||
rc_richpresence_lookup_t* lookup;
|
||||
rc_memref_value_t *value;
|
||||
unsigned short display_type;
|
||||
};
|
||||
|
||||
typedef struct rc_richpresence_display_t rc_richpresence_display_t;
|
||||
|
||||
struct rc_richpresence_display_t {
|
||||
rc_trigger_t trigger;
|
||||
rc_richpresence_display_t* next;
|
||||
rc_richpresence_display_part_t* display;
|
||||
};
|
||||
|
||||
struct rc_richpresence_t {
|
||||
rc_richpresence_display_t* first_display;
|
||||
rc_richpresence_lookup_t* first_lookup;
|
||||
rc_memref_t* memrefs;
|
||||
rc_value_t* variables;
|
||||
};
|
||||
|
||||
int rc_richpresence_size(const char* script);
|
||||
int rc_richpresence_size_lines(const char* script, int* lines_read);
|
||||
rc_richpresence_t* rc_parse_richpresence(void* buffer, const char* script, lua_State* L, int funcs_ndx);
|
||||
int rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, unsigned buffersize, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
void rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
int rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, unsigned buffersize, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* RC_RUNTIME_TYPES_H */
|
|
@ -7,11 +7,9 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned cheevo_id,
|
||||
int hardcore, const char* game_hash);
|
||||
int rc_url_award_cheevo(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned cheevo_id, int hardcore, const char* game_hash);
|
||||
|
||||
int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id,
|
||||
int value);
|
||||
int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value);
|
||||
|
||||
int rc_url_get_gameid(char* buffer, size_t size, const char* hash);
|
||||
|
||||
|
@ -23,8 +21,7 @@ int rc_url_login_with_password(char* buffer, size_t size, const char* user_name,
|
|||
|
||||
int rc_url_login_with_token(char* buffer, size_t size, const char* user_name, const char* login_token);
|
||||
|
||||
int rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid,
|
||||
int hardcore);
|
||||
int rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid, int hardcore);
|
||||
|
||||
int rc_url_post_playing(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid);
|
||||
|
||||
|
@ -32,13 +29,12 @@ int rc_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, siz
|
|||
const char* user_name, const char* login_token, unsigned gameid, const char* rich_presence);
|
||||
|
||||
// Custom exports, static in upstream rcheevos
|
||||
int rc_url_build_dorequest(char* url_buffer, size_t url_buffer_size, size_t* buffer_offset, const char* api, const char* user_name);
|
||||
|
||||
int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, unsigned value);
|
||||
|
||||
int rc_url_append_str(char* buffer, size_t buffer_size, size_t* buffer_offset, const char* param, const char* value);
|
||||
|
||||
int rc_url_build_dorequest(char* url_buffer, size_t url_buffer_size, size_t* buffer_offset, const char* api,
|
||||
const char* user_name);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,546 +1,8 @@
|
|||
#ifndef RCHEEVOS_H
|
||||
#define RCHEEVOS_H
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
typedef struct lua_State lua_State;
|
||||
|
||||
/*****************************************************************************\
|
||||
| Return values |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_OK = 0,
|
||||
RC_INVALID_LUA_OPERAND = -1,
|
||||
RC_INVALID_MEMORY_OPERAND = -2,
|
||||
RC_INVALID_CONST_OPERAND = -3,
|
||||
RC_INVALID_FP_OPERAND = -4,
|
||||
RC_INVALID_CONDITION_TYPE = -5,
|
||||
RC_INVALID_OPERATOR = -6,
|
||||
RC_INVALID_REQUIRED_HITS = -7,
|
||||
RC_DUPLICATED_START = -8,
|
||||
RC_DUPLICATED_CANCEL = -9,
|
||||
RC_DUPLICATED_SUBMIT = -10,
|
||||
RC_DUPLICATED_VALUE = -11,
|
||||
RC_DUPLICATED_PROGRESS = -12,
|
||||
RC_MISSING_START = -13,
|
||||
RC_MISSING_CANCEL = -14,
|
||||
RC_MISSING_SUBMIT = -15,
|
||||
RC_MISSING_VALUE = -16,
|
||||
RC_INVALID_LBOARD_FIELD = -17,
|
||||
RC_MISSING_DISPLAY_STRING = -18,
|
||||
RC_OUT_OF_MEMORY = -19,
|
||||
RC_INVALID_VALUE_FLAG = -20,
|
||||
RC_MISSING_VALUE_MEASURED = -21,
|
||||
RC_MULTIPLE_MEASURED = -22,
|
||||
RC_INVALID_MEASURED_TARGET = -23,
|
||||
RC_INVALID_COMPARISON = -24,
|
||||
RC_INVALID_STATE = -25
|
||||
};
|
||||
|
||||
const char* rc_error_str(int ret);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Callbacks |
|
||||
\*****************************************************************************/
|
||||
|
||||
/**
|
||||
* Callback used to read num_bytes bytes from memory starting at address. If
|
||||
* num_bytes is greater than 1, the value is read in little-endian from
|
||||
* memory.
|
||||
*/
|
||||
typedef unsigned (*rc_peek_t)(unsigned address, unsigned num_bytes, void* ud);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Memory References |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* Sizes. */
|
||||
enum {
|
||||
RC_MEMSIZE_8_BITS,
|
||||
RC_MEMSIZE_16_BITS,
|
||||
RC_MEMSIZE_24_BITS,
|
||||
RC_MEMSIZE_32_BITS,
|
||||
RC_MEMSIZE_LOW,
|
||||
RC_MEMSIZE_HIGH,
|
||||
RC_MEMSIZE_BIT_0,
|
||||
RC_MEMSIZE_BIT_1,
|
||||
RC_MEMSIZE_BIT_2,
|
||||
RC_MEMSIZE_BIT_3,
|
||||
RC_MEMSIZE_BIT_4,
|
||||
RC_MEMSIZE_BIT_5,
|
||||
RC_MEMSIZE_BIT_6,
|
||||
RC_MEMSIZE_BIT_7,
|
||||
RC_MEMSIZE_BITCOUNT,
|
||||
RC_MEMSIZE_VARIABLE
|
||||
};
|
||||
|
||||
typedef struct rc_memref_value_t {
|
||||
/* The current value of this memory reference. */
|
||||
unsigned value;
|
||||
/* The last differing value of this memory reference. */
|
||||
unsigned prior;
|
||||
|
||||
/* The size of the value. */
|
||||
char size;
|
||||
/* True if the value changed this frame. */
|
||||
char changed;
|
||||
/* True if the reference will be used in indirection.
|
||||
* NOTE: This is actually a property of the rc_memref_t, but we put it here to save space */
|
||||
char is_indirect;
|
||||
} rc_memref_value_t;
|
||||
|
||||
typedef struct rc_memref_t rc_memref_t;
|
||||
|
||||
struct rc_memref_t {
|
||||
/* The current value at the specified memory address. */
|
||||
rc_memref_value_t value;
|
||||
|
||||
/* The memory address of this variable. */
|
||||
unsigned address;
|
||||
|
||||
/* The next memory reference in the chain. */
|
||||
rc_memref_t* next;
|
||||
};
|
||||
|
||||
|
||||
/*****************************************************************************\
|
||||
| Operands |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* types */
|
||||
enum {
|
||||
RC_OPERAND_ADDRESS, /* The value of a live address in RAM. */
|
||||
RC_OPERAND_DELTA, /* The value last known at this address. */
|
||||
RC_OPERAND_CONST, /* A 32-bit unsigned integer. */
|
||||
RC_OPERAND_FP, /* A floating point value. */
|
||||
RC_OPERAND_LUA, /* A Lua function that provides the value. */
|
||||
RC_OPERAND_PRIOR, /* The last differing value at this address. */
|
||||
RC_OPERAND_BCD, /* The BCD-decoded value of a live address in RAM. */
|
||||
RC_OPERAND_INVERTED /* The twos-complement value of a live address in RAM. */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
union {
|
||||
/* A value read from memory. */
|
||||
rc_memref_t* memref;
|
||||
|
||||
/* An integer value. */
|
||||
unsigned num;
|
||||
|
||||
/* A floating point value. */
|
||||
double dbl;
|
||||
|
||||
/* A reference to the Lua function that provides the value. */
|
||||
int luafunc;
|
||||
} value;
|
||||
|
||||
/* specifies which member of the value union is being used */
|
||||
char type;
|
||||
|
||||
/* the actual RC_MEMSIZE of the operand - memref.size may differ */
|
||||
char size;
|
||||
}
|
||||
rc_operand_t;
|
||||
|
||||
/*****************************************************************************\
|
||||
| Conditions |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* types */
|
||||
enum {
|
||||
RC_CONDITION_STANDARD,
|
||||
RC_CONDITION_PAUSE_IF,
|
||||
RC_CONDITION_RESET_IF,
|
||||
RC_CONDITION_ADD_SOURCE,
|
||||
RC_CONDITION_SUB_SOURCE,
|
||||
RC_CONDITION_ADD_HITS,
|
||||
RC_CONDITION_AND_NEXT,
|
||||
RC_CONDITION_MEASURED,
|
||||
RC_CONDITION_ADD_ADDRESS,
|
||||
RC_CONDITION_OR_NEXT,
|
||||
RC_CONDITION_TRIGGER,
|
||||
RC_CONDITION_MEASURED_IF,
|
||||
RC_CONDITION_RESET_NEXT_IF,
|
||||
RC_CONDITION_SUB_HITS
|
||||
};
|
||||
|
||||
/* operators */
|
||||
enum {
|
||||
RC_OPERATOR_EQ,
|
||||
RC_OPERATOR_LT,
|
||||
RC_OPERATOR_LE,
|
||||
RC_OPERATOR_GT,
|
||||
RC_OPERATOR_GE,
|
||||
RC_OPERATOR_NE,
|
||||
RC_OPERATOR_NONE,
|
||||
RC_OPERATOR_MULT,
|
||||
RC_OPERATOR_DIV,
|
||||
RC_OPERATOR_AND
|
||||
};
|
||||
|
||||
typedef struct rc_condition_t rc_condition_t;
|
||||
|
||||
struct rc_condition_t {
|
||||
/* The condition's operands. */
|
||||
rc_operand_t operand1;
|
||||
rc_operand_t operand2;
|
||||
|
||||
/* Required hits to fire this condition. */
|
||||
unsigned required_hits;
|
||||
/* Number of hits so far. */
|
||||
unsigned current_hits;
|
||||
|
||||
/* The next condition in the chain. */
|
||||
rc_condition_t* next;
|
||||
|
||||
/* The type of the condition. */
|
||||
char type;
|
||||
|
||||
/* The comparison operator to use. */
|
||||
char oper; /* operator is a reserved word in C++. */
|
||||
|
||||
/* Set if the condition needs to processed as part of the "check if paused" pass. */
|
||||
char pause;
|
||||
|
||||
/* Whether or not the condition evaluated true on the last check */
|
||||
char is_true;
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
| Condition sets |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_condset_t rc_condset_t;
|
||||
|
||||
struct rc_condset_t {
|
||||
/* The next condition set in the chain. */
|
||||
rc_condset_t* next;
|
||||
|
||||
/* The list of conditions in this condition set. */
|
||||
rc_condition_t* conditions;
|
||||
|
||||
/* True if any condition in the set is a pause condition. */
|
||||
char has_pause;
|
||||
|
||||
/* True if the set is currently paused. */
|
||||
char is_paused;
|
||||
};
|
||||
|
||||
/*****************************************************************************\
|
||||
| Trigger |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_TRIGGER_STATE_INACTIVE, /* achievement is not being processed */
|
||||
RC_TRIGGER_STATE_WAITING, /* achievement cannot trigger until it has been false for at least one frame */
|
||||
RC_TRIGGER_STATE_ACTIVE, /* achievement is active and may trigger */
|
||||
RC_TRIGGER_STATE_PAUSED, /* achievement is currently paused and will not trigger */
|
||||
RC_TRIGGER_STATE_RESET, /* achievement hit counts were reset */
|
||||
RC_TRIGGER_STATE_TRIGGERED, /* achievement has triggered */
|
||||
RC_TRIGGER_STATE_PRIMED, /* all non-Trigger conditions are true */
|
||||
RC_TRIGGER_STATE_DISABLED /* achievement cannot be processed at this time */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
/* The main condition set. */
|
||||
rc_condset_t* requirement;
|
||||
|
||||
/* The list of sub condition sets in this test. */
|
||||
rc_condset_t* alternative;
|
||||
|
||||
/* The memory references required by the trigger. */
|
||||
rc_memref_t* memrefs;
|
||||
|
||||
/* The current state of the MEASURED condition. */
|
||||
unsigned measured_value;
|
||||
|
||||
/* The target state of the MEASURED condition */
|
||||
unsigned measured_target;
|
||||
|
||||
/* The current state of the trigger */
|
||||
char state;
|
||||
|
||||
/* True if at least one condition has a non-zero hit count */
|
||||
char has_hits;
|
||||
|
||||
/* True if at least one condition has a non-zero required hit count */
|
||||
char has_required_hits;
|
||||
}
|
||||
rc_trigger_t;
|
||||
|
||||
int rc_trigger_size(const char* memaddr);
|
||||
rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
int rc_evaluate_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L);
|
||||
int rc_test_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L);
|
||||
void rc_reset_trigger(rc_trigger_t* self);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Values |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_value_t rc_value_t;
|
||||
|
||||
struct rc_value_t {
|
||||
/* The current value of the variable. */
|
||||
rc_memref_value_t value;
|
||||
|
||||
/* The list of conditions to evaluate. */
|
||||
rc_condset_t* conditions;
|
||||
|
||||
/* The memory references required by the value. */
|
||||
rc_memref_t* memrefs;
|
||||
|
||||
/* The name of the variable. */
|
||||
const char* name;
|
||||
|
||||
/* The next variable in the chain. */
|
||||
rc_value_t* next;
|
||||
};
|
||||
|
||||
int rc_value_size(const char* memaddr);
|
||||
rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
int rc_evaluate_value(rc_value_t* value, rc_peek_t peek, void* ud, lua_State* L);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Leaderboards |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* Return values for rc_evaluate_lboard. */
|
||||
enum {
|
||||
RC_LBOARD_STATE_INACTIVE, /* leaderboard is not being processed */
|
||||
RC_LBOARD_STATE_WAITING, /* leaderboard cannot activate until the start condition has been false for at least one frame */
|
||||
RC_LBOARD_STATE_ACTIVE, /* leaderboard is active and may start */
|
||||
RC_LBOARD_STATE_STARTED, /* leaderboard attempt in progress */
|
||||
RC_LBOARD_STATE_CANCELED, /* leaderboard attempt canceled */
|
||||
RC_LBOARD_STATE_TRIGGERED, /* leaderboard attempt complete, value should be submitted */
|
||||
RC_LBOARD_STATE_DISABLED /* leaderboard cannot be processed at this time */
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
rc_trigger_t start;
|
||||
rc_trigger_t submit;
|
||||
rc_trigger_t cancel;
|
||||
rc_value_t value;
|
||||
rc_value_t* progress;
|
||||
rc_memref_t* memrefs;
|
||||
|
||||
char state;
|
||||
}
|
||||
rc_lboard_t;
|
||||
|
||||
int rc_lboard_size(const char* memaddr);
|
||||
rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
|
||||
int rc_evaluate_lboard(rc_lboard_t* lboard, int* value, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
void rc_reset_lboard(rc_lboard_t* lboard);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Value formatting |
|
||||
\*****************************************************************************/
|
||||
|
||||
/* Supported formats. */
|
||||
enum {
|
||||
RC_FORMAT_FRAMES,
|
||||
RC_FORMAT_SECONDS,
|
||||
RC_FORMAT_CENTISECS,
|
||||
RC_FORMAT_SCORE,
|
||||
RC_FORMAT_VALUE,
|
||||
RC_FORMAT_MINUTES,
|
||||
RC_FORMAT_SECONDS_AS_MINUTES
|
||||
};
|
||||
|
||||
int rc_parse_format(const char* format_str);
|
||||
int rc_format_value(char* buffer, int size, int value, int format);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Rich Presence |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_richpresence_lookup_item_t rc_richpresence_lookup_item_t;
|
||||
|
||||
struct rc_richpresence_lookup_item_t {
|
||||
unsigned first;
|
||||
unsigned last;
|
||||
rc_richpresence_lookup_item_t* left;
|
||||
rc_richpresence_lookup_item_t* right;
|
||||
const char* label;
|
||||
};
|
||||
|
||||
typedef struct rc_richpresence_lookup_t rc_richpresence_lookup_t;
|
||||
|
||||
struct rc_richpresence_lookup_t {
|
||||
rc_richpresence_lookup_item_t* root;
|
||||
rc_richpresence_lookup_t* next;
|
||||
const char* name;
|
||||
const char* default_label;
|
||||
unsigned short format;
|
||||
};
|
||||
|
||||
typedef struct rc_richpresence_display_part_t rc_richpresence_display_part_t;
|
||||
|
||||
struct rc_richpresence_display_part_t {
|
||||
rc_richpresence_display_part_t* next;
|
||||
const char* text;
|
||||
rc_richpresence_lookup_t* lookup;
|
||||
rc_memref_value_t *value;
|
||||
unsigned short display_type;
|
||||
};
|
||||
|
||||
typedef struct rc_richpresence_display_t rc_richpresence_display_t;
|
||||
|
||||
struct rc_richpresence_display_t {
|
||||
rc_trigger_t trigger;
|
||||
rc_richpresence_display_t* next;
|
||||
rc_richpresence_display_part_t* display;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
rc_richpresence_display_t* first_display;
|
||||
rc_richpresence_lookup_t* first_lookup;
|
||||
rc_memref_t* memrefs;
|
||||
rc_value_t* variables;
|
||||
}
|
||||
rc_richpresence_t;
|
||||
|
||||
int rc_richpresence_size(const char* script);
|
||||
rc_richpresence_t* rc_parse_richpresence(void* buffer, const char* script, lua_State* L, int funcs_ndx);
|
||||
int rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, unsigned buffersize, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
void rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
int rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, unsigned buffersize, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Runtime |
|
||||
\*****************************************************************************/
|
||||
|
||||
typedef struct rc_runtime_trigger_t {
|
||||
unsigned id;
|
||||
rc_trigger_t* trigger;
|
||||
void* buffer;
|
||||
rc_memref_t* invalid_memref;
|
||||
unsigned char md5[16];
|
||||
int serialized_size;
|
||||
char owns_memrefs;
|
||||
}
|
||||
rc_runtime_trigger_t;
|
||||
|
||||
typedef struct rc_runtime_lboard_t {
|
||||
unsigned id;
|
||||
int value;
|
||||
rc_lboard_t* lboard;
|
||||
void* buffer;
|
||||
rc_memref_t* invalid_memref;
|
||||
unsigned char md5[16];
|
||||
char owns_memrefs;
|
||||
}
|
||||
rc_runtime_lboard_t;
|
||||
|
||||
typedef struct rc_runtime_richpresence_t {
|
||||
rc_richpresence_t* richpresence;
|
||||
void* buffer;
|
||||
struct rc_runtime_richpresence_t* previous;
|
||||
char owns_memrefs;
|
||||
}
|
||||
rc_runtime_richpresence_t;
|
||||
|
||||
typedef struct rc_runtime_t {
|
||||
rc_runtime_trigger_t* triggers;
|
||||
unsigned trigger_count;
|
||||
unsigned trigger_capacity;
|
||||
|
||||
rc_runtime_lboard_t* lboards;
|
||||
unsigned lboard_count;
|
||||
unsigned lboard_capacity;
|
||||
|
||||
rc_runtime_richpresence_t* richpresence;
|
||||
|
||||
rc_memref_t* memrefs;
|
||||
rc_memref_t** next_memref;
|
||||
|
||||
rc_value_t* variables;
|
||||
rc_value_t** next_variable;
|
||||
}
|
||||
rc_runtime_t;
|
||||
|
||||
void rc_runtime_init(rc_runtime_t* runtime);
|
||||
void rc_runtime_destroy(rc_runtime_t* runtime);
|
||||
|
||||
int rc_runtime_activate_achievement(rc_runtime_t* runtime, unsigned id, const char* memaddr, lua_State* L, int funcs_idx);
|
||||
void rc_runtime_deactivate_achievement(rc_runtime_t* runtime, unsigned id);
|
||||
rc_trigger_t* rc_runtime_get_achievement(const rc_runtime_t* runtime, unsigned id);
|
||||
|
||||
int rc_runtime_activate_lboard(rc_runtime_t* runtime, unsigned id, const char* memaddr, lua_State* L, int funcs_idx);
|
||||
void rc_runtime_deactivate_lboard(rc_runtime_t* runtime, unsigned id);
|
||||
rc_lboard_t* rc_runtime_get_lboard(const rc_runtime_t* runtime, unsigned id);
|
||||
|
||||
int rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script, lua_State* L, int funcs_idx);
|
||||
int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, unsigned buffersize, rc_peek_t peek, void* peek_ud, lua_State* L);
|
||||
|
||||
enum {
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED, /* from WAITING, PAUSED, or PRIMED to ACTIVE */
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_RESET,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED,
|
||||
RC_RUNTIME_EVENT_LBOARD_STARTED,
|
||||
RC_RUNTIME_EVENT_LBOARD_CANCELED,
|
||||
RC_RUNTIME_EVENT_LBOARD_UPDATED,
|
||||
RC_RUNTIME_EVENT_LBOARD_TRIGGERED,
|
||||
RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED,
|
||||
RC_RUNTIME_EVENT_LBOARD_DISABLED
|
||||
};
|
||||
|
||||
typedef struct rc_runtime_event_t {
|
||||
unsigned id;
|
||||
int value;
|
||||
char type;
|
||||
}
|
||||
rc_runtime_event_t;
|
||||
|
||||
typedef void (*rc_runtime_event_handler_t)(const rc_runtime_event_t* runtime_event);
|
||||
|
||||
void rc_runtime_do_frame(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_peek_t peek, void* ud, lua_State* L);
|
||||
void rc_runtime_reset(rc_runtime_t* runtime);
|
||||
void rc_runtime_invalidate_address(rc_runtime_t* runtime, unsigned address);
|
||||
|
||||
int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L);
|
||||
int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L);
|
||||
int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const unsigned char* serialized, lua_State* L);
|
||||
|
||||
/*****************************************************************************\
|
||||
| Memory mapping |
|
||||
\*****************************************************************************/
|
||||
|
||||
enum {
|
||||
RC_MEMORY_TYPE_SYSTEM_RAM, /* normal system memory */
|
||||
RC_MEMORY_TYPE_SAVE_RAM, /* memory that persists between sessions */
|
||||
RC_MEMORY_TYPE_VIDEO_RAM, /* memory reserved for graphical processing */
|
||||
RC_MEMORY_TYPE_READONLY, /* memory that maps to read only data */
|
||||
RC_MEMORY_TYPE_HARDWARE_CONTROLLER, /* memory for interacting with system components */
|
||||
RC_MEMORY_TYPE_VIRTUAL_RAM, /* secondary address space that maps to real memory in system RAM */
|
||||
RC_MEMORY_TYPE_UNUSED /* these addresses don't really exist */
|
||||
};
|
||||
|
||||
typedef struct rc_memory_region_t {
|
||||
unsigned start_address; /* first address of block as queried by RetroAchievements */
|
||||
unsigned end_address; /* last address of block as queried by RetroAchievements */
|
||||
unsigned real_address; /* real address for first address of block */
|
||||
char type; /* RC_MEMORY_TYPE_ for block */
|
||||
const char* description; /* short description of block */
|
||||
}
|
||||
rc_memory_region_t;
|
||||
|
||||
typedef struct rc_memory_regions_t {
|
||||
const rc_memory_region_t* region;
|
||||
unsigned num_regions;
|
||||
}
|
||||
rc_memory_regions_t;
|
||||
|
||||
const rc_memory_regions_t* rc_console_memory_regions(int console_id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
#include "rc_runtime.h"
|
||||
#include "rc_runtime_types.h"
|
||||
#include "rc_consoles.h"
|
||||
|
||||
#endif /* RCHEEVOS_H */
|
||||
|
|
|
@ -73,7 +73,10 @@
|
|||
<ItemGroup>
|
||||
<ClInclude Include="include\rcheevos.h" />
|
||||
<ClInclude Include="include\rc_consoles.h" />
|
||||
<ClInclude Include="include\rc_error.h" />
|
||||
<ClInclude Include="include\rc_hash.h" />
|
||||
<ClInclude Include="include\rc_runtime.h" />
|
||||
<ClInclude Include="include\rc_runtime_types.h" />
|
||||
<ClInclude Include="include\rc_url.h" />
|
||||
<ClInclude Include="src\rcheevos\rc_compat.h" />
|
||||
<ClInclude Include="src\rcheevos\rc_internal.h" />
|
||||
|
@ -555,4 +558,4 @@
|
|||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -15,6 +15,24 @@
|
|||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\rurl\url.c">
|
||||
<Filter>rurl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rcheevos\consoleinfo.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rcheevos\format.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rcheevos\lboard.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rcheevos\memref.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rcheevos\operand.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rcheevos\richpresence.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
|
@ -42,35 +60,38 @@
|
|||
<ClCompile Include="src\rcheevos\condset.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rcheevos\consoleinfo.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rcheevos\format.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rcheevos\lboard.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rcheevos\memref.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rcheevos\operand.c">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rurl\url.c">
|
||||
<Filter>rurl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rhash\hash.c">
|
||||
<Filter>rhash</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rhash\md5.c">
|
||||
<Filter>rhash</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rhash\cdreader.c">
|
||||
<Filter>rhash</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rhash\hash.c">
|
||||
<Filter>rhash</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="include\rc_consoles.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\rc_error.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\rc_hash.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\rc_runtime.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\rc_runtime_types.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\rc_url.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\rcheevos.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\rcheevos\rc_compat.h">
|
||||
<Filter>rcheevos</Filter>
|
||||
</ClInclude>
|
||||
|
@ -80,17 +101,5 @@
|
|||
<ClInclude Include="src\rhash\md5.h">
|
||||
<Filter>rhash</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\rc_url.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\rcheevos.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\rc_consoles.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="include\rc_hash.h">
|
||||
<Filter>include</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
|
|
|
@ -108,7 +108,7 @@ char* rc_alloc_str(rc_parse_state_t* parse, const char* text, int length) {
|
|||
next = &(*next)->right;
|
||||
}
|
||||
|
||||
*next = rc_alloc_scratch(NULL, &used, sizeof(rc_scratch_string_t), RC_ALIGNOF(rc_scratch_string_t), &parse->scratch, RC_OFFSETOF(parse->scratch.objs, __rc_scratch_string_t));
|
||||
*next = (rc_scratch_string_t*)rc_alloc_scratch(NULL, &used, sizeof(rc_scratch_string_t), RC_ALIGNOF(rc_scratch_string_t), &parse->scratch, RC_OFFSETOF(parse->scratch.objs, __rc_scratch_string_t));
|
||||
ptr = (char*)rc_alloc_scratch(parse->buffer, &parse->offset, length + 1, RC_ALIGNOF(char), &parse->scratch, -1);
|
||||
|
||||
if (!ptr || !*next) {
|
||||
|
@ -142,6 +142,7 @@ void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, in
|
|||
parse->first_memref = 0;
|
||||
parse->variables = 0;
|
||||
parse->measured_target = 0;
|
||||
parse->lines_read = 0;
|
||||
parse->has_required_hits = 0;
|
||||
}
|
||||
|
||||
|
@ -186,6 +187,7 @@ const char* rc_error_str(int ret)
|
|||
case RC_INVALID_MEASURED_TARGET: return "Invalid measured target";
|
||||
case RC_INVALID_COMPARISON: return "Invalid comparison";
|
||||
case RC_INVALID_STATE: return "Invalid state";
|
||||
case RC_INVALID_JSON: return "Invalid JSON";
|
||||
|
||||
default: return "Unknown error";
|
||||
}
|
||||
|
|
|
@ -69,6 +69,7 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
|
|||
aux = *memaddr;
|
||||
self = RC_ALLOC(rc_condition_t, parse);
|
||||
self->current_hits = 0;
|
||||
self->is_true = 0;
|
||||
|
||||
if (*aux != 0 && aux[1] == ':') {
|
||||
switch (*aux) {
|
||||
|
@ -184,7 +185,12 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
|
|||
return 0;
|
||||
}
|
||||
|
||||
parse->has_required_hits = 1;
|
||||
/* if operator is none, explicitly clear out the required hits */
|
||||
if (self->oper == RC_OPERATOR_NONE)
|
||||
self->required_hits = 0;
|
||||
else
|
||||
parse->has_required_hits = 1;
|
||||
|
||||
aux = end + 1;
|
||||
}
|
||||
else if (*aux == '.') {
|
||||
|
@ -196,7 +202,12 @@ rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse
|
|||
return 0;
|
||||
}
|
||||
|
||||
parse->has_required_hits = 1;
|
||||
/* if operator is none, explicitly clear out the required hits */
|
||||
if (self->oper == RC_OPERATOR_NONE)
|
||||
self->required_hits = 0;
|
||||
else
|
||||
parse->has_required_hits = 1;
|
||||
|
||||
aux = end + 1;
|
||||
}
|
||||
else {
|
||||
|
@ -228,10 +239,18 @@ int rc_evaluate_condition_value(rc_condition_t* self, rc_eval_state_t* eval_stat
|
|||
|
||||
switch (self->oper) {
|
||||
case RC_OPERATOR_MULT:
|
||||
if (self->operand2.type == RC_OPERAND_FP)
|
||||
if (self->operand2.type == RC_OPERAND_FP) {
|
||||
value = (int)((double)value * self->operand2.value.dbl);
|
||||
else
|
||||
}
|
||||
else {
|
||||
/* the c standard for unsigned multiplication is well defined as non-overflowing truncation
|
||||
* to the type's size. this allows negative multiplication through twos-complements. i.e.
|
||||
* 1 * -1 (0xFFFFFFFF) = 0xFFFFFFFF = -1
|
||||
* 3 * -2 (0xFFFFFFFE) = 0x2FFFFFFFA & 0xFFFFFFFF = 0xFFFFFFFA = -6
|
||||
* 10 * -5 (0xFFFFFFFB) = 0x9FFFFFFCE & 0xFFFFFFFF = 0xFFFFFFCE = -50
|
||||
*/
|
||||
value *= rc_evaluate_operand(&self->operand2, eval_state);
|
||||
}
|
||||
break;
|
||||
|
||||
case RC_OPERATOR_DIV:
|
||||
|
|
|
@ -17,6 +17,7 @@ static void rc_update_condition_pause(rc_condition_t* condition, int* in_pause)
|
|||
case RC_CONDITION_AND_NEXT:
|
||||
case RC_CONDITION_OR_NEXT:
|
||||
case RC_CONDITION_ADD_ADDRESS:
|
||||
case RC_CONDITION_RESET_NEXT_IF:
|
||||
condition->pause = *in_pause;
|
||||
break;
|
||||
|
||||
|
@ -34,7 +35,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
|
|||
unsigned measured_target = 0;
|
||||
|
||||
self = RC_ALLOC(rc_condset_t, parse);
|
||||
self->has_pause = self->is_paused = 0;
|
||||
self->has_pause = self->is_paused = self->has_indirect_memrefs = 0;
|
||||
next = &self->conditions;
|
||||
|
||||
if (**memaddr == 'S' || **memaddr == 's' || !**memaddr) {
|
||||
|
@ -54,15 +55,13 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
|
|||
if ((*next)->oper == RC_OPERATOR_NONE) {
|
||||
switch ((*next)->type) {
|
||||
case RC_CONDITION_ADD_ADDRESS:
|
||||
case RC_CONDITION_ADD_HITS:
|
||||
case RC_CONDITION_SUB_HITS:
|
||||
case RC_CONDITION_ADD_SOURCE:
|
||||
case RC_CONDITION_SUB_SOURCE:
|
||||
case RC_CONDITION_AND_NEXT:
|
||||
case RC_CONDITION_OR_NEXT:
|
||||
/* these conditions don't require a right hand size (implied *1) */
|
||||
break;
|
||||
|
||||
case RC_CONDITION_MEASURED:
|
||||
/* right hand side is not required when Measured is used in a value */
|
||||
if (is_value)
|
||||
break;
|
||||
/* fallthrough to default */
|
||||
|
@ -75,6 +74,7 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
|
|||
|
||||
self->has_pause |= (*next)->type == RC_CONDITION_PAUSE_IF;
|
||||
in_add_address = (*next)->type == RC_CONDITION_ADD_ADDRESS;
|
||||
self->has_indirect_memrefs |= in_add_address;
|
||||
|
||||
switch ((*next)->type) {
|
||||
case RC_CONDITION_MEASURED:
|
||||
|
@ -140,6 +140,30 @@ rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, in
|
|||
return self;
|
||||
}
|
||||
|
||||
static void rc_condset_update_indirect_memrefs(rc_condset_t* self, rc_condition_t* condition, int processing_pause, rc_eval_state_t* eval_state) {
|
||||
for (; condition != 0; condition = condition->next) {
|
||||
if (condition->pause != processing_pause)
|
||||
continue;
|
||||
|
||||
if (condition->type == RC_CONDITION_ADD_ADDRESS) {
|
||||
eval_state->add_address = rc_evaluate_condition_value(condition, eval_state);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* call rc_get_memref_value to update the indirect memrefs. it won't do anything with non-indirect
|
||||
* memrefs and avoids a second check of is_indirect. also, we ignore the response, so it doesn't
|
||||
* matter what operand type we pass. assume RC_OPERAND_ADDRESS is the quickest. */
|
||||
if (rc_operand_is_memref(&condition->operand1))
|
||||
rc_get_memref_value(condition->operand1.value.memref, RC_OPERAND_ADDRESS, eval_state);
|
||||
|
||||
if (rc_operand_is_memref(&condition->operand2))
|
||||
rc_get_memref_value(condition->operand2.value.memref, RC_OPERAND_ADDRESS, eval_state);
|
||||
|
||||
eval_state->add_address = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc_eval_state_t* eval_state) {
|
||||
rc_condition_t* condition;
|
||||
int set_valid, cond_valid, and_next, or_next, reset_next;
|
||||
|
@ -155,9 +179,8 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc
|
|||
eval_state->add_value = eval_state->add_hits = eval_state->add_address = 0;
|
||||
|
||||
for (condition = self->conditions; condition != 0; condition = condition->next) {
|
||||
if (condition->pause != processing_pause) {
|
||||
if (condition->pause != processing_pause)
|
||||
continue;
|
||||
}
|
||||
|
||||
/* STEP 1: process modifier conditions */
|
||||
switch (condition->type) {
|
||||
|
@ -283,6 +306,17 @@ static int rc_test_condset_internal(rc_condset_t* self, int processing_pause, rc
|
|||
case RC_CONDITION_PAUSE_IF:
|
||||
/* as soon as we find a PauseIf that evaluates to true, stop processing the rest of the group */
|
||||
if (cond_valid) {
|
||||
/* indirect memrefs are not updated as part of the rc_update_memref_values call.
|
||||
* an active pause aborts processing of the remaining part of the pause subset and the entire non-pause subset.
|
||||
* if the set has any indirect memrefs, manually update them now so the deltas are correct */
|
||||
if (self->has_indirect_memrefs) {
|
||||
/* first, update any indirect memrefs in the remaining part of the pause subset */
|
||||
rc_condset_update_indirect_memrefs(self, condition->next, 1, eval_state);
|
||||
|
||||
/* then, update all indirect memrefs in the non-pause subset */
|
||||
rc_condset_update_indirect_memrefs(self, self->conditions, 0, eval_state);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
#include "rcheevos.h"
|
||||
#include "rc_consoles.h"
|
||||
|
||||
#include <ctype.h>
|
||||
|
@ -163,14 +162,23 @@ const char* rc_console_name(int console_id)
|
|||
case RC_CONSOLE_SG1000:
|
||||
return "SG-1000";
|
||||
|
||||
case RC_CONSOLE_SHARPX1:
|
||||
return "Sharp X1";
|
||||
|
||||
case RC_CONSOLE_SUPER_NINTENDO:
|
||||
return "Super Nintendo Entertainment System";
|
||||
|
||||
case RC_CONSOLE_SUPER_CASSETTEVISION:
|
||||
return "Super CassetteVision";
|
||||
|
||||
case RC_CONSOLE_WONDERSWAN:
|
||||
return "WonderSwan";
|
||||
case RC_CONSOLE_SUPERVISION:
|
||||
return "Watara Supervision";
|
||||
|
||||
case RC_CONSOLE_THOMSONTO8:
|
||||
return "Thomson TO8";
|
||||
|
||||
case RC_CONSOLE_TIC80:
|
||||
return "TIC-80";
|
||||
|
||||
case RC_CONSOLE_VECTREX:
|
||||
return "Vectrex";
|
||||
|
@ -187,6 +195,9 @@ const char* rc_console_name(int console_id)
|
|||
case RC_CONSOLE_WII_U:
|
||||
return "Wii-U";
|
||||
|
||||
case RC_CONSOLE_WONDERSWAN:
|
||||
return "WonderSwan";
|
||||
|
||||
case RC_CONSOLE_X68K:
|
||||
return "X68K";
|
||||
|
||||
|
@ -327,10 +338,14 @@ static const rc_memory_region_t _rc_memory_regions_intellivision[] = {
|
|||
static const rc_memory_regions_t rc_memory_regions_intellivision = { _rc_memory_regions_intellivision, 9 };
|
||||
|
||||
/* ===== Magnavox Odyssey 2 ===== */
|
||||
/* https://sudonull.com/post/76885-Architecture-and-programming-Philips-Videopac-Magnavox-Odyssey-2 */
|
||||
static const rc_memory_region_t _rc_memory_regions_magnavox_odyssey_2[] = {
|
||||
{ 0x000000U, 0x00003FU, 0x000040U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
|
||||
/* Internal and external RAMs are reachable using unique instructions.
|
||||
* The real addresses provided are virtual and for mapping purposes only. */
|
||||
{ 0x000000U, 0x00003FU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Internal RAM" },
|
||||
{ 0x000040U, 0x00013FU, 0x000040U, RC_MEMORY_TYPE_SYSTEM_RAM, "External RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_magnavox_odyssey_2 = { _rc_memory_regions_magnavox_odyssey_2, 1 };
|
||||
static const rc_memory_regions_t rc_memory_regions_magnavox_odyssey_2 = { _rc_memory_regions_magnavox_odyssey_2, 2 };
|
||||
|
||||
/* ===== Master System ===== */
|
||||
/* http://www.smspower.org/Development/MemoryMap */
|
||||
|
@ -363,8 +378,9 @@ static const rc_memory_regions_t rc_memory_regions_msx = { _rc_memory_regions_ms
|
|||
/* ===== Neo Geo Pocket ===== */
|
||||
/* http://neopocott.emuunlim.com/docs/tech-11.txt */
|
||||
static const rc_memory_region_t _rc_memory_regions_neo_geo_pocket[] = {
|
||||
/* MednafenNGP exposes 16KB, but the doc suggests there's 24-32KB */
|
||||
{ 0x000000U, 0x003FFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
|
||||
/* The docs suggest there's Work RAM exposed from $0000-$6FFF, Sound RAM from $7000-$7FFF, and Video
|
||||
* RAM from $8000-$BFFF, but both MednafenNGP and FBNeo only expose system RAM from $4000-$7FFF */
|
||||
{ 0x000000U, 0x003FFFU, 0x004000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_neo_geo_pocket = { _rc_memory_regions_neo_geo_pocket, 1 };
|
||||
|
||||
|
@ -456,6 +472,14 @@ static const rc_memory_region_t _rc_memory_regions_playstation[] = {
|
|||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_playstation = { _rc_memory_regions_playstation, 2 };
|
||||
|
||||
/* ===== PlayStation 2 ===== */
|
||||
/* https://psi-rockin.github.io/ps2tek/ */
|
||||
static const rc_memory_region_t _rc_memory_regions_playstation2[] = {
|
||||
{ 0x00000000U, 0x000FFFFFU, 0x00000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Kernel RAM" },
|
||||
{ 0x00100000U, 0x01FFFFFFU, 0x00100000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_playstation2 = { _rc_memory_regions_playstation2, 2 };
|
||||
|
||||
/* ===== Pokemon Mini ===== */
|
||||
/* https://www.pokemon-mini.net/documentation/memory-map/ */
|
||||
static const rc_memory_region_t _rc_memory_regions_pokemini[] = {
|
||||
|
@ -483,12 +507,17 @@ static const rc_memory_regions_t rc_memory_regions_saturn = { _rc_memory_regions
|
|||
/* ===== SG-1000 ===== */
|
||||
/* http://www.smspower.org/Development/MemoryMap */
|
||||
static const rc_memory_region_t _rc_memory_regions_sg1000[] = {
|
||||
{ 0x000000U, 0x0003FFU, 0xC000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
|
||||
/* TODO: should cartridge memory be exposed ($0000-$BFFF)? it's usually just ROM data, but may contain on-cartridge RAM
|
||||
* This not is also concerning: http://www.smspower.org/Development/MemoryMap
|
||||
* Cartridges may disable the system RAM and thus take over the full 64KB address space. */
|
||||
{ 0x000000U, 0x0003FFU, 0xC000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
|
||||
/* https://github.com/libretro/FBNeo/blob/697801c6262be6ca91615cf905444d3e039bc06f/src/burn/drv/sg1000/d_sg1000.cpp#L210-L237 */
|
||||
/* Expansion mode B exposes 8KB at $C000. The first 2KB hides the System RAM, but since the address matches,
|
||||
we'll leverage that definition and expand it another 6KB */
|
||||
{ 0x000400U, 0x001FFFU, 0xC400U, RC_MEMORY_TYPE_SYSTEM_RAM, "Extended RAM" },
|
||||
/* Expansion mode A exposes 8KB at $2000 */
|
||||
{ 0x002000U, 0x003FFFU, 0x2000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Extended RAM" },
|
||||
/* Othello exposes 2KB at $8000, and The Castle exposes 8KB at $8000 */
|
||||
{ 0x004000U, 0x005FFFU, 0x8000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Extended RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_sg1000 = { _rc_memory_regions_sg1000, 1 };
|
||||
static const rc_memory_regions_t rc_memory_regions_sg1000 = { _rc_memory_regions_sg1000, 4 };
|
||||
|
||||
/* ===== Super Cassette Vision ===== */
|
||||
static const rc_memory_region_t _rc_memory_regions_scv[] = {
|
||||
|
@ -510,6 +539,29 @@ static const rc_memory_region_t _rc_memory_regions_snes[] = {
|
|||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_snes = { _rc_memory_regions_snes, 2 };
|
||||
|
||||
/* ===== Thomson TO8 ===== */
|
||||
/* https://github.com/mamedev/mame/blob/master/src/mame/drivers/thomson.cpp#L1617 */
|
||||
static const rc_memory_region_t _rc_memory_regions_thomson_to8[] = {
|
||||
{ 0x000000U, 0x07FFFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_thomson_to8 = { _rc_memory_regions_thomson_to8, 1 };
|
||||
|
||||
/* ===== TIC-80 ===== */
|
||||
/* https://github.com/nesbox/TIC-80/wiki/RAM */
|
||||
static const rc_memory_region_t _rc_memory_regions_tic80[] = {
|
||||
{ 0x000000U, 0x003FFFU, 0x000000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Video RAM" }, /* have to classify this as system RAM because the core exposes it as part of the RETRO_MEMORY_SYSTEM_RAM */
|
||||
{ 0x004000U, 0x005FFFU, 0x004000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Tile RAM" },
|
||||
{ 0x006000U, 0x007FFFU, 0x006000U, RC_MEMORY_TYPE_SYSTEM_RAM, "Sprite RAM" },
|
||||
{ 0x008000U, 0x00FF7FU, 0x008000U, RC_MEMORY_TYPE_SYSTEM_RAM, "MAP RAM" },
|
||||
{ 0x00FF80U, 0x00FF8BU, 0x00FF80U, RC_MEMORY_TYPE_SYSTEM_RAM, "Input State" },
|
||||
{ 0x00FF8CU, 0x014003U, 0x00FF8CU, RC_MEMORY_TYPE_SYSTEM_RAM, "Sound RAM" },
|
||||
{ 0x014004U, 0x014403U, 0x014004U, RC_MEMORY_TYPE_SAVE_RAM, "Persistent Memory" }, /* this is also returned as part of RETRO_MEMORY_SYSTEM_RAM, but can be extrapolated correctly because the pointer starts at the first SYSTEM_RAM region */
|
||||
{ 0x014404U, 0x014603U, 0x014404U, RC_MEMORY_TYPE_SYSTEM_RAM, "Sprite Flags" },
|
||||
{ 0x014604U, 0x014E03U, 0x014604U, RC_MEMORY_TYPE_SYSTEM_RAM, "System Font" },
|
||||
{ 0x014E04U, 0x017FFFU, 0x014E04U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM"}
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_tic80 = { _rc_memory_regions_tic80, 10 };
|
||||
|
||||
/* ===== Vectrex ===== */
|
||||
/* https://roadsidethoughts.com/vectrex/vectrex-memory-map.htm */
|
||||
static const rc_memory_region_t _rc_memory_regions_vectrex[] = {
|
||||
|
@ -524,6 +576,15 @@ static const rc_memory_region_t _rc_memory_regions_virtualboy[] = {
|
|||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_virtualboy = { _rc_memory_regions_virtualboy, 2 };
|
||||
|
||||
/* ===== Watara Supervision ===== */
|
||||
/* https://github.com/libretro/potator/blob/b5e5ba02914fcdf4a8128072dbc709da28e08832/common/memorymap.c#L231-L259 */
|
||||
static const rc_memory_region_t _rc_memory_regions_watara_supervision[] = {
|
||||
{ 0x0000U, 0x001FFFU, 0x0000U, RC_MEMORY_TYPE_SYSTEM_RAM, "System RAM" },
|
||||
{ 0x2000U, 0x003FFFU, 0x2000U, RC_MEMORY_TYPE_HARDWARE_CONTROLLER, "Registers" },
|
||||
{ 0x4000U, 0x005FFFU, 0x4000U, RC_MEMORY_TYPE_VIDEO_RAM, "Video RAM" }
|
||||
};
|
||||
static const rc_memory_regions_t rc_memory_regions_watara_supervision = { _rc_memory_regions_watara_supervision, 3 };
|
||||
|
||||
/* ===== WonderSwan ===== */
|
||||
/* http://daifukkat.su/docs/wsman/#ovr_memmap */
|
||||
static const rc_memory_region_t _rc_memory_regions_wonderswan[] = {
|
||||
|
@ -621,11 +682,14 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
|
|||
return &rc_memory_regions_pcengine;
|
||||
|
||||
case RC_CONSOLE_PCFX:
|
||||
return &rc_memory_regions_pcfx;
|
||||
return &rc_memory_regions_pcfx;
|
||||
|
||||
case RC_CONSOLE_PLAYSTATION:
|
||||
return &rc_memory_regions_playstation;
|
||||
|
||||
case RC_CONSOLE_PLAYSTATION_2:
|
||||
return &rc_memory_regions_playstation2;
|
||||
|
||||
case RC_CONSOLE_POKEMON_MINI:
|
||||
return &rc_memory_regions_pokemini;
|
||||
|
||||
|
@ -644,6 +708,15 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
|
|||
case RC_CONSOLE_SUPER_NINTENDO:
|
||||
return &rc_memory_regions_snes;
|
||||
|
||||
case RC_CONSOLE_SUPERVISION:
|
||||
return &rc_memory_regions_watara_supervision;
|
||||
|
||||
case RC_CONSOLE_THOMSONTO8:
|
||||
return &rc_memory_regions_thomson_to8;
|
||||
|
||||
case RC_CONSOLE_TIC80:
|
||||
return &rc_memory_regions_tic80;
|
||||
|
||||
case RC_CONSOLE_VECTREX:
|
||||
return &rc_memory_regions_vectrex;
|
||||
|
||||
|
@ -651,7 +724,7 @@ const rc_memory_regions_t* rc_console_memory_regions(int console_id)
|
|||
return &rc_memory_regions_virtualboy;
|
||||
|
||||
case RC_CONSOLE_WONDERSWAN:
|
||||
return &rc_memory_regions_wonderswan;
|
||||
return &rc_memory_regions_wonderswan;
|
||||
|
||||
default:
|
||||
return &rc_memory_regions_none;
|
||||
|
|
|
@ -153,6 +153,10 @@ int rc_lboard_size(const char* memaddr) {
|
|||
rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
|
||||
rc_lboard_t* self;
|
||||
rc_parse_state_t parse;
|
||||
|
||||
if (!buffer || !memaddr)
|
||||
return 0;
|
||||
|
||||
rc_init_parse_state(&parse, buffer, L, funcs_ndx);
|
||||
|
||||
self = RC_ALLOC(rc_lboard_t, &parse);
|
||||
|
@ -161,7 +165,7 @@ rc_lboard_t* rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, in
|
|||
rc_parse_lboard_internal(self, memaddr, &parse);
|
||||
|
||||
rc_destroy_parse_state(&parse);
|
||||
return parse.offset >= 0 ? self : 0;
|
||||
return (parse.offset >= 0) ? self : 0;
|
||||
}
|
||||
|
||||
int rc_evaluate_lboard(rc_lboard_t* self, int* value, rc_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
|
|
|
@ -95,6 +95,86 @@ int rc_parse_memref(const char** memaddr, char* size, unsigned* address) {
|
|||
return RC_OK;
|
||||
}
|
||||
|
||||
static const unsigned char rc_bits_set[16] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 };
|
||||
|
||||
unsigned rc_transform_memref_value(unsigned value, char size)
|
||||
{
|
||||
switch (size)
|
||||
{
|
||||
case RC_MEMSIZE_BIT_0:
|
||||
value = (value >> 0) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_1:
|
||||
value = (value >> 1) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_2:
|
||||
value = (value >> 2) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_3:
|
||||
value = (value >> 3) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_4:
|
||||
value = (value >> 4) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_5:
|
||||
value = (value >> 5) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_6:
|
||||
value = (value >> 6) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_7:
|
||||
value = (value >> 7) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_LOW:
|
||||
value = value & 0x0f;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_HIGH:
|
||||
value = (value >> 4) & 0x0f;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BITCOUNT:
|
||||
value = rc_bits_set[(value & 0x0F)]
|
||||
+ rc_bits_set[((value >> 4) & 0x0F)];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
char rc_memref_shared_size(char size)
|
||||
{
|
||||
switch (size) {
|
||||
case RC_MEMSIZE_BIT_0:
|
||||
case RC_MEMSIZE_BIT_1:
|
||||
case RC_MEMSIZE_BIT_2:
|
||||
case RC_MEMSIZE_BIT_3:
|
||||
case RC_MEMSIZE_BIT_4:
|
||||
case RC_MEMSIZE_BIT_5:
|
||||
case RC_MEMSIZE_BIT_6:
|
||||
case RC_MEMSIZE_BIT_7:
|
||||
case RC_MEMSIZE_LOW:
|
||||
case RC_MEMSIZE_HIGH:
|
||||
case RC_MEMSIZE_BITCOUNT:
|
||||
/* these can all share an 8-bit memref and just mask off the appropriate data in rc_transform_memref_value */
|
||||
return RC_MEMSIZE_8_BITS;
|
||||
|
||||
default:
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud) {
|
||||
unsigned value;
|
||||
|
||||
|
@ -103,46 +183,6 @@ static unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void*
|
|||
|
||||
switch (size)
|
||||
{
|
||||
case RC_MEMSIZE_BIT_0:
|
||||
value = (peek(address, 1, ud) >> 0) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_1:
|
||||
value = (peek(address, 1, ud) >> 1) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_2:
|
||||
value = (peek(address, 1, ud) >> 2) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_3:
|
||||
value = (peek(address, 1, ud) >> 3) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_4:
|
||||
value = (peek(address, 1, ud) >> 4) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_5:
|
||||
value = (peek(address, 1, ud) >> 5) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_6:
|
||||
value = (peek(address, 1, ud) >> 6) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_7:
|
||||
value = (peek(address, 1, ud) >> 7) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_LOW:
|
||||
value = peek(address, 1, ud) & 0x0f;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_HIGH:
|
||||
value = (peek(address, 1, ud) >> 4) & 0x0f;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_8_BITS:
|
||||
value = peek(address, 1, ud);
|
||||
break;
|
||||
|
@ -161,7 +201,15 @@ static unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void*
|
|||
break;
|
||||
|
||||
default:
|
||||
value = 0;
|
||||
if (rc_memref_shared_size(size) == RC_MEMSIZE_8_BITS)
|
||||
{
|
||||
value = peek(address, 1, ud);
|
||||
value = rc_transform_memref_value(value, size);
|
||||
}
|
||||
else
|
||||
{
|
||||
value = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -102,27 +102,7 @@ static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_
|
|||
if (ret != RC_OK)
|
||||
return ret;
|
||||
|
||||
switch (self->size) {
|
||||
case RC_MEMSIZE_BIT_0:
|
||||
case RC_MEMSIZE_BIT_1:
|
||||
case RC_MEMSIZE_BIT_2:
|
||||
case RC_MEMSIZE_BIT_3:
|
||||
case RC_MEMSIZE_BIT_4:
|
||||
case RC_MEMSIZE_BIT_5:
|
||||
case RC_MEMSIZE_BIT_6:
|
||||
case RC_MEMSIZE_BIT_7:
|
||||
case RC_MEMSIZE_LOW:
|
||||
case RC_MEMSIZE_HIGH:
|
||||
case RC_MEMSIZE_BITCOUNT:
|
||||
/* these can all share an 8-bit memref and just mask off the appropriate data in rc_evaluate_operand */
|
||||
size = RC_MEMSIZE_8_BITS;
|
||||
break;
|
||||
|
||||
default:
|
||||
size = self->size;
|
||||
break;
|
||||
}
|
||||
|
||||
size = rc_memref_shared_size(self->size);
|
||||
self->value.memref = rc_alloc_memref(parse, address, size, is_indirect);
|
||||
if (parse->offset < 0)
|
||||
return parse->offset;
|
||||
|
@ -283,7 +263,17 @@ static int rc_luapeek(lua_State* L) {
|
|||
|
||||
#endif /* RC_DISABLE_LUA */
|
||||
|
||||
static const unsigned char rc_bits_set[16] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 };
|
||||
int rc_operand_is_memref(rc_operand_t* self) {
|
||||
switch (self->type) {
|
||||
case RC_OPERAND_CONST:
|
||||
case RC_OPERAND_FP:
|
||||
case RC_OPERAND_LUA:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) {
|
||||
#ifndef RC_DISABLE_LUA
|
||||
|
@ -336,56 +326,7 @@ unsigned rc_evaluate_operand(rc_operand_t* self, rc_eval_state_t* eval_state) {
|
|||
}
|
||||
|
||||
/* step 2: mask off appropriate bits */
|
||||
switch (self->size)
|
||||
{
|
||||
case RC_MEMSIZE_BIT_0:
|
||||
value = (value >> 0) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_1:
|
||||
value = (value >> 1) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_2:
|
||||
value = (value >> 2) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_3:
|
||||
value = (value >> 3) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_4:
|
||||
value = (value >> 4) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_5:
|
||||
value = (value >> 5) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_6:
|
||||
value = (value >> 6) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BIT_7:
|
||||
value = (value >> 7) & 1;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_LOW:
|
||||
value = value & 0x0f;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_HIGH:
|
||||
value = (value >> 4) & 0x0f;
|
||||
break;
|
||||
|
||||
case RC_MEMSIZE_BITCOUNT:
|
||||
value = rc_bits_set[(value & 0x0F)]
|
||||
+ rc_bits_set[((value >> 4) & 0x0F)];
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
value = rc_transform_memref_value(value, self->size);
|
||||
|
||||
/* step 3: apply logic */
|
||||
switch (self->type)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
#ifndef INTERNAL_H
|
||||
#define INTERNAL_H
|
||||
|
||||
#include "rcheevos.h"
|
||||
#include "rc_runtime_types.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
@ -96,6 +96,7 @@ typedef struct {
|
|||
rc_value_t** variables;
|
||||
|
||||
unsigned measured_target;
|
||||
int lines_read;
|
||||
|
||||
char has_required_hits;
|
||||
}
|
||||
|
@ -116,8 +117,11 @@ void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud);
|
|||
void rc_update_memref_value(rc_memref_value_t* memref, unsigned value);
|
||||
unsigned rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state);
|
||||
unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type);
|
||||
char rc_memref_shared_size(char size);
|
||||
unsigned rc_transform_memref_value(unsigned value, char size);
|
||||
|
||||
void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse);
|
||||
int rc_trigger_state_active(int state);
|
||||
|
||||
rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, int is_value);
|
||||
int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state);
|
||||
|
|
|
@ -22,8 +22,9 @@ static rc_memref_value_t* rc_alloc_helper_variable_memref_value(const char* mema
|
|||
if (rc_parse_memref(&end, &size, &address) == RC_OK) {
|
||||
/* make sure the entire memaddr was consumed. if not, there's an operator and it's a comparison, not a memory reference */
|
||||
if (end == &memaddr[memaddr_len]) {
|
||||
/* just a memory reference, allocate it */
|
||||
return &rc_alloc_memref(parse, address, size, 0)->value;
|
||||
/* if it's not a derived size, we can reference the memref directly */
|
||||
if (rc_memref_shared_size(size) == size)
|
||||
return &rc_alloc_memref(parse, address, size, 0)->value;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,7 +36,7 @@ static rc_memref_value_t* rc_alloc_helper_variable_memref_value(const char* mema
|
|||
return &variable->value;
|
||||
}
|
||||
|
||||
static const char* rc_parse_line(const char* line, const char** end) {
|
||||
static const char* rc_parse_line(const char* line, const char** end, rc_parse_state_t* parse) {
|
||||
const char* nextline;
|
||||
const char* endline;
|
||||
|
||||
|
@ -44,25 +45,31 @@ static const char* rc_parse_line(const char* line, const char** end) {
|
|||
while (*nextline && *nextline != '\n')
|
||||
++nextline;
|
||||
|
||||
/* find a trailing comment marker (//) */
|
||||
/* if a trailing comment marker (//) exists, the line stops there */
|
||||
endline = line;
|
||||
while (endline < nextline && (endline[0] != '/' || endline[1] != '/' || (endline > line && endline[-1] == '\\')))
|
||||
++endline;
|
||||
|
||||
/* remove trailing whitespace */
|
||||
if (endline == nextline) {
|
||||
/* trailing whitespace on a line without a comment marker may be significant, just remove the line ending */
|
||||
if (endline > line && endline[-1] == '\r')
|
||||
--endline;
|
||||
} else {
|
||||
while (endline > line && isspace(endline[-1]))
|
||||
/* remove trailing whitespace before the comment marker */
|
||||
while (endline > line && isspace((int)((unsigned char*)endline)[-1]))
|
||||
--endline;
|
||||
}
|
||||
|
||||
/* end is pointing at the first character to ignore - makes subtraction for length easier */
|
||||
/* point end at the first character to ignore, it makes subtraction for length easier */
|
||||
*end = endline;
|
||||
|
||||
/* tally the line */
|
||||
++parse->lines_read;
|
||||
|
||||
/* skip the newline character so we're pointing at the next line */
|
||||
if (*nextline == '\n')
|
||||
++nextline;
|
||||
|
||||
return nextline;
|
||||
}
|
||||
|
||||
|
@ -337,7 +344,7 @@ static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup
|
|||
do
|
||||
{
|
||||
line = nextline;
|
||||
nextline = rc_parse_line(line, &endline);
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
|
||||
if (endline - line < 2) {
|
||||
/* ignore full line comments inside a lookup */
|
||||
|
@ -436,13 +443,20 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
|
|||
const char* endline;
|
||||
const char* ptr;
|
||||
int hasdisplay = 0;
|
||||
int display_line = 0;
|
||||
int chars;
|
||||
|
||||
/* special case for empty script to return 1 line read */
|
||||
if (!*script) {
|
||||
parse->lines_read = 1;
|
||||
parse->offset = RC_MISSING_DISPLAY_STRING;
|
||||
return;
|
||||
}
|
||||
|
||||
/* first pass: process macro initializers */
|
||||
line = script;
|
||||
while (*line)
|
||||
{
|
||||
nextline = rc_parse_line(line, &endline);
|
||||
while (*line) {
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
if (strncmp(line, "Lookup:", 7) == 0) {
|
||||
line += 7;
|
||||
|
||||
|
@ -469,7 +483,7 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
|
|||
nextlookup = &lookup->next;
|
||||
|
||||
line = nextline;
|
||||
nextline = rc_parse_line(line, &endline);
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
if (parse->buffer && strncmp(line, "FormatType=", 11) == 0) {
|
||||
line += 11;
|
||||
|
||||
|
@ -485,10 +499,11 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
|
|||
}
|
||||
} else if (strncmp(line, "Display:", 8) == 0) {
|
||||
display = nextline;
|
||||
display_line = parse->lines_read;
|
||||
|
||||
do {
|
||||
line = nextline;
|
||||
nextline = rc_parse_line(line, &endline);
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
} while (*line == '?');
|
||||
}
|
||||
|
||||
|
@ -502,8 +517,12 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
|
|||
|
||||
/* second pass, process display string*/
|
||||
if (display) {
|
||||
/* point the parser back at the display strings */
|
||||
int lines_read = parse->lines_read;
|
||||
parse->lines_read = display_line;
|
||||
line = display;
|
||||
nextline = rc_parse_line(line, &endline);
|
||||
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
|
||||
while (*line == '?') {
|
||||
/* conditional display: ?trigger?string */
|
||||
|
@ -525,7 +544,7 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
|
|||
}
|
||||
|
||||
line = nextline;
|
||||
nextline = rc_parse_line(line, &endline);
|
||||
nextline = rc_parse_line(line, &endline, parse);
|
||||
}
|
||||
|
||||
/* non-conditional display: string */
|
||||
|
@ -534,6 +553,9 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
|
|||
hasdisplay = 1;
|
||||
nextdisplay = &((*nextdisplay)->next);
|
||||
}
|
||||
|
||||
/* restore the parser state */
|
||||
parse->lines_read = lines_read;
|
||||
}
|
||||
|
||||
/* finalize */
|
||||
|
@ -544,7 +566,7 @@ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script,
|
|||
}
|
||||
}
|
||||
|
||||
int rc_richpresence_size(const char* script) {
|
||||
int rc_richpresence_size_lines(const char* script, int* lines_read) {
|
||||
rc_richpresence_t* self;
|
||||
rc_parse_state_t parse;
|
||||
rc_memref_t* first_memref;
|
||||
|
@ -556,13 +578,24 @@ int rc_richpresence_size(const char* script) {
|
|||
self = RC_ALLOC(rc_richpresence_t, &parse);
|
||||
rc_parse_richpresence_internal(self, script, &parse);
|
||||
|
||||
if (lines_read)
|
||||
*lines_read = parse.lines_read;
|
||||
|
||||
rc_destroy_parse_state(&parse);
|
||||
return parse.offset;
|
||||
}
|
||||
|
||||
int rc_richpresence_size(const char* script) {
|
||||
return rc_richpresence_size_lines(script, NULL);
|
||||
}
|
||||
|
||||
rc_richpresence_t* rc_parse_richpresence(void* buffer, const char* script, lua_State* L, int funcs_ndx) {
|
||||
rc_richpresence_t* self;
|
||||
rc_parse_state_t parse;
|
||||
|
||||
if (!buffer || !script)
|
||||
return NULL;
|
||||
|
||||
rc_init_parse_state(&parse, buffer, L, funcs_ndx);
|
||||
|
||||
self = RC_ALLOC(rc_richpresence_t, &parse);
|
||||
|
@ -572,7 +605,7 @@ rc_richpresence_t* rc_parse_richpresence(void* buffer, const char* script, lua_S
|
|||
rc_parse_richpresence_internal(self, script, &parse);
|
||||
|
||||
rc_destroy_parse_state(&parse);
|
||||
return parse.offset >= 0 ? self : 0;
|
||||
return (parse.offset >= 0) ? self : NULL;
|
||||
}
|
||||
|
||||
void rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "rc_runtime.h"
|
||||
#include "rc_internal.h"
|
||||
|
||||
#include "../rhash/md5.h"
|
||||
|
@ -217,6 +218,29 @@ rc_trigger_t* rc_runtime_get_achievement(const rc_runtime_t* self, unsigned id)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
int rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, unsigned id, unsigned* measured_value, unsigned* measured_target)
|
||||
{
|
||||
const rc_trigger_t* trigger = rc_runtime_get_achievement(runtime, id);
|
||||
if (!measured_value || !measured_target)
|
||||
return 0;
|
||||
|
||||
if (!trigger) {
|
||||
*measured_value = *measured_target = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (rc_trigger_state_active(trigger->state)) {
|
||||
*measured_value = trigger->measured_value;
|
||||
*measured_target = trigger->measured_target;
|
||||
}
|
||||
else {
|
||||
/* don't report measured information for inactive triggers */
|
||||
*measured_value = *measured_target = 0;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void rc_runtime_deactivate_lboard_by_index(rc_runtime_t* self, unsigned index) {
|
||||
if (self->lboards[index].owns_memrefs) {
|
||||
/* if the lboard has one or more memrefs in its buffer, we can't free the buffer.
|
||||
|
@ -353,6 +377,11 @@ rc_lboard_t* rc_runtime_get_lboard(const rc_runtime_t* self, unsigned id)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
int rc_runtime_format_lboard_value(char* buffer, int size, int value, int format)
|
||||
{
|
||||
return rc_format_value(buffer, size, value, format);
|
||||
}
|
||||
|
||||
int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua_State* L, int funcs_idx) {
|
||||
rc_richpresence_t* richpresence;
|
||||
rc_runtime_richpresence_t* previous;
|
||||
|
@ -422,7 +451,7 @@ int rc_runtime_activate_richpresence(rc_runtime_t* self, const char* script, lua
|
|||
return RC_OK;
|
||||
}
|
||||
|
||||
int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, unsigned buffersize, rc_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, unsigned buffersize, rc_runtime_peek_t peek, void* peek_ud, lua_State* L) {
|
||||
if (self->richpresence && self->richpresence->richpresence)
|
||||
return rc_get_richpresence_display_string(self->richpresence->richpresence, buffer, buffersize, peek, peek_ud, L);
|
||||
|
||||
|
@ -430,7 +459,7 @@ int rc_runtime_get_richpresence(const rc_runtime_t* self, char* buffer, unsigned
|
|||
return 0;
|
||||
}
|
||||
|
||||
void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_handler, rc_peek_t peek, void* ud, lua_State* L) {
|
||||
void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_handler, rc_runtime_peek_t peek, void* ud, lua_State* L) {
|
||||
rc_runtime_event_t runtime_event;
|
||||
int i;
|
||||
|
||||
|
@ -441,7 +470,7 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
|
|||
|
||||
for (i = self->trigger_count - 1; i >= 0; --i) {
|
||||
rc_trigger_t* trigger = self->triggers[i].trigger;
|
||||
int trigger_state;
|
||||
int old_state, new_state;
|
||||
|
||||
if (!trigger)
|
||||
continue;
|
||||
|
@ -460,15 +489,34 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
|
|||
continue;
|
||||
}
|
||||
|
||||
trigger_state = trigger->state;
|
||||
switch (rc_evaluate_trigger(trigger, peek, ud, L))
|
||||
{
|
||||
case RC_TRIGGER_STATE_RESET:
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_RESET;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
break;
|
||||
old_state = trigger->state;
|
||||
new_state = rc_evaluate_trigger(trigger, peek, ud, L);
|
||||
|
||||
/* the trigger state doesn't actually change to RESET, RESET just serves as a notification.
|
||||
* handle the notification, then look at the actual state */
|
||||
if (new_state == RC_TRIGGER_STATE_RESET)
|
||||
{
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_RESET;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
|
||||
new_state = trigger->state;
|
||||
}
|
||||
|
||||
/* if the state hasn't changed, there won't be any events raised */
|
||||
if (new_state == old_state)
|
||||
continue;
|
||||
|
||||
/* raise an UNPRIMED event when changing from UNPRIMED to anything else */
|
||||
if (old_state == RC_TRIGGER_STATE_PRIMED) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
|
||||
/* raise events for each of the possible new states */
|
||||
switch (new_state)
|
||||
{
|
||||
case RC_TRIGGER_STATE_TRIGGERED:
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
|
@ -476,23 +524,21 @@ void rc_runtime_do_frame(rc_runtime_t* self, rc_runtime_event_handler_t event_ha
|
|||
break;
|
||||
|
||||
case RC_TRIGGER_STATE_PAUSED:
|
||||
if (trigger_state != RC_TRIGGER_STATE_PAUSED) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
break;
|
||||
|
||||
case RC_TRIGGER_STATE_PRIMED:
|
||||
if (trigger_state != RC_TRIGGER_STATE_PRIMED) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PRIMED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
break;
|
||||
|
||||
case RC_TRIGGER_STATE_ACTIVE:
|
||||
if (trigger_state != RC_TRIGGER_STATE_ACTIVE) {
|
||||
/* only raise ACTIVATED event when transitioning from an inactive state.
|
||||
* note that inactive in this case means active but cannot trigger. */
|
||||
if (old_state == RC_TRIGGER_STATE_WAITING || old_state == RC_TRIGGER_STATE_PAUSED) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
event_handler(&runtime_event);
|
||||
|
@ -594,7 +640,9 @@ static int rc_condset_contains_memref(const rc_condset_t* condset, const rc_memr
|
|||
return 0;
|
||||
|
||||
for (cond = condset->conditions; cond; cond = cond->next) {
|
||||
if (cond->operand1.value.memref == memref || cond->operand2.value.memref == memref)
|
||||
if (rc_operand_is_memref(&cond->operand1) && cond->operand1.value.memref == memref)
|
||||
return 1;
|
||||
if (rc_operand_is_memref(&cond->operand2) && cond->operand2.value.memref == memref)
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
@ -630,32 +678,8 @@ static int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memr
|
|||
return 0;
|
||||
}
|
||||
|
||||
void rc_runtime_invalidate_address(rc_runtime_t* self, unsigned address) {
|
||||
static void rc_runtime_invalidate_memref(rc_runtime_t* self, rc_memref_t* memref) {
|
||||
unsigned i;
|
||||
rc_memref_t* memref;
|
||||
rc_memref_t** last_memref;
|
||||
|
||||
if (!self->memrefs)
|
||||
return;
|
||||
|
||||
/* remove the invalid memref from the chain so we don't try to evaluate it in the future.
|
||||
* it's still there, so anything referencing it will continue to fetch 0.
|
||||
*/
|
||||
last_memref = &self->memrefs;
|
||||
memref = *last_memref;
|
||||
do {
|
||||
if (memref->address == address && !memref->value.is_indirect) {
|
||||
*last_memref = memref->next;
|
||||
break;
|
||||
}
|
||||
|
||||
last_memref = &memref->next;
|
||||
memref = *last_memref;
|
||||
} while (memref);
|
||||
|
||||
/* if the address is only used indirectly, don't disable anything dependent on it */
|
||||
if (!memref)
|
||||
return;
|
||||
|
||||
/* disable any achievements dependent on the address */
|
||||
for (i = 0; i < self->trigger_count; ++i) {
|
||||
|
@ -689,3 +713,80 @@ void rc_runtime_invalidate_address(rc_runtime_t* self, unsigned address) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void rc_runtime_invalidate_address(rc_runtime_t* self, unsigned address) {
|
||||
rc_memref_t** last_memref = &self->memrefs;
|
||||
rc_memref_t* memref = self->memrefs;
|
||||
|
||||
while (memref) {
|
||||
if (memref->address == address && !memref->value.is_indirect) {
|
||||
/* remove the invalid memref from the chain so we don't try to evaluate it in the future.
|
||||
* it's still there, so anything referencing it will continue to fetch 0.
|
||||
*/
|
||||
*last_memref = memref->next;
|
||||
|
||||
rc_runtime_invalidate_memref(self, memref);
|
||||
break;
|
||||
}
|
||||
|
||||
last_memref = &memref->next;
|
||||
memref = *last_memref;
|
||||
}
|
||||
}
|
||||
|
||||
void rc_runtime_validate_addresses(rc_runtime_t* self, rc_runtime_event_handler_t event_handler,
|
||||
rc_runtime_validate_address_t validate_handler) {
|
||||
rc_memref_t** last_memref = &self->memrefs;
|
||||
rc_memref_t* memref = self->memrefs;
|
||||
int num_invalid = 0;
|
||||
|
||||
while (memref) {
|
||||
if (!memref->value.is_indirect && !validate_handler(memref->address)) {
|
||||
/* remove the invalid memref from the chain so we don't try to evaluate it in the future.
|
||||
* it's still there, so anything referencing it will continue to fetch 0.
|
||||
*/
|
||||
*last_memref = memref->next;
|
||||
|
||||
rc_runtime_invalidate_memref(self, memref);
|
||||
++num_invalid;
|
||||
}
|
||||
else {
|
||||
last_memref = &memref->next;
|
||||
}
|
||||
|
||||
memref = *last_memref;
|
||||
}
|
||||
|
||||
if (num_invalid) {
|
||||
rc_runtime_event_t runtime_event;
|
||||
int i;
|
||||
|
||||
for (i = self->trigger_count - 1; i >= 0; --i) {
|
||||
rc_trigger_t* trigger = self->triggers[i].trigger;
|
||||
if (trigger && self->triggers[i].invalid_memref) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_DISABLED;
|
||||
runtime_event.id = self->triggers[i].id;
|
||||
runtime_event.value = self->triggers[i].invalid_memref->address;
|
||||
|
||||
trigger->state = RC_TRIGGER_STATE_DISABLED;
|
||||
self->triggers[i].invalid_memref = NULL;
|
||||
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = self->lboard_count - 1; i >= 0; --i) {
|
||||
rc_lboard_t* lboard = self->lboards[i].lboard;
|
||||
if (lboard && self->lboards[i].invalid_memref) {
|
||||
runtime_event.type = RC_RUNTIME_EVENT_LBOARD_DISABLED;
|
||||
runtime_event.id = self->lboards[i].id;
|
||||
runtime_event.value = self->lboards[i].invalid_memref->address;
|
||||
|
||||
lboard->state = RC_LBOARD_STATE_DISABLED;
|
||||
self->lboards[i].invalid_memref = NULL;
|
||||
|
||||
event_handler(&runtime_event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#include "rc_runtime.h"
|
||||
#include "rc_internal.h"
|
||||
|
||||
#include "../rhash/md5.h"
|
||||
|
@ -336,17 +337,9 @@ static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progres
|
|||
if (!runtime_trigger->trigger)
|
||||
continue;
|
||||
|
||||
switch (runtime_trigger->trigger->state)
|
||||
{
|
||||
case RC_TRIGGER_STATE_DISABLED:
|
||||
case RC_TRIGGER_STATE_INACTIVE:
|
||||
case RC_TRIGGER_STATE_TRIGGERED:
|
||||
/* don't store state for inactive or triggered achievements */
|
||||
continue;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
/* don't store state for inactive or triggered achievements */
|
||||
if (!rc_trigger_state_active(runtime_trigger->trigger->state))
|
||||
continue;
|
||||
|
||||
if (!progress->buffer) {
|
||||
if (runtime_trigger->serialized_size) {
|
||||
|
@ -469,19 +462,12 @@ int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const unsigned char*
|
|||
for (i = 0; i < runtime->trigger_count; ++i) {
|
||||
rc_runtime_trigger_t* runtime_trigger = &runtime->triggers[i];
|
||||
if (runtime_trigger->trigger) {
|
||||
switch (runtime_trigger->trigger->state)
|
||||
/* don't update state for inactive or triggered achievements */
|
||||
if (rc_trigger_state_active(runtime_trigger->trigger->state))
|
||||
{
|
||||
case RC_TRIGGER_STATE_DISABLED:
|
||||
case RC_TRIGGER_STATE_INACTIVE:
|
||||
case RC_TRIGGER_STATE_TRIGGERED:
|
||||
/* don't update state for inactive or triggered achievements */
|
||||
break;
|
||||
|
||||
default:
|
||||
/* mark active achievements as unupdated. anything that's still unupdated
|
||||
* after deserializing the progress will be reset to waiting */
|
||||
runtime_trigger->trigger->state = RC_TRIGGER_STATE_UNUPDATED;
|
||||
break;
|
||||
/* mark active achievements as unupdated. anything that's still unupdated
|
||||
* after deserializing the progress will be reset to waiting */
|
||||
runtime_trigger->trigger->state = RC_TRIGGER_STATE_UNUPDATED;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,6 +65,10 @@ int rc_trigger_size(const char* memaddr) {
|
|||
rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
|
||||
rc_trigger_t* self;
|
||||
rc_parse_state_t parse;
|
||||
|
||||
if (!buffer || !memaddr)
|
||||
return NULL;
|
||||
|
||||
rc_init_parse_state(&parse, buffer, L, funcs_ndx);
|
||||
|
||||
self = RC_ALLOC(rc_trigger_t, &parse);
|
||||
|
@ -73,7 +77,21 @@ rc_trigger_t* rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L,
|
|||
rc_parse_trigger_internal(self, &memaddr, &parse);
|
||||
|
||||
rc_destroy_parse_state(&parse);
|
||||
return parse.offset >= 0 ? self : 0;
|
||||
return (parse.offset >= 0) ? self : NULL;
|
||||
}
|
||||
|
||||
int rc_trigger_state_active(int state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case RC_TRIGGER_STATE_DISABLED:
|
||||
case RC_TRIGGER_STATE_INACTIVE:
|
||||
case RC_TRIGGER_STATE_TRIGGERED:
|
||||
return 0;
|
||||
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
static void rc_reset_trigger_hitcounts(rc_trigger_t* self) {
|
||||
|
@ -98,21 +116,28 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State*
|
|||
char is_paused;
|
||||
char is_primed;
|
||||
|
||||
/* previously triggered, do nothing - return INACTIVE so caller doesn't think it triggered again */
|
||||
if (self->state == RC_TRIGGER_STATE_TRIGGERED)
|
||||
return RC_TRIGGER_STATE_INACTIVE;
|
||||
switch (self->state)
|
||||
{
|
||||
case RC_TRIGGER_STATE_TRIGGERED:
|
||||
/* previously triggered. do nothing - return INACTIVE so caller doesn't think it triggered again */
|
||||
return RC_TRIGGER_STATE_INACTIVE;
|
||||
|
||||
/* unsupported, do nothing - return INACTIVE */
|
||||
if (self->state == RC_TRIGGER_STATE_DISABLED)
|
||||
return RC_TRIGGER_STATE_INACTIVE;
|
||||
case RC_TRIGGER_STATE_DISABLED:
|
||||
/* unsupported. do nothing - return INACTIVE */
|
||||
return RC_TRIGGER_STATE_INACTIVE;
|
||||
|
||||
case RC_TRIGGER_STATE_INACTIVE:
|
||||
/* not yet active. update the memrefs so deltas are correct when it becomes active, then return INACTIVE */
|
||||
rc_update_memref_values(self->memrefs, peek, ud);
|
||||
return RC_TRIGGER_STATE_INACTIVE;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* update the memory references */
|
||||
rc_update_memref_values(self->memrefs, peek, ud);
|
||||
|
||||
/* not yet active, only update the memrefs so deltas are correct when it becomes active */
|
||||
if (self->state == RC_TRIGGER_STATE_INACTIVE)
|
||||
return RC_TRIGGER_STATE_INACTIVE;
|
||||
|
||||
/* process the trigger */
|
||||
memset(&eval_state, 0, sizeof(eval_state));
|
||||
eval_state.peek = peek;
|
||||
|
@ -174,6 +199,11 @@ int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State*
|
|||
/* if there were hit counts to clear, return RESET, but don't change the state */
|
||||
if (self->has_hits) {
|
||||
self->has_hits = 0;
|
||||
|
||||
/* cannot be PRIMED while ResetIf is true */
|
||||
if (self->state == RC_TRIGGER_STATE_PRIMED)
|
||||
self->state = RC_TRIGGER_STATE_ACTIVE;
|
||||
|
||||
return RC_TRIGGER_STATE_RESET;
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_stat
|
|||
}
|
||||
else {
|
||||
/* if it looks like a floating point number, add the 'f' prefix */
|
||||
while (isdigit(*buffer_ptr))
|
||||
while (isdigit(*(unsigned char*)buffer_ptr))
|
||||
++buffer_ptr;
|
||||
if (*buffer_ptr == '.')
|
||||
*ptr++ = 'f';
|
||||
|
@ -171,6 +171,10 @@ int rc_value_size(const char* memaddr) {
|
|||
rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx) {
|
||||
rc_value_t* self;
|
||||
rc_parse_state_t parse;
|
||||
|
||||
if (!buffer || !memaddr)
|
||||
return NULL;
|
||||
|
||||
rc_init_parse_state(&parse, buffer, L, funcs_ndx);
|
||||
|
||||
self = RC_ALLOC(rc_value_t, &parse);
|
||||
|
@ -179,7 +183,7 @@ rc_value_t* rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int
|
|||
rc_parse_value_internal(self, &memaddr, &parse);
|
||||
|
||||
rc_destroy_parse_state(&parse);
|
||||
return parse.offset >= 0 ? self : 0;
|
||||
return (parse.offset >= 0) ? self : NULL;
|
||||
}
|
||||
|
||||
int rc_evaluate_value(rc_value_t* self, rc_peek_t peek, void* ud, lua_State* L) {
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
/* internal helper functions in hash.c */
|
||||
extern void* rc_file_open(const char* path);
|
||||
extern void rc_file_seek(void* file_handle, size_t offset, int origin);
|
||||
extern size_t rc_file_tell(void* file_handle);
|
||||
extern void rc_file_seek(void* file_handle, int64_t offset, int origin);
|
||||
extern int64_t rc_file_tell(void* file_handle);
|
||||
extern size_t rc_file_read(void* file_handle, void* buffer, int requested_bytes);
|
||||
extern void rc_file_close(void* file_handle);
|
||||
extern int rc_hash_error(const char* message);
|
||||
|
@ -22,8 +22,8 @@ struct cdrom_t
|
|||
void* file_handle;
|
||||
int sector_size;
|
||||
int sector_header_size;
|
||||
int first_sector_offset;
|
||||
int first_sector;
|
||||
int64_t first_sector_offset;
|
||||
};
|
||||
|
||||
static void cdreader_determine_sector_size(struct cdrom_t* cdrom)
|
||||
|
@ -38,13 +38,14 @@ static void cdreader_determine_sector_size(struct cdrom_t* cdrom)
|
|||
};
|
||||
|
||||
unsigned char header[32];
|
||||
const int toc_sector = 16;
|
||||
const int64_t toc_sector = 16;
|
||||
|
||||
cdrom->sector_size = 0;
|
||||
cdrom->sector_header_size = 0;
|
||||
|
||||
rc_file_seek(cdrom->file_handle, toc_sector * 2352 + cdrom->first_sector_offset, SEEK_SET);
|
||||
rc_file_read(cdrom->file_handle, header, sizeof(header));
|
||||
if (rc_file_read(cdrom->file_handle, header, sizeof(header)) < sizeof(header))
|
||||
return;
|
||||
|
||||
if (memcmp(header, sync_pattern, 12) == 0)
|
||||
{
|
||||
|
@ -219,9 +220,9 @@ static char* cdreader_get_bin_path(const char* cue_path, const char* bin_name)
|
|||
return bin_filename;
|
||||
}
|
||||
|
||||
static size_t cdreader_get_bin_size(const char* cue_path, const char* bin_name)
|
||||
static int64_t cdreader_get_bin_size(const char* cue_path, const char* bin_name)
|
||||
{
|
||||
size_t size = 0;
|
||||
int64_t size = 0;
|
||||
char* bin_filename = cdreader_get_bin_path(cue_path, bin_name);
|
||||
if (bin_filename)
|
||||
{
|
||||
|
@ -242,7 +243,7 @@ static size_t cdreader_get_bin_size(const char* cue_path, const char* bin_name)
|
|||
static void* cdreader_open_cue_track(const char* path, uint32_t track)
|
||||
{
|
||||
void* file_handle;
|
||||
size_t file_offset = 0;
|
||||
int64_t file_offset = 0;
|
||||
char buffer[1024], mode[16];
|
||||
char* bin_filename;
|
||||
char file[256];
|
||||
|
@ -253,14 +254,14 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
|
|||
int previous_sector_size = 0;
|
||||
int previous_index_sector_offset = 0;
|
||||
int previous_track_is_data = 0;
|
||||
int previous_track_sector_offset = 0;
|
||||
int64_t previous_track_sector_offset = 0;
|
||||
char previous_track_mode[16];
|
||||
int largest_track = 0;
|
||||
int largest_track_sector_count = 0;
|
||||
int largest_track_offset = 0;
|
||||
int64_t largest_track_offset = 0;
|
||||
char largest_track_mode[16];
|
||||
char largest_track_file[256];
|
||||
int offset = 0;
|
||||
int64_t offset = 0;
|
||||
int done = 0;
|
||||
size_t num_read = 0;
|
||||
struct cdrom_t* cdrom = NULL;
|
||||
|
@ -290,7 +291,8 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
|
|||
if (strncasecmp(ptr, "INDEX ", 6) == 0)
|
||||
{
|
||||
int m = 0, s = 0, f = 0;
|
||||
int index, sector_offset;
|
||||
int index;
|
||||
int sector_offset;
|
||||
|
||||
ptr += 6;
|
||||
index = atoi(ptr);
|
||||
|
@ -336,7 +338,9 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
|
|||
++scan;
|
||||
*scan = '\0';
|
||||
|
||||
snprintf(message, sizeof(message), "Found %s track %d (sector size %d, track starts at %d)", mode, current_track, sector_size, offset);
|
||||
/* it's undesirable to truncate offset to 32-bits, but %lld isn't defined in c89. */
|
||||
snprintf(message, sizeof(message), "Found %s track %d (sector size %d, track starts at %d)",
|
||||
mode, current_track, sector_size, (int)offset);
|
||||
verbose_message_callback(message);
|
||||
}
|
||||
|
||||
|
@ -501,7 +505,8 @@ static void* cdreader_open_cue_track(const char* path, uint32_t track)
|
|||
if (verbose_message_callback)
|
||||
{
|
||||
if (cdrom->first_sector_offset)
|
||||
snprintf((char*)buffer, sizeof(buffer), "Opened track %d (sector size %d, track starts at %d)", track, cdrom->sector_size, cdrom->first_sector_offset);
|
||||
snprintf((char*)buffer, sizeof(buffer), "Opened track %d (sector size %d, track starts at %d)",
|
||||
track, cdrom->sector_size, (int)cdrom->first_sector_offset);
|
||||
else
|
||||
snprintf((char*)buffer, sizeof(buffer), "Opened track %d (sector size %d)", track, cdrom->sector_size);
|
||||
|
||||
|
@ -531,7 +536,7 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
|
|||
char mode[16] = "MODE1/";
|
||||
char sector_size[16];
|
||||
char file[256];
|
||||
size_t track_size;
|
||||
int64_t track_size;
|
||||
int track_type;
|
||||
char* bin_path = "";
|
||||
uint32_t current_track = 0;
|
||||
|
@ -539,14 +544,14 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
|
|||
int lba = 0;
|
||||
|
||||
uint32_t largest_track = 0;
|
||||
size_t largest_track_size = 0;
|
||||
int64_t largest_track_size = 0;
|
||||
char largest_track_file[256];
|
||||
char largest_track_sector_size[16];
|
||||
int largest_track_lba = 0;
|
||||
|
||||
int found = 0;
|
||||
size_t num_read = 0;
|
||||
size_t file_offset = 0;
|
||||
int64_t file_offset = 0;
|
||||
struct cdrom_t* cdrom = NULL;
|
||||
|
||||
file_handle = rc_file_open(path);
|
||||
|
@ -584,6 +589,9 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
|
|||
++ptr;
|
||||
|
||||
/* line format: [trackid] [lba] [type] [sectorsize] [file] [?] */
|
||||
while (isspace(*ptr))
|
||||
++ptr;
|
||||
|
||||
current_track = (uint32_t)atoi(ptr);
|
||||
if (track && current_track != track)
|
||||
continue;
|
||||
|
@ -592,22 +600,34 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
|
|||
++ptr;
|
||||
++ptr;
|
||||
|
||||
while (isspace(*ptr))
|
||||
++ptr;
|
||||
|
||||
lba = atoi(ptr);
|
||||
while (isdigit(*ptr))
|
||||
++ptr;
|
||||
++ptr;
|
||||
|
||||
while (isspace(*ptr))
|
||||
++ptr;
|
||||
|
||||
track_type = atoi(ptr);
|
||||
while (isdigit(*ptr))
|
||||
++ptr;
|
||||
++ptr;
|
||||
|
||||
while (isspace(*ptr))
|
||||
++ptr;
|
||||
|
||||
ptr2 = sector_size;
|
||||
while (isdigit(*ptr))
|
||||
*ptr2++ = *ptr++;
|
||||
*ptr2 = '\0';
|
||||
++ptr;
|
||||
|
||||
while (isspace(*ptr))
|
||||
++ptr;
|
||||
|
||||
ptr2 = file;
|
||||
if (*ptr == '\"')
|
||||
{
|
||||
|
@ -698,6 +718,8 @@ static void* cdreader_open_gdi_track(const char* path, uint32_t track)
|
|||
cdrom = NULL;
|
||||
}
|
||||
|
||||
free(bin_path);
|
||||
|
||||
return cdrom;
|
||||
}
|
||||
|
||||
|
@ -717,7 +739,7 @@ static void* cdreader_open_track(const char* path, uint32_t track)
|
|||
|
||||
static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes)
|
||||
{
|
||||
size_t sector_start;
|
||||
int64_t sector_start;
|
||||
size_t num_read, total_read = 0;
|
||||
uint8_t* buffer_ptr = (uint8_t*)buffer;
|
||||
|
||||
|
@ -725,7 +747,7 @@ static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* bu
|
|||
if (!cdrom)
|
||||
return 0;
|
||||
|
||||
sector_start = sector * cdrom->sector_size + cdrom->sector_header_size + cdrom->first_sector_offset;
|
||||
sector_start = (int64_t)sector * cdrom->sector_size + cdrom->sector_header_size + cdrom->first_sector_offset;
|
||||
|
||||
while (requested_bytes > 2048)
|
||||
{
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "md5.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
|
||||
/* arbitrary limit to prevent allocating and hashing large files */
|
||||
|
@ -50,14 +51,27 @@ static void* filereader_open(const char* path)
|
|||
return fopen(path, "rb");
|
||||
}
|
||||
|
||||
static void filereader_seek(void* file_handle, size_t offset, int origin)
|
||||
static void filereader_seek(void* file_handle, int64_t offset, int origin)
|
||||
{
|
||||
fseek((FILE*)file_handle, (long)offset, origin);
|
||||
#if defined(_WIN32)
|
||||
_fseeki64((FILE*)file_handle, offset, origin);
|
||||
#elif defined(_LARGEFILE64_SOURCE)
|
||||
fseeko64((FILE*)file_handle, offset, origin);
|
||||
#else
|
||||
#pragma message("Using generic fseek may fail for large files")
|
||||
fseek((FILE*)file_handle, offset, origin);
|
||||
#endif
|
||||
}
|
||||
|
||||
static size_t filereader_tell(void* file_handle)
|
||||
static int64_t filereader_tell(void* file_handle)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
return _ftelli64((FILE*)file_handle);
|
||||
#elif defined(_LARGEFILE64_SOURCE)
|
||||
return ftello64((FILE*)file_handle);
|
||||
#else
|
||||
return ftell((FILE*)file_handle);
|
||||
#endif
|
||||
}
|
||||
|
||||
static size_t filereader_read(void* file_handle, void* buffer, size_t requested_bytes)
|
||||
|
@ -118,13 +132,13 @@ void* rc_file_open(const char* path)
|
|||
return handle;
|
||||
}
|
||||
|
||||
void rc_file_seek(void* file_handle, size_t offset, int origin)
|
||||
void rc_file_seek(void* file_handle, int64_t offset, int origin)
|
||||
{
|
||||
if (filereader)
|
||||
filereader->seek(file_handle, offset, origin);
|
||||
}
|
||||
|
||||
size_t rc_file_tell(void* file_handle)
|
||||
int64_t rc_file_tell(void* file_handle)
|
||||
{
|
||||
return (filereader) ? filereader->tell(file_handle) : 0;
|
||||
}
|
||||
|
@ -367,6 +381,50 @@ static int rc_hash_buffer(char hash[33], uint8_t* buffer, size_t buffer_size)
|
|||
return rc_hash_finalize(&md5, hash);
|
||||
}
|
||||
|
||||
static int rc_hash_cd_file(md5_state_t* md5, void* track_handle, uint32_t sector, const char* name, unsigned size, const char* description)
|
||||
{
|
||||
uint8_t buffer[2048];
|
||||
size_t num_read;
|
||||
|
||||
if ((num_read = rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer))) < sizeof(buffer))
|
||||
{
|
||||
char message[128];
|
||||
snprintf(message, sizeof(message), "Could not read %s", description);
|
||||
return rc_hash_error(message);
|
||||
}
|
||||
|
||||
if (size > MAX_BUFFER_SIZE)
|
||||
size = MAX_BUFFER_SIZE;
|
||||
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
char message[128];
|
||||
if (name)
|
||||
snprintf(message, sizeof(message), "Hashing %s title (%u bytes) and contents (%u bytes) ", name, (unsigned)strlen(name), size);
|
||||
else
|
||||
snprintf(message, sizeof(message), "Hashing %s contents (%u bytes)", description, size);
|
||||
|
||||
verbose_message_callback(message);
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
md5_append(md5, buffer, (int)num_read);
|
||||
|
||||
size -= (unsigned)num_read;
|
||||
if (size == 0)
|
||||
break;
|
||||
|
||||
++sector;
|
||||
if (size >= sizeof(buffer))
|
||||
num_read = rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer));
|
||||
else
|
||||
num_read = rc_cd_read_sector(track_handle, sector, buffer, size);
|
||||
} while (num_read > 0);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int rc_hash_3do(char hash[33], const char* path)
|
||||
{
|
||||
uint8_t buffer[2048];
|
||||
|
@ -500,6 +558,20 @@ static int rc_hash_3do(char hash[33], const char* path)
|
|||
return rc_hash_finalize(&md5, hash);
|
||||
}
|
||||
|
||||
static int rc_hash_7800(char hash[33], uint8_t* buffer, size_t buffer_size)
|
||||
{
|
||||
/* if the file contains a header, ignore it */
|
||||
if (memcmp(&buffer[1], "ATARI7800", 9) == 0)
|
||||
{
|
||||
rc_hash_verbose("Ignoring 7800 header");
|
||||
|
||||
buffer += 128;
|
||||
buffer_size -= 128;
|
||||
}
|
||||
|
||||
return rc_hash_buffer(hash, buffer, buffer_size);
|
||||
}
|
||||
|
||||
static int rc_hash_arcade(char hash[33], const char* path)
|
||||
{
|
||||
/* arcade hash is just the hash of the filename (no extension) - the cores are pretty stringent about having the right ROM data */
|
||||
|
@ -614,7 +686,7 @@ static int rc_hash_nintendo_ds(char hash[33], const char* path)
|
|||
uint8_t* hash_buffer;
|
||||
unsigned int hash_size, arm9_size, arm9_addr, arm7_size, arm7_addr, icon_addr;
|
||||
size_t num_read;
|
||||
int offset = 0;
|
||||
int64_t offset = 0;
|
||||
md5_state_t md5;
|
||||
void* file_handle;
|
||||
|
||||
|
@ -745,7 +817,10 @@ static int rc_hash_pce_track(char hash[33], void* track_handle)
|
|||
* the string "PC Engine CD-ROM SYSTEM" should exist at 32 bytes into the sector
|
||||
* http://shu.sheldows.com/shu/download/pcedocs/pce_cdrom.html
|
||||
*/
|
||||
rc_cd_read_sector(track_handle, 1, buffer, 128);
|
||||
if (rc_cd_read_sector(track_handle, 1, buffer, 128) < 128)
|
||||
{
|
||||
return rc_hash_error("Not a PC Engine CD");
|
||||
}
|
||||
|
||||
/* normal PC Engine CD will have a header block in sector 1 */
|
||||
if (memcmp("PC Engine CD-ROM SYSTEM", &buffer[32], 23) == 0)
|
||||
|
@ -914,13 +989,13 @@ static int rc_hash_pcfx_cd(char hash[33], const char* path)
|
|||
|
||||
static int rc_hash_dreamcast(char hash[33], const char* path)
|
||||
{
|
||||
uint8_t buffer[2048];
|
||||
uint8_t buffer[256];
|
||||
void* track_handle;
|
||||
void* last_track_handle;
|
||||
char exe_file[32] = "";
|
||||
unsigned size;
|
||||
size_t num_read = 0;
|
||||
uint32_t sector;
|
||||
uint32_t track_sector;
|
||||
int result = 0;
|
||||
md5_state_t md5;
|
||||
int i = 0;
|
||||
|
@ -940,6 +1015,7 @@ static int rc_hash_dreamcast(char hash[33], const char* path)
|
|||
return rc_hash_error("Not a Dreamcast CD");
|
||||
}
|
||||
|
||||
/* start the hash with the game meta information */
|
||||
md5_init(&md5);
|
||||
md5_append(&md5, (md5_byte_t*)buffer, 256);
|
||||
|
||||
|
@ -955,6 +1031,7 @@ static int rc_hash_dreamcast(char hash[33], const char* path)
|
|||
verbose_message_callback(message);
|
||||
}
|
||||
|
||||
/* the boot filename is 96 bytes into the meta information (https://mc.pp.se/dc/ip0000.bin.html) */
|
||||
/* remove whitespace from bootfile */
|
||||
i = 0;
|
||||
while (!isspace(buffer[96 + i]) && i < 16)
|
||||
|
@ -980,50 +1057,98 @@ static int rc_hash_dreamcast(char hash[33], const char* path)
|
|||
|
||||
/* last track contains the boot executable */
|
||||
last_track_handle = rc_cd_open_track(path, RC_HASH_CDTRACK_LAST);
|
||||
track_sector = rc_cd_absolute_sector_to_track_sector(last_track_handle, sector);
|
||||
|
||||
sector = rc_cd_absolute_sector_to_track_sector(last_track_handle, sector);
|
||||
|
||||
if ((num_read = rc_cd_read_sector(last_track_handle, sector, buffer, sizeof(buffer))) < sizeof(buffer))
|
||||
rc_hash_error("Could not read boot executable");
|
||||
|
||||
if (size > MAX_BUFFER_SIZE)
|
||||
size = MAX_BUFFER_SIZE;
|
||||
|
||||
if (verbose_message_callback)
|
||||
if ((int32_t)track_sector < 0)
|
||||
{
|
||||
char message[128];
|
||||
snprintf(message, sizeof(message), "Hashing %s contents (%u bytes)", exe_file, size);
|
||||
verbose_message_callback(message);
|
||||
/* boot executable is not in the last track; try the primary data track.
|
||||
* There's only a handful of games that do this: Q*bert was the first identified. */
|
||||
rc_cd_close_track(last_track_handle);
|
||||
|
||||
rc_hash_verbose("Boot executable not found in last track, trying primary track");
|
||||
last_track_handle = rc_cd_open_track(path, 3);
|
||||
track_sector = rc_cd_absolute_sector_to_track_sector(last_track_handle, sector);
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
md5_append(&md5, buffer, (int)num_read);
|
||||
|
||||
size -= (unsigned)num_read;
|
||||
if (size == 0)
|
||||
break;
|
||||
|
||||
++sector;
|
||||
if (size >= sizeof(buffer))
|
||||
num_read = rc_cd_read_sector(last_track_handle, sector, buffer, sizeof(buffer));
|
||||
else
|
||||
num_read = rc_cd_read_sector(last_track_handle, sector, buffer, size);
|
||||
} while (num_read > 0);
|
||||
result = rc_hash_cd_file(&md5, last_track_handle, track_sector, NULL, size, "boot executable");
|
||||
|
||||
rc_cd_close_track(last_track_handle);
|
||||
|
||||
result = rc_hash_finalize(&md5, hash);
|
||||
|
||||
rc_hash_finalize(&md5, hash);
|
||||
return result;
|
||||
}
|
||||
|
||||
static int rc_hash_find_playstation_executable(void* track_handle, const char* boot_key, const char* cdrom_prefix,
|
||||
char exe_name[], unsigned exe_name_size, unsigned* exe_size)
|
||||
{
|
||||
uint8_t buffer[2048];
|
||||
unsigned size;
|
||||
char* ptr;
|
||||
char* start;
|
||||
const size_t boot_key_len = strlen(boot_key);
|
||||
const size_t cdrom_prefix_len = strlen(cdrom_prefix);
|
||||
int sector;
|
||||
|
||||
sector = rc_cd_find_file_sector(track_handle, "SYSTEM.CNF", NULL);
|
||||
if (!sector)
|
||||
return 0;
|
||||
|
||||
size = (unsigned)rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer) - 1);
|
||||
buffer[size] = '\0';
|
||||
|
||||
for (ptr = (char*)buffer; *ptr; ++ptr)
|
||||
{
|
||||
if (strncmp(ptr, boot_key, boot_key_len) == 0)
|
||||
{
|
||||
ptr += boot_key_len;
|
||||
while (isspace(*ptr))
|
||||
++ptr;
|
||||
|
||||
if (*ptr == '=')
|
||||
{
|
||||
++ptr;
|
||||
while (isspace(*ptr))
|
||||
++ptr;
|
||||
|
||||
if (strncmp(ptr, cdrom_prefix, cdrom_prefix_len) == 0)
|
||||
ptr += cdrom_prefix_len;
|
||||
if (*ptr == '\\')
|
||||
++ptr;
|
||||
|
||||
start = ptr;
|
||||
while (!isspace(*ptr) && *ptr != ';')
|
||||
++ptr;
|
||||
|
||||
size = (unsigned)(ptr - start);
|
||||
if (size >= exe_name_size)
|
||||
size = exe_name_size - 1;
|
||||
|
||||
memcpy(exe_name, start, size);
|
||||
exe_name[size] = '\0';
|
||||
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
snprintf((char*)buffer, sizeof(buffer), "Looking for boot executable: %s", exe_name);
|
||||
verbose_message_callback((const char*)buffer);
|
||||
}
|
||||
|
||||
sector = rc_cd_find_file_sector(track_handle, exe_name, exe_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* advance to end of line */
|
||||
while (*ptr && *ptr != '\n')
|
||||
++ptr;
|
||||
}
|
||||
|
||||
return sector;
|
||||
}
|
||||
|
||||
static int rc_hash_psx(char hash[33], const char* path)
|
||||
{
|
||||
uint8_t buffer[2048];
|
||||
uint8_t buffer[32];
|
||||
char exe_name[64] = "";
|
||||
char* ptr;
|
||||
char* start;
|
||||
void* track_handle;
|
||||
uint32_t sector;
|
||||
unsigned size;
|
||||
|
@ -1035,63 +1160,12 @@ static int rc_hash_psx(char hash[33], const char* path)
|
|||
if (!track_handle)
|
||||
return rc_hash_error("Could not open track");
|
||||
|
||||
sector = rc_cd_find_file_sector(track_handle, "SYSTEM.CNF", NULL);
|
||||
sector = rc_hash_find_playstation_executable(track_handle, "BOOT", "cdrom:", exe_name, sizeof(exe_name), &size);
|
||||
if (!sector)
|
||||
{
|
||||
sector = rc_cd_find_file_sector(track_handle, "PSX.EXE", &size);
|
||||
if (sector)
|
||||
strcpy(exe_name, "PSX.EXE");
|
||||
}
|
||||
else
|
||||
{
|
||||
size = (unsigned)rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer) - 1);
|
||||
buffer[size] = '\0';
|
||||
|
||||
for (ptr = (char*)buffer; *ptr; ++ptr)
|
||||
{
|
||||
if (strncmp(ptr, "BOOT", 4) == 0)
|
||||
{
|
||||
ptr += 4;
|
||||
while (isspace(*ptr))
|
||||
++ptr;
|
||||
|
||||
if (*ptr == '=')
|
||||
{
|
||||
++ptr;
|
||||
while (isspace(*ptr))
|
||||
++ptr;
|
||||
|
||||
if (strncmp(ptr, "cdrom:", 6) == 0)
|
||||
ptr += 6;
|
||||
if (*ptr == '\\')
|
||||
++ptr;
|
||||
|
||||
start = ptr;
|
||||
while (!isspace(*ptr) && *ptr != ';')
|
||||
++ptr;
|
||||
|
||||
size = (unsigned)(ptr - start);
|
||||
if (size >= sizeof(exe_name))
|
||||
size = sizeof(exe_name) - 1;
|
||||
|
||||
memcpy(exe_name, start, size);
|
||||
exe_name[size] = '\0';
|
||||
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
snprintf((char*)buffer, sizeof(buffer), "Looking for boot executable: %s", exe_name);
|
||||
verbose_message_callback((const char*)buffer);
|
||||
}
|
||||
|
||||
sector = rc_cd_find_file_sector(track_handle, exe_name, &size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* advance to end of line */
|
||||
while (*ptr && *ptr != '\n')
|
||||
++ptr;
|
||||
}
|
||||
memcpy(exe_name, "PSX.EXE", 8);
|
||||
}
|
||||
|
||||
if (!sector)
|
||||
|
@ -1121,38 +1195,65 @@ static int rc_hash_psx(char hash[33], const char* path)
|
|||
size = (((uint8_t)buffer[31] << 24) | ((uint8_t)buffer[30] << 16) | ((uint8_t)buffer[29] << 8) | (uint8_t)buffer[28]) + 2048;
|
||||
}
|
||||
|
||||
if (size > MAX_BUFFER_SIZE)
|
||||
size = MAX_BUFFER_SIZE;
|
||||
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
char message[128];
|
||||
snprintf(message, sizeof(message), "Hashing %s title (%u bytes) and contents (%u bytes) ", exe_name, (unsigned)strlen(exe_name), size);
|
||||
verbose_message_callback(message);
|
||||
}
|
||||
|
||||
/* there's also a few games that are use a singular engine and only differ via their data files. luckily, they have
|
||||
* unique serial numbers, and use the serial number as the boot file in the standard way. include the boot file in the hash
|
||||
/* there's a few games that use a singular engine and only differ via their data files. luckily, they have unique
|
||||
* serial numbers, and use the serial number as the boot file in the standard way. include the boot file name in the hash.
|
||||
*/
|
||||
md5_init(&md5);
|
||||
md5_append(&md5, (md5_byte_t*)exe_name, (int)strlen(exe_name));
|
||||
|
||||
do
|
||||
result = rc_hash_cd_file(&md5, track_handle, sector, exe_name, size, "primary executable");
|
||||
rc_hash_finalize(&md5, hash);
|
||||
}
|
||||
|
||||
rc_cd_close_track(track_handle);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int rc_hash_ps2(char hash[33], const char* path)
|
||||
{
|
||||
uint8_t buffer[4];
|
||||
char exe_name[64] = "";
|
||||
void* track_handle;
|
||||
uint32_t sector;
|
||||
unsigned size;
|
||||
size_t num_read;
|
||||
int result = 0;
|
||||
md5_state_t md5;
|
||||
|
||||
track_handle = rc_cd_open_track(path, 1);
|
||||
if (!track_handle)
|
||||
return rc_hash_error("Could not open track");
|
||||
|
||||
sector = rc_hash_find_playstation_executable(track_handle, "BOOT2", "cdrom0:", exe_name, sizeof(exe_name), &size);
|
||||
if (!sector)
|
||||
{
|
||||
rc_hash_error("Could not locate primary executable");
|
||||
}
|
||||
else if ((num_read = rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer))) < sizeof(buffer))
|
||||
{
|
||||
rc_hash_error("Could not read primary executable");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (memcmp(buffer, "\x7f\x45\x4c\x46", 4) != 0)
|
||||
{
|
||||
md5_append(&md5, buffer, (int)num_read);
|
||||
if (verbose_message_callback)
|
||||
{
|
||||
char message[128];
|
||||
snprintf(message, sizeof(message), "%s did not contain ELF marker", exe_name);
|
||||
verbose_message_callback(message);
|
||||
}
|
||||
}
|
||||
|
||||
size -= (unsigned)num_read;
|
||||
if (size == 0)
|
||||
break;
|
||||
/* there's a few games that use a singular engine and only differ via their data files. luckily, they have unique
|
||||
* serial numbers, and use the serial number as the boot file in the standard way. include the boot file name in the hash.
|
||||
*/
|
||||
md5_init(&md5);
|
||||
md5_append(&md5, (md5_byte_t*)exe_name, (int)strlen(exe_name));
|
||||
|
||||
++sector;
|
||||
if (size >= sizeof(buffer))
|
||||
num_read = rc_cd_read_sector(track_handle, sector, buffer, sizeof(buffer));
|
||||
else
|
||||
num_read = rc_cd_read_sector(track_handle, sector, buffer, size);
|
||||
} while (num_read > 0);
|
||||
|
||||
result = rc_hash_finalize(&md5, hash);
|
||||
result = rc_hash_cd_file(&md5, track_handle, sector, exe_name, size, "primary executable");
|
||||
rc_hash_finalize(&md5, hash);
|
||||
}
|
||||
|
||||
rc_cd_close_track(track_handle);
|
||||
|
@ -1217,7 +1318,6 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer,
|
|||
|
||||
case RC_CONSOLE_APPLE_II:
|
||||
case RC_CONSOLE_ATARI_2600:
|
||||
case RC_CONSOLE_ATARI_7800:
|
||||
case RC_CONSOLE_ATARI_JAGUAR:
|
||||
case RC_CONSOLE_COLECOVISION:
|
||||
case RC_CONSOLE_GAMEBOY:
|
||||
|
@ -1236,11 +1336,16 @@ int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer,
|
|||
case RC_CONSOLE_POKEMON_MINI:
|
||||
case RC_CONSOLE_SEGA_32X:
|
||||
case RC_CONSOLE_SG1000:
|
||||
case RC_CONSOLE_SUPERVISION:
|
||||
case RC_CONSOLE_TIC80:
|
||||
case RC_CONSOLE_VECTREX:
|
||||
case RC_CONSOLE_VIRTUAL_BOY:
|
||||
case RC_CONSOLE_WONDERSWAN:
|
||||
return rc_hash_buffer(hash, buffer, buffer_size);
|
||||
|
||||
case RC_CONSOLE_ATARI_7800:
|
||||
return rc_hash_7800(hash, buffer, buffer_size);
|
||||
|
||||
case RC_CONSOLE_ATARI_LYNX:
|
||||
return rc_hash_lynx(hash, buffer, buffer_size);
|
||||
|
||||
|
@ -1259,9 +1364,10 @@ static int rc_hash_whole_file(char hash[33], int console_id, const char* path)
|
|||
{
|
||||
md5_state_t md5;
|
||||
uint8_t* buffer;
|
||||
size_t size;
|
||||
int64_t size;
|
||||
const size_t buffer_size = 65536;
|
||||
void* file_handle;
|
||||
size_t remaining;
|
||||
int result = 0;
|
||||
|
||||
file_handle = rc_file_open(path);
|
||||
|
@ -1282,7 +1388,9 @@ static int rc_hash_whole_file(char hash[33], int console_id, const char* path)
|
|||
}
|
||||
|
||||
if (size > MAX_BUFFER_SIZE)
|
||||
size = MAX_BUFFER_SIZE;
|
||||
remaining = MAX_BUFFER_SIZE;
|
||||
else
|
||||
remaining = (size_t)size;
|
||||
|
||||
md5_init(&md5);
|
||||
|
||||
|
@ -1290,17 +1398,17 @@ static int rc_hash_whole_file(char hash[33], int console_id, const char* path)
|
|||
if (buffer)
|
||||
{
|
||||
rc_file_seek(file_handle, 0, SEEK_SET);
|
||||
while (size >= buffer_size)
|
||||
while (remaining >= buffer_size)
|
||||
{
|
||||
rc_file_read(file_handle, buffer, (int)buffer_size);
|
||||
md5_append(&md5, buffer, (int)buffer_size);
|
||||
size -= buffer_size;
|
||||
remaining -= buffer_size;
|
||||
}
|
||||
|
||||
if (size > 0)
|
||||
if (remaining > 0)
|
||||
{
|
||||
rc_file_read(file_handle, buffer, (int)size);
|
||||
md5_append(&md5, buffer, (int)size);
|
||||
rc_file_read(file_handle, buffer, (int)remaining);
|
||||
md5_append(&md5, buffer, (int)remaining);
|
||||
}
|
||||
|
||||
free(buffer);
|
||||
|
@ -1314,7 +1422,7 @@ static int rc_hash_whole_file(char hash[33], int console_id, const char* path)
|
|||
static int rc_hash_buffered_file(char hash[33], int console_id, const char* path)
|
||||
{
|
||||
uint8_t* buffer;
|
||||
size_t size;
|
||||
int64_t size;
|
||||
int result = 0;
|
||||
void* file_handle;
|
||||
|
||||
|
@ -1338,13 +1446,13 @@ static int rc_hash_buffered_file(char hash[33], int console_id, const char* path
|
|||
if (size > MAX_BUFFER_SIZE)
|
||||
size = MAX_BUFFER_SIZE;
|
||||
|
||||
buffer = (uint8_t*)malloc(size);
|
||||
buffer = (uint8_t*)malloc((size_t)size);
|
||||
if (buffer)
|
||||
{
|
||||
rc_file_seek(file_handle, 0, SEEK_SET);
|
||||
rc_file_read(file_handle, buffer, (int)size);
|
||||
|
||||
result = rc_hash_generate_from_buffer(hash, console_id, buffer, size);
|
||||
result = rc_hash_generate_from_buffer(hash, console_id, buffer, (size_t)size);
|
||||
|
||||
free(buffer);
|
||||
}
|
||||
|
@ -1492,7 +1600,6 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
|
|||
|
||||
case RC_CONSOLE_APPLE_II:
|
||||
case RC_CONSOLE_ATARI_2600:
|
||||
case RC_CONSOLE_ATARI_7800:
|
||||
case RC_CONSOLE_ATARI_JAGUAR:
|
||||
case RC_CONSOLE_COLECOVISION:
|
||||
case RC_CONSOLE_GAMEBOY:
|
||||
|
@ -1509,6 +1616,8 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
|
|||
case RC_CONSOLE_POKEMON_MINI:
|
||||
case RC_CONSOLE_SEGA_32X:
|
||||
case RC_CONSOLE_SG1000:
|
||||
case RC_CONSOLE_SUPERVISION:
|
||||
case RC_CONSOLE_TIC80:
|
||||
case RC_CONSOLE_VECTREX:
|
||||
case RC_CONSOLE_VIRTUAL_BOY:
|
||||
case RC_CONSOLE_WONDERSWAN:
|
||||
|
@ -1523,6 +1632,7 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
|
|||
|
||||
return rc_hash_whole_file(hash, console_id, path);
|
||||
|
||||
case RC_CONSOLE_ATARI_7800:
|
||||
case RC_CONSOLE_ATARI_LYNX:
|
||||
case RC_CONSOLE_NINTENDO:
|
||||
case RC_CONSOLE_SUPER_NINTENDO:
|
||||
|
@ -1562,6 +1672,12 @@ int rc_hash_generate_from_file(char hash[33], int console_id, const char* path)
|
|||
|
||||
return rc_hash_psx(hash, path);
|
||||
|
||||
case RC_CONSOLE_PLAYSTATION_2:
|
||||
if (rc_path_compare_extension(path, "m3u"))
|
||||
return rc_hash_generate_from_playlist(hash, console_id, path);
|
||||
|
||||
return rc_hash_ps2(hash, path);
|
||||
|
||||
case RC_CONSOLE_DREAMCAST:
|
||||
if (rc_path_compare_extension(path, "m3u"))
|
||||
return rc_hash_generate_from_playlist(hash, console_id, path);
|
||||
|
@ -1601,7 +1717,7 @@ static void rc_hash_initialize_dsk_iterator(struct rc_hash_iterator* iterator, c
|
|||
if (file)
|
||||
{
|
||||
rc_file_seek(file, 0, SEEK_END);
|
||||
size = rc_file_tell(file);
|
||||
size = (size_t)rc_file_tell(file);
|
||||
rc_file_close(file);
|
||||
}
|
||||
}
|
||||
|
@ -1655,6 +1771,13 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
const char* ext = rc_path_get_extension(path);
|
||||
switch (tolower(*ext))
|
||||
{
|
||||
case '2':
|
||||
if (rc_path_compare_extension(ext, "2d"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_SHARPX1;
|
||||
}
|
||||
break;
|
||||
|
||||
case '7':
|
||||
if (rc_path_compare_extension(ext, "7z"))
|
||||
{
|
||||
|
@ -1680,7 +1803,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
void* file = rc_file_open(path);
|
||||
if (file)
|
||||
{
|
||||
size_t size;
|
||||
int64_t size;
|
||||
|
||||
rc_file_seek(file, 0, SEEK_END);
|
||||
size = rc_file_tell(file);
|
||||
|
@ -1690,17 +1813,18 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_3DO; /* 4DO supports directly opening the bin file */
|
||||
iterator->consoles[1] = RC_CONSOLE_PLAYSTATION; /* PCSX ReARMed supports directly opening the bin file*/
|
||||
iterator->consoles[2] = RC_CONSOLE_SEGA_CD; /* Genesis Plus GX supports directly opening the bin file*/
|
||||
iterator->consoles[2] = RC_CONSOLE_PLAYSTATION_2; /* PCSX2 supports directly opening the bin file*/
|
||||
iterator->consoles[3] = RC_CONSOLE_SEGA_CD; /* Genesis Plus GX supports directly opening the bin file*/
|
||||
|
||||
/* fallback to megadrive which just does a full hash */
|
||||
iterator->consoles[3] = RC_CONSOLE_MEGA_DRIVE;
|
||||
iterator->consoles[4] = RC_CONSOLE_MEGA_DRIVE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* bin is associated with MegaDrive, Sega32X and Atari 2600. Since they all use the same
|
||||
* hashing algorithm, only specify one of them */
|
||||
/* bin is associated with MegaDrive, Sega32X, Atari 2600, and Watara Supervision.
|
||||
* Since they all use the same hashing algorithm, only specify one of them */
|
||||
iterator->consoles[0] = RC_CONSOLE_MEGA_DRIVE;
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "bs"))
|
||||
|
@ -1713,20 +1837,22 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
if (rc_path_compare_extension(ext, "cue"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_PLAYSTATION;
|
||||
iterator->consoles[1] = RC_CONSOLE_PC_ENGINE;
|
||||
iterator->consoles[2] = RC_CONSOLE_3DO;
|
||||
iterator->consoles[3] = RC_CONSOLE_PCFX;
|
||||
iterator->consoles[4] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
|
||||
iterator->consoles[1] = RC_CONSOLE_PLAYSTATION_2;
|
||||
iterator->consoles[2] = RC_CONSOLE_PC_ENGINE;
|
||||
iterator->consoles[3] = RC_CONSOLE_3DO;
|
||||
iterator->consoles[4] = RC_CONSOLE_PCFX;
|
||||
iterator->consoles[5] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
|
||||
need_path = 1;
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "chd"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_PLAYSTATION;
|
||||
iterator->consoles[1] = RC_CONSOLE_DREAMCAST;
|
||||
iterator->consoles[2] = RC_CONSOLE_PC_ENGINE;
|
||||
iterator->consoles[3] = RC_CONSOLE_3DO;
|
||||
iterator->consoles[4] = RC_CONSOLE_PCFX;
|
||||
iterator->consoles[5] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
|
||||
iterator->consoles[1] = RC_CONSOLE_PLAYSTATION_2;
|
||||
iterator->consoles[2] = RC_CONSOLE_DREAMCAST;
|
||||
iterator->consoles[3] = RC_CONSOLE_PC_ENGINE;
|
||||
iterator->consoles[4] = RC_CONSOLE_3DO;
|
||||
iterator->consoles[5] = RC_CONSOLE_PCFX;
|
||||
iterator->consoles[6] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
|
||||
need_path = 1;
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "col"))
|
||||
|
@ -1747,6 +1873,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
else if (rc_path_compare_extension(ext, "d88"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_PC8800;
|
||||
iterator->consoles[1] = RC_CONSOLE_SHARPX1;
|
||||
}
|
||||
break;
|
||||
|
||||
|
@ -1759,6 +1886,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_NINTENDO;
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "fd"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */
|
||||
}
|
||||
break;
|
||||
|
||||
case 'g':
|
||||
|
@ -1787,8 +1918,9 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
case 'i':
|
||||
if (rc_path_compare_extension(ext, "iso"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_3DO;
|
||||
iterator->consoles[1] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
|
||||
iterator->consoles[0] = RC_CONSOLE_PLAYSTATION_2;
|
||||
iterator->consoles[1] = RC_CONSOLE_3DO;
|
||||
iterator->consoles[2] = RC_CONSOLE_SEGA_CD; /* ASSERT: handles both Sega CD and Saturn */
|
||||
need_path = 1;
|
||||
}
|
||||
break;
|
||||
|
@ -1800,6 +1932,13 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
}
|
||||
break;
|
||||
|
||||
case 'k':
|
||||
if (rc_path_compare_extension(ext, "k7"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* tape */
|
||||
}
|
||||
break;
|
||||
|
||||
case 'l':
|
||||
if (rc_path_compare_extension(ext, "lnx"))
|
||||
{
|
||||
|
@ -1835,6 +1974,14 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_MSX;
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "m5"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* cartridge */
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "m7"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* cartridge */
|
||||
}
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
|
@ -1868,6 +2015,7 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
if (rc_path_compare_extension(ext, "rom"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_MSX;
|
||||
iterator->consoles[1] = RC_CONSOLE_THOMSONTO8; /* cartridge */
|
||||
}
|
||||
if (rc_path_compare_extension(ext, "ri"))
|
||||
{
|
||||
|
@ -1890,6 +2038,14 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_PC_ENGINE;
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "sv"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_SUPERVISION;
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "sap"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_THOMSONTO8; /* disk */
|
||||
}
|
||||
break;
|
||||
|
||||
case 't':
|
||||
|
@ -1897,6 +2053,10 @@ void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char*
|
|||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_ORIC;
|
||||
}
|
||||
else if (rc_path_compare_extension(ext, "tic"))
|
||||
{
|
||||
iterator->consoles[0] = RC_CONSOLE_TIC80;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'v':
|
||||
|
|
|
@ -98,7 +98,7 @@ int rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const
|
|||
}
|
||||
|
||||
/* Evaluate the signature. */
|
||||
snprintf(signature, sizeof(signature), "%u%s%u", lboard_id, user_name, lboard_id);
|
||||
snprintf(signature, sizeof(signature), "%u%s%d", lboard_id, user_name, value);
|
||||
md5_init(&state);
|
||||
md5_append(&state, (unsigned char*)signature, (int)strlen(signature));
|
||||
md5_finish(&state, hash);
|
||||
|
@ -298,7 +298,7 @@ int rc_url_append_unum(char* buffer, size_t buffer_size, size_t* buffer_offset,
|
|||
int written = rc_url_append_param_equals(buffer, buffer_size, *buffer_offset, param);
|
||||
if (written > 0) {
|
||||
char num[16];
|
||||
int chars = sprintf(num, "%u", value);
|
||||
int chars = snprintf(num, sizeof(num), "%u", value);
|
||||
|
||||
if (chars + written < (int)buffer_size)
|
||||
{
|
||||
|
|
|
@ -888,7 +888,7 @@ static void GetLbInfoCallback(s32 status_code, const FrontendCommon::HTTPDownloa
|
|||
}
|
||||
|
||||
char score[128];
|
||||
rc_format_value(score, sizeof(score), entry["Score"].GetInt(), leaderboard->format);
|
||||
rc_runtime_format_lboard_value(score, sizeof(score), entry["Score"].GetInt(), leaderboard->format);
|
||||
|
||||
LeaderboardEntry lbe;
|
||||
lbe.user = entry["User"].GetString();
|
||||
|
@ -1374,6 +1374,13 @@ void SubmitLeaderboard(u32 leaderboard_id, int value)
|
|||
s_http_downloader->CreateRequest(url, SubmitLeaderboardCallback);
|
||||
}
|
||||
|
||||
std::pair<u32, u32> GetAchievementProgress(const Achievement& achievement)
|
||||
{
|
||||
std::pair<u32, u32> result;
|
||||
rc_runtime_get_achievement_measured(&s_rcheevos_runtime, achievement.id, &result.first, &result.second);
|
||||
return result;
|
||||
}
|
||||
|
||||
void CheevosEventHandler(const rc_runtime_event_t* runtime_event)
|
||||
{
|
||||
static const char* events[] = {"RC_RUNTIME_EVENT_ACHIEVEMENT_ACTIVATED", "RC_RUNTIME_EVENT_ACHIEVEMENT_PAUSED",
|
||||
|
|
|
@ -113,6 +113,8 @@ const Leaderboard* GetLeaderboardByID(u32 id);
|
|||
u32 GetLeaderboardCount();
|
||||
bool IsLeaderboardTimeType(const Leaderboard& leaderboard);
|
||||
|
||||
std::pair<u32, u32> GetAchievementProgress(const Achievement& achievement);
|
||||
|
||||
void UnlockAchievement(u32 achievement_id, bool add_notification = true);
|
||||
void SubmitLeaderboard(u32 leaderboard_id, int value);
|
||||
|
||||
|
|
|
@ -4119,14 +4119,22 @@ void DrawDebugDebugMenu()
|
|||
static void DrawAchievement(const Cheevos::Achievement& cheevo)
|
||||
{
|
||||
static constexpr float alpha = 0.8f;
|
||||
static constexpr float progress_height_unscaled = 20.0f;
|
||||
static constexpr float progress_spacing_unscaled = 5.0f;
|
||||
|
||||
TinyString id_str;
|
||||
id_str.Format("%u", cheevo.id);
|
||||
|
||||
const auto progress = Cheevos::GetAchievementProgress(cheevo);
|
||||
const bool is_measured = progress.second != 0;
|
||||
|
||||
ImRect bb;
|
||||
bool visible, hovered;
|
||||
bool pressed =
|
||||
MenuButtonFrame(id_str, true, LAYOUT_MENU_BUTTON_HEIGHT, &visible, &hovered, &bb.Min, &bb.Max, 0, alpha);
|
||||
MenuButtonFrame(id_str, true,
|
||||
!is_measured ? LAYOUT_MENU_BUTTON_HEIGHT :
|
||||
LAYOUT_MENU_BUTTON_HEIGHT + progress_height_unscaled + progress_spacing_unscaled,
|
||||
&visible, &hovered, &bb.Min, &bb.Max, 0, alpha);
|
||||
if (!visible)
|
||||
return;
|
||||
|
||||
|
@ -4162,6 +4170,27 @@ static void DrawAchievement(const Cheevos::Achievement& cheevo)
|
|||
ImGui::PopFont();
|
||||
}
|
||||
|
||||
if (is_measured)
|
||||
{
|
||||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
const float progress_height = LayoutScale(progress_height_unscaled);
|
||||
const float progress_spacing = LayoutScale(progress_spacing_unscaled);
|
||||
const float top = midpoint + g_medium_font->FontSize + progress_spacing;
|
||||
const ImRect progress_bb(ImVec2(text_start_x, top), ImVec2(bb.Max.x, top + progress_height));
|
||||
const float fraction = static_cast<float>(progress.first) / static_cast<float>(progress.second);
|
||||
dl->AddRectFilled(progress_bb.Min, progress_bb.Max, ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryDarkColor()));
|
||||
dl->AddRectFilled(progress_bb.Min, ImVec2(progress_bb.Min.x + fraction * progress_bb.GetWidth(), progress_bb.Max.y),
|
||||
ImGui::GetColorU32(ImGuiFullscreen::UISecondaryColor()));
|
||||
|
||||
text.Format("%u / %u", progress.first, progress.second);
|
||||
const ImVec2 text_size = ImGui::CalcTextSize(text);
|
||||
const ImVec2 text_pos(progress_bb.Min.x + ((progress_bb.Max.x - progress_bb.Min.x) / 2.0f) - (text_size.x / 2.0f),
|
||||
progress_bb.Min.y + ((progress_bb.Max.y - progress_bb.Min.y) / 2.0f) - (text_size.y / 2.0f));
|
||||
dl->AddText(g_medium_font, g_medium_font->FontSize, text_pos,
|
||||
ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryTextColor()), text.GetCharArray(),
|
||||
text.GetCharArray() + text.GetLength());
|
||||
}
|
||||
|
||||
#if 0
|
||||
// The API doesn't seem to send us this :(
|
||||
if (!cheevo.locked)
|
||||
|
@ -4458,6 +4487,7 @@ void DrawLeaderboardsWindow()
|
|||
const ImVec2 display_size(ImGui::GetIO().DisplaySize);
|
||||
const float padding = LayoutScale(10.0f);
|
||||
const float spacing = LayoutScale(10.0f);
|
||||
const float spacing_small = spacing / 2.0f;
|
||||
float heading_height = LayoutScale(heading_height_unscaled);
|
||||
if (is_leaderboard_open)
|
||||
{
|
||||
|
@ -4545,7 +4575,7 @@ void DrawLeaderboardsWindow()
|
|||
const ImRect subtitle_bb(ImVec2(left, top), ImVec2(right, top + g_large_font->FontSize));
|
||||
text.Assign(lboard->title);
|
||||
|
||||
top += g_large_font->FontSize + spacing;
|
||||
top += g_large_font->FontSize + spacing_small;
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::RenderTextClipped(subtitle_bb.Min, subtitle_bb.Max, text.GetCharArray(),
|
||||
|
@ -4565,7 +4595,7 @@ void DrawLeaderboardsWindow()
|
|||
}
|
||||
|
||||
const ImRect summary_bb(ImVec2(left, top), ImVec2(right, top + g_medium_font->FontSize));
|
||||
top += g_medium_font->FontSize + spacing;
|
||||
top += g_medium_font->FontSize + spacing_small;
|
||||
|
||||
ImGui::PushFont(g_medium_font);
|
||||
ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, text.GetCharArray(),
|
||||
|
@ -4574,7 +4604,7 @@ void DrawLeaderboardsWindow()
|
|||
if (!IsCheevosHardcoreModeActive())
|
||||
{
|
||||
const ImRect hardcore_warning_bb(ImVec2(left, top), ImVec2(right, top + g_medium_font->FontSize));
|
||||
top += g_medium_font->FontSize + spacing;
|
||||
top += g_medium_font->FontSize + spacing_small;
|
||||
|
||||
ImGui::RenderTextClipped(
|
||||
hardcore_warning_bb.Min, hardcore_warning_bb.Max,
|
||||
|
|
Loading…
Reference in a new issue