Initial commit

main
Pk11 3 years ago
commit 41c9a75219

4
.gitignore vendored

@ -0,0 +1,4 @@
build
*.elf
*.nds
*.DS_Store

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2018 RetroAchievements.org
Copyright (c) 2022 Pk11
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,220 @@
#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif
# These set the information text in the nds file
#GAME_TITLE := My Wonderful Homebrew
#GAME_SUBTITLE1 := built with devkitARM
#GAME_SUBTITLE2 := http://devitpro.org
include $(DEVKITARM)/ds_rules
#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# INCLUDES is a list of directories containing extra header files
# DATA is a list of directories containing binary files embedded using bin2o
# GRAPHICS is a list of directories containing image files to be converted with grit
# AUDIO is a list of directories containing audio to be converted by maxmod
# ICON is the image used to create the game icon, leave blank to use default rule
# NITRO is a directory that will be accessible via NitroFS
#---------------------------------------------------------------------------------
TARGET := $(shell basename $(CURDIR))
BUILD := build
SOURCES := source source/rcheevos source/rhash
INCLUDES := include
DATA := data
GRAPHICS :=
AUDIO :=
ICON :=
# specify a directory which contains the nitro filesystem
# this is relative to the Makefile
NITRO :=
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -marm -mthumb-interwork -march=armv5te -mtune=arm946e-s
CFLAGS := -g -Wall -O3\
$(ARCH) $(INCLUDE) -DARM9 -DRC_DISABLE_LUA
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=ds_arm9.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project (order is important)
#---------------------------------------------------------------------------------
LIBS := -lfat -lnds9
# automatigically add libraries for NitroFS
ifneq ($(strip $(NITRO)),)
LIBS := -lfilesystem -lfat $(LIBS)
endif
# automagically add maxmod library
ifneq ($(strip $(AUDIO)),)
LIBS := -lmm9 $(LIBS)
endif
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS := $(LIBNDS) $(PORTLIBS)
#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export VPATH := $(CURDIR)/$(subst /,,$(dir $(ICON)))\
$(foreach dir,$(SOURCES),$(CURDIR)/$(dir))\
$(foreach dir,$(DATA),$(CURDIR)/$(dir))\
$(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
PNGFILES := $(foreach dir,$(GRAPHICS),$(notdir $(wildcard $(dir)/*.png)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
# prepare NitroFS directory
ifneq ($(strip $(NITRO)),)
export NITRO_FILES := $(CURDIR)/$(NITRO)
endif
# get audio list for maxmod
ifneq ($(strip $(AUDIO)),)
export MODFILES := $(foreach dir,$(notdir $(wildcard $(AUDIO)/*.*)),$(CURDIR)/$(AUDIO)/$(dir))
# place the soundbank file in NitroFS if using it
ifneq ($(strip $(NITRO)),)
export SOUNDBANK := $(NITRO_FILES)/soundbank.bin
# otherwise, needs to be loaded from memory
else
export SOUNDBANK := soundbank.bin
BINFILES += $(SOUNDBANK)
endif
endif
#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
export LD := $(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
export LD := $(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(PNGFILES:.png=.o) $(OFILES_BIN) $(OFILES_SOURCES)
export HFILES := $(PNGFILES:.png=.h) $(addsuffix .h,$(subst .,_,$(BINFILES)))
export INCLUDE := $(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir))\
$(foreach dir,$(LIBDIRS),-I$(dir)/include)\
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
ifeq ($(strip $(ICON)),)
icons := $(wildcard *.bmp)
ifneq (,$(findstring $(TARGET).bmp,$(icons)))
export GAME_ICON := $(CURDIR)/$(TARGET).bmp
else
ifneq (,$(findstring icon.bmp,$(icons)))
export GAME_ICON := $(CURDIR)/icon.bmp
endif
endif
else
ifeq ($(suffix $(ICON)), .grf)
export GAME_ICON := $(CURDIR)/$(ICON)
else
export GAME_ICON := $(CURDIR)/$(BUILD)/$(notdir $(basename $(ICON))).grf
endif
endif
.PHONY: $(BUILD) clean
#---------------------------------------------------------------------------------
$(BUILD):
@mkdir -p $@
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#---------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).elf $(TARGET).nds $(SOUNDBANK)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------
$(OUTPUT).nds: $(OUTPUT).elf $(NITRO_FILES) $(GAME_ICON)
$(OUTPUT).elf: $(OFILES)
# source files depend on generated headers
$(OFILES_SOURCES) : $(HFILES)
# need to build soundbank first
$(OFILES): $(SOUNDBANK)
#---------------------------------------------------------------------------------
# rule to build solution from music files
#---------------------------------------------------------------------------------
$(SOUNDBANK) : $(MODFILES)
#---------------------------------------------------------------------------------
mmutil $^ -d -o$@ -hsoundbank.h
#---------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#---------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
#---------------------------------------------------------------------------------
# This rule creates assembly source files using grit
# grit takes an image file and a .grit describing how the file is to be processed
# add additional rules like this for each image extension
# you use in the graphics folders
#---------------------------------------------------------------------------------
%.s %.h: %.png %.grit
#---------------------------------------------------------------------------------
grit $< -fts -o$*
#---------------------------------------------------------------------------------
# Convert non-GRF game icon to GRF if needed
#---------------------------------------------------------------------------------
$(GAME_ICON): $(notdir $(ICON))
#---------------------------------------------------------------------------------
@echo convert $(notdir $<)
@grit $< -g -gt -gB4 -gT FF00FF -m! -p -pe 16 -fh! -ftr
-include $(DEPSDIR)/*.d
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

@ -0,0 +1,3 @@
# rcheevos test on the DS
A test app of running rcheevos on the DS with a Python script to convert the JSON to a simpler binary format.

@ -0,0 +1,190 @@
#!/usr/bin/env python3
from argparse import ArgumentParser, FileType
from hashlib import md5
from requests import get
from struct import pack
from urllib.parse import urlencode
# char magic[8]
# u32 game_id
# u32 console_id
# u32 forum_topic_id
# u32 flags
# u32 is_final
# u32 achievement_count
# u32 leaderboard_count
# u32 reserved1[3]
# u8 hash[16]
# char title[32]
# char publisher[32]
# char developer[32]
# char genre[32]
# char release_date[32]
# char console_name[32]
HEADER_STRUCT = "<8sIIIIIII12x16s32s32s32s32s32s32s" # 0x100 bytes
# u32 id
# u32 points
# u32 flags
# u32 creation_time
# u32 modified_time
# u32 description_len
# u32 mem_addr_len
# u32 completion_time
# u32 reserved1[8]
# char title[32]
# char author[32]
# char reserved2[128]
# char description[256]
# char mem_addr[512]
ACHIEVEMENT_STRUCT = "<IIIIIII4x32x32s32s128x256s512s" # 0x400 bytes
# u32 id
# u32 hidden
# u32 lower_is_better
# u32 description_len
# u32 mem_len
# u32 reserved1[11]
# char title[32]
# char format[32]
# char reserved2[128]
# char description[256]
# char mem[512]
LEADERBOARD_STRUCT = "<IIIII44x32s32s128x256s512s" # 0x400 bytes
def log(str):
if args.verbose:
print(str)
def rc_hash_nintendo_ds(input):
offset = 0
input.seek(0)
header = input.read(512)
if len(header) != 512:
raise Exception("Failed to read header")
if header[0] == 0x2E and header[1] == 0x00 and header[2] == 0x00 and header[3] == 0xEA and \
header[0xB0] == 0x44 and header[0xB1] == 0x46 and header[0xB2] == 0x96 and header[0xB3] == 0:
# SuperCard header detected, ignore it
log("Ignoring SuperCard header")
offset = 512
input.seek(offset)
header = input.read(512)
if len(header) != 512:
raise Exception("Failed to read header")
arm9_addr = header[0x20] | (header[0x21] << 8) | (header[0x22] << 16) | (header[0x23] << 24)
arm9_size = header[0x2C] | (header[0x2D] << 8) | (header[0x2E] << 16) | (header[0x2F] << 24)
arm7_addr = header[0x30] | (header[0x31] << 8) | (header[0x32] << 16) | (header[0x33] << 24)
arm7_size = header[0x3C] | (header[0x3D] << 8) | (header[0x3E] << 16) | (header[0x3F] << 24)
icon_addr = header[0x68] | (header[0x69] << 8) | (header[0x6A] << 16) | (header[0x6B] << 24)
if arm9_size + arm7_size > 16 * 1024 * 1024:
# sanity check - code blocks are typically less than 1MB each - assume not a DS ROM
raise Exception("arm9 code size (%u) + arm7 code size (%u) exceeds 16MB" % (arm9_size, arm7_size))
log("Hashing 352 byte header")
hash_buffer = header[:0x160]
log("Hashing %u byte arm9 code (at %08X)" % (arm9_size, arm9_addr))
input.seek(arm9_addr + offset)
hash_buffer += input.read(arm9_size)
log("Hashing %u byte arm7 code (at %08X)" % (arm7_size, arm7_addr))
input.seek(arm7_addr + offset)
hash_buffer += input.read(arm7_size)
log("Hashing 2560 byte icon and labels data (at %08X)" % icon_addr)
input.seek(icon_addr + offset)
icon_buf = input.read(0xA00)
if len(icon_buf) < 0xA00:
# some homebrew games don't provide a full icon block, and no data after the icon block.
# if we didn't get a full icon block, fill the remaining portion with 0s
log("Warning: only got %u bytes for icon and labels data, 0-padding to 2560 bytes" % len(icon_buf))
icon_buf += b"\0" * (0xA00 - len(icon_buf))
hash_buffer += icon_buf
with open("test.bin", "wb") as f:
f.write(hash_buffer)
return md5(hash_buffer)
def api_get(request, vars={}):
vars["r"] = request
j = get("http://retroachievements.org/dorequest.php?" + urlencode(vars)).json()
if j["Success"]:
return j
else:
raise Exception("API request failed")
def bsach(input, output, username, password):
hash = rc_hash_nintendo_ds(input)
log("Hash: %s" % hash.hexdigest())
game_id = api_get("gameid", {"m": hash})["GameID"]
log("Game ID: %u" % game_id)
token = api_get("login", {"u": username, "p": password})["Token"]
patch_data = api_get("patch", {"u": "Pk11", "t": token, "g": 12711})["PatchData"]
output.write(pack(HEADER_STRUCT,
b"BSACHv1",
patch_data["ID"],
patch_data["ConsoleID"],
patch_data["ForumTopicID"],
patch_data["Flags"],
patch_data["IsFinal"],
len(patch_data["Achievements"]),
len(patch_data["Leaderboards"]),
hash.digest(),
patch_data["Title"].encode("utf8"),
patch_data["Publisher"].encode("utf8"),
patch_data["Developer"].encode("utf8"),
patch_data["Genre"].encode("utf8"),
patch_data["Released"].encode("utf8"),
patch_data["ConsoleName"].encode("utf8")
))
for achievement in patch_data["Achievements"]:
output.write(pack(ACHIEVEMENT_STRUCT,
achievement["ID"],
achievement["Points"],
achievement["Flags"],
achievement["Created"],
achievement["Modified"],
len(achievement["Description"]),
len(achievement["MemAddr"]),
achievement["Title"].encode("utf8"),
achievement["Author"].encode("utf8"),
achievement["Description"].encode("utf8"),
achievement["MemAddr"].encode("utf8")
))
for leaderboard in patch_data["Leaderboards"]:
output.write(pack(LEADERBOARD_STRUCT,
leaderboard["ID"],
leaderboard["Hidden"],
leaderboard["LowerIsBetter"],
len(leaderboard["Description"]),
len(leaderboard["Mem"]),
leaderboard["Title"].encode("utf8"),
leaderboard["Format"].encode("utf8"),
leaderboard["Description"].encode("utf8"),
leaderboard["Mem"].encode("utf8")
))
log("%s successfully created with %d achievements and %d leaderboards" % (output.name, len(patch_data["Achievements"]), len(patch_data["Leaderboards"])))
if __name__ == "__main__":
parser = ArgumentParser(description="Downloads RetroAchievements to an nds-bootstrap friendly format")
parser.add_argument("input", type=FileType("rb"), help="input nds")
parser.add_argument("output", type=FileType("wb"), help="output bsach")
parser.add_argument("username", type=str, help="RetroAchievements username")
parser.add_argument("password", type=str, help="RetroAchievements password")
parser.add_argument("-v", "--verbose", action="store_true", help="enable args.verbose logging")
args = parser.parse_args()
bsach(args.input, args.output, args.username, args.password)

@ -0,0 +1,247 @@
#ifndef RC_API_EDITOR_H
#define RC_API_EDITOR_H
#include "rc_api_request.h"
#ifdef __cplusplus
extern "C" {
#endif
/* --- Fetch Code Notes --- */
/**
* API parameters for a fetch code notes request.
*/
typedef struct rc_api_fetch_code_notes_request_t {
/* The unique identifier of the game */
unsigned game_id;
}
rc_api_fetch_code_notes_request_t;
/* A code note definiton */
typedef struct rc_api_code_note_t {
/* The address the note is associated to */
unsigned address;
/* The name of the use who last updated the note */
const char* author;
/* The contents of the note */
const char* note;
} rc_api_code_note_t;
/**
* Response data for a fetch code notes request.
*/
typedef struct rc_api_fetch_code_notes_response_t {
/* An array of code notes for the game */
rc_api_code_note_t* notes;
/* The number of items in the notes array */
unsigned num_notes;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_code_notes_response_t;
int rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params);
int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response);
void rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response);
/* --- Update Code Note --- */
/**
* API parameters for an update code note request.
*/
typedef struct rc_api_update_code_note_request_t {
/* The username of the developer */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the game */
unsigned game_id;
/* The address the note is associated to */
unsigned address;
/* The contents of the note (NULL or empty to delete a note) */
const char* note;
}
rc_api_update_code_note_request_t;
/**
* Response data for an update code note request.
*/
typedef struct rc_api_update_code_note_response_t {
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_update_code_note_response_t;
int rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params);
int rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response);
void rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response);
/* --- Update Achievement --- */
/**
* API parameters for an update achievement request.
*/
typedef struct rc_api_update_achievement_request_t {
/* The username of the developer */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the achievement (0 to create a new achievement) */
unsigned achievement_id;
/* The unique identifier of the game */
unsigned game_id;
/* The name of the achievement */
const char* title;
/* The description of the achievement */
const char* description;
/* The badge name for the achievement */
const char* badge;
/* The serialized trigger for the achievement */
const char* trigger;
/* The number of points the achievement is worth */
unsigned points;
/* The category of the achievement */
unsigned category;
}
rc_api_update_achievement_request_t;
/**
* Response data for an update achievement request.
*/
typedef struct rc_api_update_achievement_response_t {
/* The unique identifier of the achievement */
unsigned achievement_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_update_achievement_response_t;
int rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params);
int rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response);
void rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response);
/* --- Update Leaderboard --- */
/**
* API parameters for an update leaderboard request.
*/
typedef struct rc_api_update_leaderboard_request_t {
/* The username of the developer */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the leaderboard (0 to create a new leaderboard) */
unsigned leaderboard_id;
/* The unique identifier of the game */
unsigned game_id;
/* The name of the leaderboard */
const char* title;
/* The description of the leaderboard */
const char* description;
/* The start trigger for the leaderboard */
const char* start_trigger;
/* The submit trigger for the leaderboard */
const char* submit_trigger;
/* The cancel trigger for the leaderboard */
const char* cancel_trigger;
/* The value definition for the leaderboard */
const char* value_definition;
/* The format of leaderboard values */
const char* format;
/* Whether or not lower scores are better for the leaderboard */
int lower_is_better;
}
rc_api_update_leaderboard_request_t;
/**
* Response data for an update leaderboard request.
*/
typedef struct rc_api_update_leaderboard_response_t {
/* The unique identifier of the leaderboard */
unsigned leaderboard_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_update_leaderboard_response_t;
int rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params);
int rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response);
void rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response);
/* --- Fetch Badge Range --- */
/**
* API parameters for a fetch badge range request.
*/
typedef struct rc_api_fetch_badge_range_request_t {
/* Unused */
unsigned unused;
}
rc_api_fetch_badge_range_request_t;
/**
* Response data for a fetch badge range request.
*/
typedef struct rc_api_fetch_badge_range_response_t {
/* The numeric identifier of the first valid badge ID */
unsigned first_badge_id;
/* The numeric identifier of the first unassigned badge ID */
unsigned next_badge_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_badge_range_response_t;
int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params);
int rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response);
void rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response);
/* --- Add Game Hash --- */
/**
* API parameters for an add game hash request.
*/
typedef struct rc_api_add_game_hash_request_t {
/* The username of the developer */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the game (0 to create a new game entry) */
unsigned game_id;
/* The unique identifier of the console for the game */
unsigned console_id;
/* The title of the game */
const char* title;
/* The hash being added */
const char* hash;
/* A description of the hash being added (usually the normalized ROM name) */
const char* hash_description;
}
rc_api_add_game_hash_request_t;
/**
* Response data for an update code note request.
*/
typedef struct rc_api_add_game_hash_response_t {
/* The unique identifier of the game */
unsigned game_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_add_game_hash_response_t;
int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params);
int rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response);
void rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response);
#ifdef __cplusplus
}
#endif
#endif /* RC_EDITOR_H */

@ -0,0 +1,182 @@
#ifndef RC_API_INFO_H
#define RC_API_INFO_H
#include "rc_api_request.h"
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
/* --- Fetch Achievement Info --- */
/**
* API parameters for a fetch achievement info request.
*/
typedef struct rc_api_fetch_achievement_info_request_t {
/* The username of the player */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the achievement */
unsigned achievement_id;
/* The 1-based index of the first entry to retrieve */
unsigned first_entry;
/* The number of entries to retrieve */
unsigned count;
/* Non-zero to only return unlocks earned by the user's friends */
unsigned friends_only;
}
rc_api_fetch_achievement_info_request_t;
/* An achievement awarded entry */
typedef struct rc_api_achievement_awarded_entry_t {
/* The user associated to the entry */
const char* username;
/* When the achievement was awarded */
time_t awarded;
}
rc_api_achievement_awarded_entry_t;
/**
* Response data for a fetch achievement info request.
*/
typedef struct rc_api_fetch_achievement_info_response_t {
/* The unique identifier of the achievement */
unsigned id;
/* The unique identifier of the game to which the leaderboard is associated */
unsigned game_id;
/* The number of times the achievement has been awarded */
unsigned num_awarded;
/* The number of players that have earned at least one achievement for the game */
unsigned num_players;
/* An array of recently rewarded entries */
rc_api_achievement_awarded_entry_t* recently_awarded;
/* The number of items in the recently_awarded array */
unsigned num_recently_awarded;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_achievement_info_response_t;
int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params);
int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response);
void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response);
/* --- Fetch Leaderboard Info --- */
/**
* API parameters for a fetch leaderboard info request.
*/
typedef struct rc_api_fetch_leaderboard_info_request_t {
/* The unique identifier of the leaderboard */
unsigned leaderboard_id;
/* The number of entries to retrieve */
unsigned count;
/* The 1-based index of the first entry to retrieve */
unsigned first_entry;
/* The username of the player around whom the entries should be returned */
const char* username;
}
rc_api_fetch_leaderboard_info_request_t;
/* A leaderboard info entry */
typedef struct rc_api_lboard_info_entry_t {
/* The user associated to the entry */
const char* username;
/* The rank of the entry */
unsigned rank;
/* The index of the entry */
unsigned index;
/* The value of the entry */
int score;
/* When the entry was submitted */
time_t submitted;
}
rc_api_lboard_info_entry_t;
/**
* Response data for a fetch leaderboard info request.
*/
typedef struct rc_api_fetch_leaderboard_info_response_t {
/* The unique identifier of the leaderboard */
unsigned id;
/* The format to pass to rc_format_value to format the leaderboard value */
int format;
/* If non-zero, indicates that lower scores appear first */
int lower_is_better;
/* The title of the leaderboard */
const char* title;
/* The description of the leaderboard */
const char* description;
/* The definition of the leaderboard to be passed to rc_runtime_activate_lboard */
const char* definition;
/* The unique identifier of the game to which the leaderboard is associated */
unsigned game_id;
/* The author of the leaderboard */
const char* author;
/* When the leaderboard was first uploaded to the server */
time_t created;
/* When the leaderboard was last modified on the server */
time_t updated;
/* An array of requested entries */
rc_api_lboard_info_entry_t* entries;
/* The number of items in the entries array */
unsigned num_entries;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_leaderboard_info_response_t;
int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params);
int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response);
void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response);
/* --- Fetch Games List --- */
/**
* API parameters for a fetch games list request.
*/
typedef struct rc_api_fetch_games_list_request_t {
/* The unique identifier of the console to query */
unsigned console_id;
}
rc_api_fetch_games_list_request_t;
/* A game list entry */
typedef struct rc_api_game_list_entry_t {
/* The unique identifier of the game */
unsigned id;
/* The name of the game */
const char* name;
}
rc_api_game_list_entry_t;
/**
* Response data for a fetch games list request.
*/
typedef struct rc_api_fetch_games_list_response_t {
/* An array of requested entries */
rc_api_game_list_entry_t* entries;
/* The number of items in the entries array */
unsigned num_entries;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_games_list_response_t;
int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params);
int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response);
void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response);
#ifdef __cplusplus
}
#endif
#endif /* RC_API_INFO_H */

@ -0,0 +1,63 @@
#ifndef RC_API_REQUEST_H
#define RC_API_REQUEST_H
#include "rc_error.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* A block of memory for variable length data (like strings and arrays).
*/
typedef struct rc_api_buffer_t {
/* The current location where data is being written */
char* write;
/* The first byte past the end of data where writing cannot occur */
char* end;
/* The next block in the allocated memory chain */
struct rc_api_buffer_t* next;
/* The buffer containing the data. The actual size may be larger than 256 bytes for buffers allocated in
* the next chain. The 256 byte size specified is for the initial allocation within the container object. */
char data[256];
}
rc_api_buffer_t;
/**
* A constructed request to send to the retroachievements server.
*/
typedef struct rc_api_request_t {
/* The URL to send the request to (contains protocol, host, path, and query args) */
const char* url;
/* Additional query args that should be sent via a POST command. If null, GET may be used */
const char* post_data;
/* Storage for the url and post_data */
rc_api_buffer_t buffer;
}
rc_api_request_t;
/**
* Common attributes for all server responses.
*/
typedef struct rc_api_response_t {
/* Server-provided success indicator (non-zero on failure) */
int succeeded;
/* Server-provided message associated to the failure */
const char* error_message;
/* Storage for the response data */
rc_api_buffer_t buffer;
}
rc_api_response_t;
void rc_api_destroy_request(rc_api_request_t* request);
void rc_api_set_host(const char* hostname);
void rc_api_set_image_host(const char* hostname);
#ifdef __cplusplus
}
#endif
#endif /* RC_API_REQUEST_H */

@ -0,0 +1,291 @@
#ifndef RC_API_RUNTIME_H
#define RC_API_RUNTIME_H
#include "rc_api_request.h"
#include <time.h>
#ifdef __cplusplus
extern "C" {
#endif
/* --- Fetch Image --- */
/**
* API parameters for a fetch image request.
* NOTE: fetch image server response is the raw image data. There is no rc_api_process_fetch_image_response function.
*/
typedef struct rc_api_fetch_image_request_t {
/* The name of the image to fetch */
const char* image_name;
/* The type of image to fetch */
int image_type;
}
rc_api_fetch_image_request_t;
#define RC_IMAGE_TYPE_GAME 1
#define RC_IMAGE_TYPE_ACHIEVEMENT 2
#define RC_IMAGE_TYPE_ACHIEVEMENT_LOCKED 3
#define RC_IMAGE_TYPE_USER 4
int rc_api_init_fetch_image_request(rc_api_request_t* request, const rc_api_fetch_image_request_t* api_params);
/* --- Resolve Hash --- */
/**
* API parameters for a resolve hash request.
*/
typedef struct rc_api_resolve_hash_request_t {
/* Unused - hash lookup does not require credentials */
const char* username;
/* Unused - hash lookup does not require credentials */
const char* api_token;
/* The generated hash of the game to be identified */
const char* game_hash;
}
rc_api_resolve_hash_request_t;
/**
* Response data for a resolve hash request.
*/
typedef struct rc_api_resolve_hash_response_t {
/* The unique identifier of the game, 0 if no match was found */
unsigned game_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_resolve_hash_response_t;
int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params);
int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response);
void rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response);
/* --- Fetch Game Data --- */
/**
* API parameters for a fetch game data request.
*/
typedef struct rc_api_fetch_game_data_request_t {
/* The username of the player */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the game */
unsigned game_id;
}
rc_api_fetch_game_data_request_t;
/* A leaderboard definition */
typedef struct rc_api_leaderboard_definition_t {
/* The unique identifier of the leaderboard */
unsigned id;
/* The format to pass to rc_format_value to format the leaderboard value */
int format;
/* The title of the leaderboard */
const char* title;
/* The description of the leaderboard */
const char* description;
/* The definition of the leaderboard to be passed to rc_runtime_activate_lboard */
const char* definition;
/* Non-zero if lower values are better for this leaderboard */
int lower_is_better;
/* Non-zero if the leaderboard should not be displayed in a list of leaderboards */
int hidden;
}
rc_api_leaderboard_definition_t;
/* An achievement definition */
typedef struct rc_api_achievement_definition_t {
/* The unique identifier of the achievement */
unsigned id;
/* The number of points the achievement is worth */
unsigned points;
/* The achievement category (core, unofficial) */
unsigned category;
/* The title of the achievement */
const char* title;
/* The dscription of the achievement */
const char* description;
/* The definition of the achievement to be passed to rc_runtime_activate_achievement */
const char* definition;
/* The author of the achievment */
const char* author;
/* The image name for the achievement badge */
const char* badge_name;
/* When the achievement was first uploaded to the server */
time_t created;
/* When the achievement was last modified on the server */
time_t updated;
}
rc_api_achievement_definition_t;
#define RC_ACHIEVEMENT_CATEGORY_CORE 3
#define RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL 5
/**
* Response data for a fetch game data request.
*/
typedef struct rc_api_fetch_game_data_response_t {
/* The unique identifier of the game */
unsigned id;
/* The console associated to the game */
unsigned console_id;
/* The title of the game */
const char* title;
/* The image name for the game badge */
const char* image_name;
/* The rich presence script for the game to be passed to rc_runtime_activate_richpresence */
const char* rich_presence_script;
/* An array of achievements for the game */
rc_api_achievement_definition_t* achievements;
/* The number of items in the achievements array */
unsigned num_achievements;
/* An array of leaderboards for the game */
rc_api_leaderboard_definition_t* leaderboards;
/* The number of items in the leaderboards array */
unsigned num_leaderboards;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_game_data_response_t;
int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params);
int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response);
void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response);
/* --- Ping --- */
/**
* API parameters for a ping request.
*/
typedef struct rc_api_ping_request_t {
/* The username of the player */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the game */
unsigned game_id;
/* (optional) The current rich presence evaluation for the user */
const char* rich_presence;
}
rc_api_ping_request_t;
/**
* Response data for a ping request.
*/
typedef struct rc_api_ping_response_t {
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_ping_response_t;
int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params);
int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response);
void rc_api_destroy_ping_response(rc_api_ping_response_t* response);
/* --- Award Achievement --- */
/**
* API parameters for an award achievement request.
*/
typedef struct rc_api_award_achievement_request_t {
/* The username of the player */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the achievement */
unsigned achievement_id;
/* Non-zero if the achievement was earned in hardcore */
int hardcore;
/* The hash associated to the game being played */
const char* game_hash;
}
rc_api_award_achievement_request_t;
/**
* Response data for an award achievement request.
*/
typedef struct rc_api_award_achievement_response_t {
/* The unique identifier of the achievement that was awarded */
unsigned awarded_achievement_id;
/* The updated player score */
unsigned new_player_score;
/* The number of achievements the user has not yet unlocked for this game
* (in hardcore/non-hardcore per hardcore flag in request) */
unsigned achievements_remaining;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_award_achievement_response_t;
int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params);
int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response);
void rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response);
/* --- Submit Leaderboard Entry --- */
/**
* API parameters for a submit lboard entry request.
*/
typedef struct rc_api_submit_lboard_entry_request_t {
/* The username of the player */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the leaderboard */
unsigned leaderboard_id;
/* The value being submitted */
int score;
/* The hash associated to the game being played */
const char* game_hash;
}
rc_api_submit_lboard_entry_request_t;
/* A leaderboard entry */
typedef struct rc_api_lboard_entry_t {
/* The user associated to the entry */
const char* username;
/* The rank of the entry */
unsigned rank;
/* The value of the entry */
int score;
}
rc_api_lboard_entry_t;
/**
* Response data for a submit lboard entry request.
*/
typedef struct rc_api_submit_lboard_entry_response_t {
/* The value that was submitted */
int submitted_score;
/* The player's best submitted value */
int best_score;
/* The player's new rank within the leaderboard */
unsigned new_rank;
/* The total number of entries in the leaderboard */
unsigned num_entries;
/* An array of the top entries for the leaderboard */
rc_api_lboard_entry_t* top_entries;
/* The number of items in the top_entries array */
unsigned num_top_entries;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_submit_lboard_entry_response_t;
int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params);
int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response);
void rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response);
#ifdef __cplusplus
}
#endif
#endif /* RC_API_RUNTIME_H */

@ -0,0 +1,117 @@
#ifndef RC_API_USER_H
#define RC_API_USER_H
#include "rc_api_request.h"
#ifdef __cplusplus
extern "C" {
#endif
/* --- Login --- */
/**
* API parameters for a login request.
* If both password and api_token are provided, api_token will be ignored.
*/
typedef struct rc_api_login_request_t {
/* The username of the player being logged in */
const char* username;
/* The API token from a previous login */
const char* api_token;
/* The player's password */
const char* password;
}
rc_api_login_request_t;
/**
* Response data for a login request.
*/
typedef struct rc_api_login_response_t {
/* The case-corrected username of the player */
const char* username;
/* The API token to use for all future requests */
const char* api_token;
/* The current score of the player */
unsigned score;
/* The number of unread messages waiting for the player on the web site */
unsigned num_unread_messages;
/* The preferred name to display for the player */
const char* display_name;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_login_response_t;
int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params);
int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response);
void rc_api_destroy_login_response(rc_api_login_response_t* response);
/* --- Start Session --- */
/**
* API parameters for a start session request.
*/
typedef struct rc_api_start_session_request_t {
/* The username of the player */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the game */
unsigned game_id;
}
rc_api_start_session_request_t;
/**
* Response data for a start session request.
*/
typedef struct rc_api_start_session_response_t {
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_start_session_response_t;
int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params);
int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response);
void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response);
/* --- Fetch User Unlocks --- */
/**
* API parameters for a fetch user unlocks request.
*/
typedef struct rc_api_fetch_user_unlocks_request_t {
/* The username of the player */
const char* username;
/* The API token from the login request */
const char* api_token;
/* The unique identifier of the game */
unsigned game_id;
/* Non-zero to fetch hardcore unlocks, 0 to fetch non-hardcore unlocks */
int hardcore;
}
rc_api_fetch_user_unlocks_request_t;
/**
* Response data for a fetch user unlocks request.
*/
typedef struct rc_api_fetch_user_unlocks_response_t {
/* An array of achievement IDs previously unlocked by the user */
unsigned* achievement_ids;
/* The number of items in the achievement_ids array */
unsigned num_achievement_ids;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_user_unlocks_response_t;
int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params);
int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response);
void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response);
#ifdef __cplusplus
}
#endif
#endif /* RC_API_H */

@ -0,0 +1,133 @@
#ifndef RC_CONSOLES_H
#define RC_CONSOLES_H
#ifdef __cplusplus
extern "C" {
#endif
/*****************************************************************************\
| Console identifiers |
\*****************************************************************************/
enum {
RC_CONSOLE_MEGA_DRIVE = 1,
RC_CONSOLE_NINTENDO_64 = 2,
RC_CONSOLE_SUPER_NINTENDO = 3,
RC_CONSOLE_GAMEBOY = 4,
RC_CONSOLE_GAMEBOY_ADVANCE = 5,
RC_CONSOLE_GAMEBOY_COLOR = 6,
RC_CONSOLE_NINTENDO = 7,
RC_CONSOLE_PC_ENGINE = 8,
RC_CONSOLE_SEGA_CD = 9,
RC_CONSOLE_SEGA_32X = 10,
RC_CONSOLE_MASTER_SYSTEM = 11,
RC_CONSOLE_PLAYSTATION = 12,
RC_CONSOLE_ATARI_LYNX = 13,
RC_CONSOLE_NEOGEO_POCKET = 14,
RC_CONSOLE_GAME_GEAR = 15,
RC_CONSOLE_GAMECUBE = 16,
RC_CONSOLE_ATARI_JAGUAR = 17,
RC_CONSOLE_NINTENDO_DS = 18,
RC_CONSOLE_WII = 19,
RC_CONSOLE_WII_U = 20,
RC_CONSOLE_PLAYSTATION_2 = 21,
RC_CONSOLE_XBOX = 22,
RC_CONSOLE_MAGNAVOX_ODYSSEY2 = 23,
RC_CONSOLE_POKEMON_MINI = 24,
RC_CONSOLE_ATARI_2600 = 25,
RC_CONSOLE_MS_DOS = 26,
RC_CONSOLE_ARCADE = 27,
RC_CONSOLE_VIRTUAL_BOY = 28,
RC_CONSOLE_MSX = 29,
RC_CONSOLE_COMMODORE_64 = 30,
RC_CONSOLE_ZX81 = 31,
RC_CONSOLE_ORIC = 32,
RC_CONSOLE_SG1000 = 33,
RC_CONSOLE_VIC20 = 34,
RC_CONSOLE_AMIGA = 35,
RC_CONSOLE_ATARI_ST = 36,
RC_CONSOLE_AMSTRAD_PC = 37,
RC_CONSOLE_APPLE_II = 38,
RC_CONSOLE_SATURN = 39,
RC_CONSOLE_DREAMCAST = 40,
RC_CONSOLE_PSP = 41,
RC_CONSOLE_CDI = 42,
RC_CONSOLE_3DO = 43,
RC_CONSOLE_COLECOVISION = 44,
RC_CONSOLE_INTELLIVISION = 45,
RC_CONSOLE_VECTREX = 46,
RC_CONSOLE_PC8800 = 47,
RC_CONSOLE_PC9800 = 48,
RC_CONSOLE_PCFX = 49,
RC_CONSOLE_ATARI_5200 = 50,
RC_CONSOLE_ATARI_7800 = 51,
RC_CONSOLE_X68K = 52,
RC_CONSOLE_WONDERSWAN = 53,
RC_CONSOLE_CASSETTEVISION = 54,
RC_CONSOLE_SUPER_CASSETTEVISION = 55,
RC_CONSOLE_NEO_GEO_CD = 56,
RC_CONSOLE_FAIRCHILD_CHANNEL_F = 57,
RC_CONSOLE_FM_TOWNS = 58,
RC_CONSOLE_ZX_SPECTRUM = 59,
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_PC6000 = 67,
RC_CONSOLE_PICO = 68,
RC_CONSOLE_MEGADUCK = 69,
RC_CONSOLE_ZEEBO = 70,
RC_CONSOLE_ARDUBOY = 71,
RC_CONSOLE_WASM4 = 72,
RC_CONSOLE_ARCADIA_2001 = 73,
RC_CONSOLE_INTERTON_VC_4000 = 74,
RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER = 75,
RC_CONSOLE_PC_ENGINE_CD = 76,
RC_CONSOLE_ATARI_JAGUAR_CD = 77,
RC_CONSOLE_HUBS = 100,
RC_CONSOLE_EVENTS = 101
};
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
#endif /* RC_CONSOLES_H */

@ -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 */

@ -0,0 +1,134 @@
#ifndef RC_HASH_H
#define RC_HASH_H
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include "rc_consoles.h"
#ifdef __cplusplus
extern "C" {
#endif
/* ===================================================== */
/* generates a hash from a block of memory.
* returns non-zero on success, or zero on failure.
*/
int rc_hash_generate_from_buffer(char hash[33], int console_id, const uint8_t* buffer, size_t buffer_size);
/* generates a hash from a file.
* returns non-zero on success, or zero on failure.
*/
int rc_hash_generate_from_file(char hash[33], int console_id, const char* path);
/* ===================================================== */
/* data for rc_hash_iterate
*/
struct rc_hash_iterator
{
uint8_t* buffer;
size_t buffer_size;
uint8_t consoles[12];
int index;
const char* path;
};
/* initializes a rc_hash_iterator
* - path must be provided
* - if buffer and buffer_size are provided, path may be a filename (i.e. for something extracted from a zip file)
*/
void rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, uint8_t* buffer, size_t buffer_size);
/* releases resources associated to a rc_hash_iterator
*/
void rc_hash_destroy_iterator(struct rc_hash_iterator* iterator);
/* generates the next hash for the data in the rc_hash_iterator.
* returns non-zero if a hash was generated, or zero if no more hashes can be generated for the data.
*/
int rc_hash_iterate(char hash[33], struct rc_hash_iterator* iterator);
/* ===================================================== */
/* specifies a function to call when an error occurs to display the error message */
typedef void (*rc_hash_message_callback)(const char*);
void rc_hash_init_error_message_callback(rc_hash_message_callback callback);
/* specifies a function to call for verbose logging */
void rc_hash_init_verbose_message_callback(rc_hash_message_callback callback);
/* ===================================================== */
/* opens a file */
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, int64_t offset, int origin);
/* locates the file pointer */
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.
*/
typedef size_t (*rc_hash_filereader_read_handler)(void* file_handle, void* buffer, size_t requested_bytes);
/* closes the file */
typedef void (*rc_hash_filereader_close_file_handler)(void* file_handle);
struct rc_hash_filereader
{
rc_hash_filereader_open_file_handler open;
rc_hash_filereader_seek_handler seek;
rc_hash_filereader_tell_handler tell;
rc_hash_filereader_read_handler read;
rc_hash_filereader_close_file_handler close;
};
void rc_hash_init_custom_filereader(struct rc_hash_filereader* reader);
/* ===================================================== */
#define RC_HASH_CDTRACK_FIRST_DATA ((uint32_t)-1)
#define RC_HASH_CDTRACK_LAST ((uint32_t)-2)
#define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3)
#define RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION ((uint32_t)-4)
/* opens a track from the specified file. track 0 indicates the largest data track should be opened.
* returns a handle to be passed to the other functions, or NULL if the track could not be opened.
*/
typedef void* (*rc_hash_cdreader_open_track_handler)(const char* path, uint32_t track);
/* attempts to read the specified number of bytes from the file starting at the specified absolute sector.
* returns the number of bytes actually read.
*/
typedef size_t (*rc_hash_cdreader_read_sector_handler)(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes);
/* closes the track handle */
typedef void (*rc_hash_cdreader_close_track_handler)(void* track_handle);
/* gets the absolute sector index for the first sector of a track */
typedef uint32_t(*rc_hash_cdreader_first_track_sector_handler)(void* track_handle);
struct rc_hash_cdreader
{
rc_hash_cdreader_open_track_handler open_track;
rc_hash_cdreader_read_sector_handler read_sector;
rc_hash_cdreader_close_track_handler close_track;
rc_hash_cdreader_first_track_sector_handler first_track_sector;
};
void rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader);
void rc_hash_init_default_cdreader(void);
void rc_hash_init_custom_cdreader(struct rc_hash_cdreader* reader);
/* ===================================================== */
#ifdef __cplusplus
}
#endif
#endif /* RC_HASH_H */

@ -0,0 +1,151 @@
#ifndef RC_RUNTIME_H
#define RC_RUNTIME_H
#ifdef __cplusplus
extern "C" {
#endif
#include "rc_error.h"
#include <stddef.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];
int serialized_size;
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;
unsigned char md5[16];
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_format_achievement_measured(const rc_runtime_t* runtime, unsigned id, char *buffer, size_t buffer_size);
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 */

@ -0,0 +1,419 @@
#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_16_BITS_BE,
RC_MEMSIZE_24_BITS_BE,
RC_MEMSIZE_32_BITS_BE,
RC_MEMSIZE_FLOAT,
RC_MEMSIZE_MBF32,
RC_MEMSIZE_MBF32_LE,
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;
/* The value type of the value (for variables) */
char type;
/* 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(const 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,
RC_OPERATOR_XOR
};
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;
/* True if the measured value should be displayed as a percentage */
char measured_as_percent;
};
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 variable. */
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,
RC_FORMAT_FLOAT1,
RC_FORMAT_FLOAT2,
RC_FORMAT_FLOAT3,
RC_FORMAT_FLOAT4,
RC_FORMAT_FLOAT5,
RC_FORMAT_FLOAT6
};
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);
void rc_reset_richpresence(rc_richpresence_t* self);
#ifdef __cplusplus
}
#endif
#endif /* RC_RUNTIME_TYPES_H */

@ -0,0 +1,38 @@
#ifndef RC_URL_H
#define RC_URL_H
#include <stddef.h>
#ifdef __cplusplus
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_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value);
int rc_url_get_lboard_entries(char* buffer, size_t size, unsigned lboard_id, unsigned first_index, unsigned count);
int rc_url_get_lboard_entries_near_user(char* buffer, size_t size, unsigned lboard_id, const char* user_name, unsigned count);
int rc_url_get_gameid(char* buffer, size_t size, const char* hash);
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_url_ping(char* url_buffer, size_t url_buffer_size, char* post_buffer, size_t post_buffer_size,
const char* user_name, const char* login_token, unsigned gameid, const char* rich_presence);
#ifdef __cplusplus
}
#endif
#endif /* RC_URL_H */

@ -0,0 +1,8 @@
#ifndef RCHEEVOS_H
#define RCHEEVOS_H
#include "rc_runtime.h"
#include "rc_runtime_types.h"
#include "rc_consoles.h"
#endif /* RCHEEVOS_H */

@ -0,0 +1,142 @@
#include "rc_hash.h"
#include "rc_runtime.h"
#include <fat.h>
#include <nds.h>
#include <stdio.h>
typedef struct rc_bsach_header_t {
char magic[8];
u32 game_id;
u32 console_id;
u32 forum_topic_id;
u32 flags;
u32 is_final;
u32 achievement_count;
u32 leaderboard_count;
u32 reserved1[3];
u8 hash[16];
char title[32];
char publisher[32];
char developer[32];
char genre[32];
char release_date[32];
char console_name[32];
} __packed rc_bsach_header_t;
typedef struct rc_achievement_t {
u32 id;
u32 points;
u32 flags;
u32 creation_time;
u32 modified_time;
u32 description_len;
u32 mem_addr_len;
u32 completion_time;
u32 reserved1[8];
char title[32];
char author[32];
char reserved2[128];
char description[256];
char mem_addr[512];
} __packed rc_achievement_t;
typedef struct rc_leaderboard_t {
u32 id;
u32 hidden;
u32 lower_is_better;
u32 description_len;
u32 mem_len;
u32 reserved1[11];
char title[32];
char format[32];
char reserved2[128];
char description[256];
char mem[512];
} __packed rc_leaderboard_t;
rc_runtime_t runtime;
void eventHandler(const rc_runtime_event_t *runtime_event) {
iprintf("%d : %d : %d\n", runtime_event->id, runtime_event->value, runtime_event->type);
if(runtime_event->type == RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED) {
iprintf("!!!\n");
rc_runtime_deactivate_achievement(&runtime, runtime_event->id);
}
}
unsigned peek(unsigned address, unsigned num_bytes, void *ud) {
switch(num_bytes) {
case 1:
return *(u8 *)address;
case 2:
return *(u16 *)address;
case 4:
return *(u32 *)address;
}
return 0;
}
void error(const char *str) {
iprintf("\x1B[41mError:\x1B[47m %s\n", str);
}
void verbose(const char *str) {
iprintf("\x1B[42mVerbose:\x1B[47m %s\n", str);
}
void register_achievements(const char *path, char *file_hash) {
FILE *file = fopen(path, "rb");
if(file) {
rc_bsach_header_t header;
fread(&header, 1, sizeof(header), file);
iprintf("Loading: %s\n", header.title);
char hash_str[33];
u8 *d = header.hash;
sniprintf(hash_str, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], d[11], d[12], d[13], d[14], d[15]
);
if(strcmp(hash_str, file_hash) != 0) {
iprintf("Invalid hash:\n%s\n\nExpected:\n%s\n", file_hash, hash_str);
}
for(int i = 0; i < header.achievement_count; i++) {
rc_achievement_t achievement;
fread(&achievement, 1, sizeof(achievement), file);
rc_runtime_activate_achievement(&runtime, achievement.id, achievement.mem_addr, NULL, 0);
iprintf("- %s\n", achievement.title);
}
fclose(file);
}
}
int main(void) {
consoleDemoInit();
if(!fatInitDefault())
iprintf("FAT init fail\n");
rc_hash_init_error_message_callback(error);
rc_hash_init_verbose_message_callback(verbose);
char hash[33];
if(!rc_hash_generate_from_file(hash, RC_CONSOLE_NINTENDO_DS, "sd:/Mario Kart DS.nds"))
printf("hash fail\n");
rc_runtime_init(&runtime);
register_achievements("sd:/Mario Kart DS.bsach", hash);
while(1) {
swiWaitForVBlank();
scanKeys();
int pressed = keysDown();
if(pressed & KEY_START) break;
rc_runtime_do_frame(&runtime, eventHandler, peek, NULL, NULL);
}
}

@ -0,0 +1,195 @@
#include "rc_internal.h"
#include <stdlib.h>
#include <string.h>
void* rc_alloc_scratch(void* pointer, int* offset, int size, int alignment, rc_scratch_t* scratch, int scratch_object_pointer_offset)
{
rc_scratch_buffer_t* buffer;
/* if we have a real buffer, then allocate the data there */
if (pointer)
return rc_alloc(pointer, offset, size, alignment, NULL, scratch_object_pointer_offset);
/* update how much space will be required in the real buffer */
{
const int aligned_offset = (*offset + alignment - 1) & ~(alignment - 1);
*offset += (aligned_offset - *offset);
*offset += size;
}
/* find a scratch buffer to hold the temporary data */
buffer = &scratch->buffer;
do {
const int aligned_buffer_offset = (buffer->offset + alignment - 1) & ~(alignment - 1);
const int remaining = sizeof(buffer->buffer) - aligned_buffer_offset;
if (remaining >= size) {
/* claim the required space from an existing buffer */
return rc_alloc(buffer->buffer, &buffer->offset, size, alignment, NULL, -1);
}
if (!buffer->next)
break;
buffer = buffer->next;
} while (1);
/* not enough space in any existing buffer, allocate more */
if (size > (int)sizeof(buffer->buffer)) {
/* caller is asking for more than we can fit in a standard rc_scratch_buffer_t.
* leverage the fact that the buffer is the last field and extend its size.
* this chunk will be exactly large enough to hold the needed data, and since offset
* will exceed sizeof(buffer->buffer), it will never be eligible to hold anything else.
*/
const int needed = sizeof(rc_scratch_buffer_t) - sizeof(buffer->buffer) + size;
buffer->next = (rc_scratch_buffer_t*)malloc(needed);
}
else {
buffer->next = (rc_scratch_buffer_t*)malloc(sizeof(rc_scratch_buffer_t));
}
if (!buffer->next) {
*offset = RC_OUT_OF_MEMORY;
return NULL;
}
buffer = buffer->next;
buffer->offset = 0;
buffer->next = NULL;
/* claim the required space from the new buffer */
return rc_alloc(buffer->buffer, &buffer->offset, size, alignment, NULL, -1);
}
void* rc_alloc(void* pointer, int* offset, int size, int alignment, rc_scratch_t* scratch, int scratch_object_pointer_offset) {
void* ptr;
*offset = (*offset + alignment - 1) & ~(alignment - 1);
if (pointer != 0) {
/* valid buffer, grab the next chunk */
ptr = (void*)((char*)pointer + *offset);
}
else if (scratch != 0 && scratch_object_pointer_offset >= 0) {
/* only allocate one instance of each object type (indentified by scratch_object_pointer_offset) */
void** scratch_object_pointer = (void**)((char*)&scratch->objs + scratch_object_pointer_offset);
ptr = *scratch_object_pointer;
if (!ptr) {
int used;
ptr = *scratch_object_pointer = rc_alloc_scratch(NULL, &used, size, alignment, scratch, -1);
}
}
else {
/* nowhere to get memory from, return NULL */
ptr = NULL;
}
*offset += size;
return ptr;
}
char* rc_alloc_str(rc_parse_state_t* parse, const char* text, int length) {
int used = 0;
char* ptr;
rc_scratch_string_t** next = &parse->scratch.strings;
while (*next) {
int diff = strncmp(text, (*next)->value, length);
if (diff == 0) {
diff = (*next)->value[length];
if (diff == 0)
return (*next)->value;
}
if (diff < 0)
next = &(*next)->left;
else
next = &(*next)->right;
}
*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) {
if (parse->offset >= 0)
parse->offset = RC_OUT_OF_MEMORY;
return NULL;
}
memcpy(ptr, text, length);
ptr[length] = '\0';
(*next)->left = NULL;
(*next)->right = NULL;
(*next)->value = ptr;
return ptr;
}
void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, int funcs_ndx)
{
/* could use memset here, but rc_parse_state_t contains a 512 byte buffer that doesn't need to be initialized */
parse->offset = 0;
parse->L = L;
parse->funcs_ndx = funcs_ndx;
parse->buffer = buffer;
parse->scratch.buffer.offset = 0;
parse->scratch.buffer.next = NULL;
parse->scratch.strings = NULL;
memset(&parse->scratch.objs, 0, sizeof(parse->scratch.objs));
parse->first_memref = 0;
parse->variables = 0;
parse->measured_target = 0;
parse->lines_read = 0;
parse->has_required_hits = 0;
parse->measured_as_percent = 0;
}
void rc_destroy_parse_state(rc_parse_state_t* parse)
{
rc_scratch_buffer_t* buffer = parse->scratch.buffer.next;
rc_scratch_buffer_t* next;
while (buffer) {
next = buffer->next;
free(buffer);
buffer = next;
}
}
const char* rc_error_str(int ret)
{
switch (ret) {
case RC_OK: return "OK";
case RC_INVALID_LUA_OPERAND: return "Invalid Lua operand";
case RC_INVALID_MEMORY_OPERAND: return "Invalid memory operand";
case RC_INVALID_CONST_OPERAND: return "Invalid constant operand";
case RC_INVALID_FP_OPERAND: return "Invalid floating-point operand";
case RC_INVALID_CONDITION_TYPE: return "Invalid condition type";
case RC_INVALID_OPERATOR: return "Invalid operator";
case RC_INVALID_REQUIRED_HITS: return "Invalid required hits";
case RC_DUPLICATED_START: return "Duplicated start condition";
case RC_DUPLICATED_CANCEL: return "Duplicated cancel condition";
case RC_DUPLICATED_SUBMIT: return "Duplicated submit condition";
case RC_DUPLICATED_VALUE: return "Duplicated value expression";
case RC_DUPLICATED_PROGRESS: return "Duplicated progress expression";
case RC_MISSING_START: return "Missing start condition";
case RC_MISSING_CANCEL: return "Missing cancel condition";
case RC_MISSING_SUBMIT: return "Missing submit condition";
case RC_MISSING_VALUE: return "Missing value expression";
case RC_INVALID_LBOARD_FIELD: return "Invalid field in leaderboard";
case RC_MISSING_DISPLAY_STRING: return "Missing display string";
case RC_OUT_OF_MEMORY: return "Out of memory";
case RC_INVALID_VALUE_FLAG: return "Invalid flag in value expression";
case RC_MISSING_VALUE_MEASURED: return "Missing measured flag in value expression";
case RC_MULTIPLE_MEASURED: return "Multiple measured targets";
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";
}
}

@ -0,0 +1,63 @@
#include "rc_compat.h"
#include <ctype.h>
#include <stdarg.h>
int rc_strncasecmp(const char* left, const char* right, size_t length)
{
while (length)
{
if (*left != *right)
{
const int diff = tolower(*left) - tolower(*right);
if (diff != 0)
return diff;
}
++left;
++right;
--length;
}
return 0;
}
int rc_strcasecmp(const char* left, const char* right)
{
while (*left || *right)
{
if (*left != *right)
{
const int diff = tolower(*left) - tolower(*right);
if (diff != 0)
return diff;
}
++left;
++right;
}
return 0;
}
char* rc_strdup(const char* str)
{
const size_t length = strlen(str);
char* buffer = (char*)malloc(length + 1);
memcpy(buffer, str, length + 1);
return buffer;
}
int rc_snprintf(char* buffer, size_t size, const char* format, ...)
{
int result;
va_list args;
va_start(args, format);
/* assume buffer is large enough and ignore size */
(void)size;
result = vsprintf(buffer, format, args);
va_end(args);
return result;
}

@ -0,0 +1,275 @@
#include "rc_internal.h"
#include <stdlib.h>
static int rc_parse_operator(const char** memaddr) {
const char* oper = *memaddr;
switch (*oper) {
case '=':
++(*memaddr);
(*memaddr) += (**memaddr == '=');
return RC_OPERATOR_EQ;
case '!':
if (oper[1] == '=') {
(*memaddr) += 2;
return RC_OPERATOR_NE;
}
/* fall through */
default:
return RC_INVALID_OPERATOR;
case '<':
if (oper[1] == '=') {
(*memaddr) += 2;
return RC_OPERATOR_LE;
}
++(*memaddr);
return RC_OPERATOR_LT;
case '>':
if (oper[1] == '=') {
(*memaddr) += 2;
return RC_OPERATOR_GE;
}
++(*memaddr);
return RC_OPERATOR_GT;
case '*':
++(*memaddr);
return RC_OPERATOR_MULT;
case '/':
++(*memaddr);
return RC_OPERATOR_DIV;
case '&':
++(*memaddr);
return RC_OPERATOR_AND;
case '^':
++(*memaddr);
return RC_OPERATOR_XOR;
case '\0':/* end of string */
case '_': /* next condition */
case 'S': /* next condset */
case ')': /* end of macro */
case '$': /* maximum of values */
/* valid condition separator, condition may not have an operator */
return RC_OPERATOR_NONE;
}
}
rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, int is_indirect) {
rc_condition_t* self;
const char* aux;
int result;
int can_modify = 0;
aux = *memaddr;
self = RC_ALLOC(rc_condition_t, parse);
self->current_hits = 0;
self->is_true = 0;
self->pause = 0;
if (*aux != 0 && aux[1] == ':') {
switch (*aux) {
case 'p': case 'P': self->type = RC_CONDITION_PAUSE_IF; break;
case 'r': case 'R': self->type = RC_CONDITION_RESET_IF; break;
case 'a': case 'A': self->type = RC_CONDITION_ADD_SOURCE; can_modify = 1; break;
case 'b': case 'B': self->type = RC_CONDITION_SUB_SOURCE; can_modify = 1; break;
case 'c': case 'C': self->type = RC_CONDITION_ADD_HITS; break;
case 'd': case 'D': self->type = RC_CONDITION_SUB_HITS; break;
case 'n': case 'N': self->type = RC_CONDITION_AND_NEXT; break;
case 'o': case 'O': self->type = RC_CONDITION_OR_NEXT; break;
case 'm': case 'M': self->type = RC_CONDITION_MEASURED; break;
case 'q': case 'Q': self->type = RC_CONDITION_MEASURED_IF; break;
case 'i': case 'I': self->type = RC_CONDITION_ADD_ADDRESS; can_modify = 1; break;
case 't': case 'T': self->type = RC_CONDITION_TRIGGER; break;
case 'z': case 'Z': self->type = RC_CONDITION_RESET_NEXT_IF; break;
case 'g': case 'G':
parse->measured_as_percent = 1;
self->type = RC_CONDITION_MEASURED;
break;
/* e f h j k l s u v w x y */
default: parse->offset = RC_INVALID_CONDITION_TYPE; return 0;
}
aux += 2;
}
else {
self->type = RC_CONDITION_STANDARD;
}
result = rc_parse_operand(&self->operand1, &aux, is_indirect, parse);
if (result < 0) {
parse->offset = result;
return 0;
}
result = rc_parse_operator(&aux);
if (result < 0) {
parse->offset = result;
return 0;
}
self->oper = (char)result;
switch (self->oper) {
case RC_OPERATOR_NONE:
/* non-modifying statements must have a second operand */
if (!can_modify) {
/* measured does not require a second operand when used in a value */
if (self->type != RC_CONDITION_MEASURED) {
parse->offset = RC_INVALID_OPERATOR;
return 0;
}
}
/* provide dummy operand of '1' and no required hits */
self->operand2.type = RC_OPERAND_CONST;
self->operand2.value.num = 1;
self->required_hits = 0;
*memaddr = aux;
return self;
case RC_OPERATOR_MULT:
case RC_OPERATOR_DIV:
case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
/* modifying operators are only valid on modifying statements */
if (can_modify)
break;
/* fallthrough */
default:
/* comparison operators are not valid on modifying statements */
if (can_modify) {
switch (self->type) {
case RC_CONDITION_ADD_SOURCE:
case RC_CONDITION_SUB_SOURCE:
case RC_CONDITION_ADD_ADDRESS:
/* prevent parse errors on legacy achievements where a condition was present before changing the type */
self->oper = RC_OPERATOR_NONE;
break;
default:
parse->offset = RC_INVALID_OPERATOR;
return 0;
}
}
break;
}
result = rc_parse_operand(&self->operand2, &aux, is_indirect, parse);
if (result < 0) {
parse->offset = result;
return 0;
}
if (self->oper == RC_OPERATOR_NONE) {
/* if operator is none, explicitly clear out the right side */
self->operand2.type = RC_OPERAND_CONST;
self->operand2.value.num = 0;
}
if (*aux == '(') {
char* end;
self->required_hits = (unsigned)strtoul(++aux, &end, 10);
if (end == aux || *end != ')') {
parse->offset = RC_INVALID_REQUIRED_HITS;
return 0;
}
/* 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 == '.') {
char* end;
self->required_hits = (unsigned)strtoul(++aux, &end, 10);
if (end == aux || *end != '.') {
parse->offset = RC_INVALID_REQUIRED_HITS;
return 0;
}
/* 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 {
self->required_hits = 0;
}
*memaddr = aux;
return self;
}
int rc_condition_is_combining(const rc_condition_t* self) {
switch (self->type) {
case RC_CONDITION_STANDARD:
case RC_CONDITION_PAUSE_IF:
case RC_CONDITION_RESET_IF:
case RC_CONDITION_MEASURED_IF:
case RC_CONDITION_TRIGGER:
case RC_CONDITION_MEASURED:
return 0;
default:
return 1;
}
}
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state) {
rc_typed_value_t value1, value2;
rc_evaluate_operand(&value1, &self->operand1, eval_state);
if (eval_state->add_value.type != RC_VALUE_TYPE_NONE)
rc_typed_value_add(&value1, &eval_state->add_value);
rc_evaluate_operand(&value2, &self->operand2, eval_state);
return rc_typed_value_compare(&value1, &value2, self->oper);
}
void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_eval_state_t* eval_state) {
rc_typed_value_t amount;
rc_evaluate_operand(value, &self->operand1, eval_state);
rc_evaluate_operand(&amount, &self->operand2, eval_state);
switch (self->oper) {
case RC_OPERATOR_MULT:
rc_typed_value_multiply(value, &amount);
break;
case RC_OPERATOR_DIV:
rc_typed_value_divide(value, &amount);
break;
case RC_OPERATOR_AND:
rc_typed_value_convert(value, RC_VALUE_TYPE_UNSIGNED);
rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED);
value->value.u32 &= amount.value.u32;
break;
case RC_OPERATOR_XOR:
rc_typed_value_convert(value, RC_VALUE_TYPE_UNSIGNED);
rc_typed_value_convert(&amount, RC_VALUE_TYPE_UNSIGNED);
value->value.u32 ^= amount.value.u32;
break;
}
}

@ -0,0 +1,438 @@
#include "rc_internal.h"
#include <string.h> /* memcpy */
static void rc_update_condition_pause(rc_condition_t* condition) {
rc_condition_t* subclause = condition;
while (condition) {
if (condition->type == RC_CONDITION_PAUSE_IF) {
while (subclause != condition) {
subclause->pause = 1;
subclause = subclause->next;
}
condition->pause = 1;
}
else {
condition->pause = 0;
}
if (!rc_condition_is_combining(condition))
subclause = condition->next;
condition = condition->next;
}
}
rc_condset_t* rc_parse_condset(const char** memaddr, rc_parse_state_t* parse, int is_value) {
rc_condset_t* self;
rc_condition_t** next;
int in_add_address;
unsigned measured_target = 0;
self = RC_ALLOC(rc_condset_t, parse);
self->has_pause = self->is_paused = self->has_indirect_memrefs = 0;
next = &self->conditions;
if (**memaddr == 'S' || **memaddr == 's' || !**memaddr) {
/* empty group - editor allows it, so we have to support it */
*next = 0;
return self;
}
in_add_address = 0;
for (;;) {
*next = rc_parse_condition(memaddr, parse, in_add_address);
if (parse->offset < 0) {
return 0;
}
if ((*next)->oper == RC_OPERATOR_NONE) {
switch ((*next)->type) {
case RC_CONDITION_ADD_ADDRESS:
case RC_CONDITION_ADD_SOURCE:
case RC_CONDITION_SUB_SOURCE:
/* 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 */
default:
parse->offset = RC_INVALID_OPERATOR;
return 0;
}
}
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:
if (measured_target != 0) {
/* multiple Measured flags cannot exist in the same group */
parse->offset = RC_MULTIPLE_MEASURED;
return 0;
}
else if (is_value) {
measured_target = (unsigned)-1;
switch ((*next)->oper)
{
case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
case RC_OPERATOR_DIV:
case RC_OPERATOR_MULT:
case RC_OPERATOR_NONE:
/* measuring value. leave required_hits at 0 */
break;
default:
/* comparison operator, measuring hits. set required_hits to MAX_INT */
(*next)->required_hits = measured_target;
break;
}
}
else if ((*next)->required_hits != 0) {
measured_target = (*next)->required_hits;
}
else if ((*next)->operand2.type == RC_OPERAND_CONST) {
measured_target = (*next)->operand2.value.num;
}
else if ((*next)->operand2.type == RC_OPERAND_FP) {
measured_target = (unsigned)(*next)->operand2.value.dbl;
}
else {
parse->offset = RC_INVALID_MEASURED_TARGET;
return 0;
}
if (parse->measured_target && measured_target != parse->measured_target) {
/* multiple Measured flags in separate groups must have the same target */
parse->offset = RC_MULTIPLE_MEASURED;
return 0;
}
parse->measured_target = measured_target;
break;
case RC_CONDITION_STANDARD:
case RC_CONDITION_TRIGGER:
/* these flags are not allowed in value expressions */
if (is_value) {
parse->offset = RC_INVALID_VALUE_FLAG;
return 0;
}
break;
default:
break;
}
next = &(*next)->next;
if (**memaddr != '_') {
break;
}
(*memaddr)++;
}
*next = 0;
if (parse->buffer != 0)
rc_update_condition_pause(self->conditions);
return self;
}
static void rc_condset_update_indirect_memrefs(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) {
rc_typed_value_t value;
rc_evaluate_condition_value(&value, condition, eval_state);
rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED);
eval_state->add_address = value.value.u32;
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;
rc_typed_value_t value;
int set_valid, cond_valid, and_next, or_next, reset_next, measured_from_hits, can_measure;
rc_typed_value_t measured_value;
unsigned total_hits;
measured_value.type = RC_VALUE_TYPE_NONE;
measured_from_hits = 0;
can_measure = 1;
total_hits = 0;
eval_state->primed = 1;
set_valid = 1;
and_next = 1;
or_next = 0;
reset_next = 0;
eval_state->add_value.type = RC_VALUE_TYPE_NONE;
eval_state->add_hits = eval_state->add_address = 0;
for (condition = self->conditions; condition != 0; condition = condition->next) {
if (condition->pause != processing_pause)
continue;
/* STEP 1: process modifier conditions */
switch (condition->type) {
case RC_CONDITION_ADD_SOURCE:
rc_evaluate_condition_value(&value, condition, eval_state);
rc_typed_value_add(&eval_state->add_value, &value);
eval_state->add_address = 0;
continue;
case RC_CONDITION_SUB_SOURCE:
rc_evaluate_condition_value(&value, condition, eval_state);
rc_typed_value_convert(&value, RC_VALUE_TYPE_SIGNED);
value.value.i32 = -value.value.i32;
rc_typed_value_add(&eval_state->add_value, &value);
eval_state->add_address = 0;
continue;
case RC_CONDITION_ADD_ADDRESS:
rc_evaluate_condition_value(&value, condition, eval_state);
rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED);
eval_state->add_address = value.value.u32;
continue;
case RC_CONDITION_MEASURED:
if (condition->required_hits == 0 && can_measure) {
/* Measured condition without a hit target measures the value of the left operand */
rc_evaluate_condition_value(&measured_value, condition, eval_state);
rc_typed_value_add(&measured_value, &eval_state->add_value);
}
break;
default:
break;
}
/* STEP 2: evaluate the current condition */
condition->is_true = (char)rc_test_condition(condition, eval_state);
eval_state->add_value.type = RC_VALUE_TYPE_NONE;
eval_state->add_address = 0;
/* apply logic flags and reset them for the next condition */
cond_valid = condition->is_true;
cond_valid &= and_next;
cond_valid |= or_next;
and_next = 1;
or_next = 0;
if (reset_next) {
/* previous ResetNextIf resets the hit count on this condition and prevents it from being true */
if (condition->current_hits)
eval_state->was_cond_reset = 1;
condition->current_hits = 0;
cond_valid = 0;
}
else if (cond_valid) {
/* true conditions should update hit count */
eval_state->has_hits = 1;
if (condition->required_hits == 0) {
/* no target hit count, just keep tallying */
++condition->current_hits;
}
else if (condition->current_hits < condition->required_hits) {
/* target hit count hasn't been met, tally and revalidate - only true if hit count becomes met */
++condition->current_hits;
cond_valid = (condition->current_hits == condition->required_hits);
}
else {
/* target hit count has been met, do nothing */
}
}
else if (condition->current_hits > 0) {
/* target has been true in the past, if the hit target is met, consider it true now */
eval_state->has_hits = 1;
cond_valid = (condition->current_hits == condition->required_hits);
}
/* STEP 3: handle logic flags */
switch (condition->type) {
case RC_CONDITION_ADD_HITS:
eval_state->add_hits += condition->current_hits;
reset_next = 0; /* ResetNextIf was applied to this AddHits condition; don't apply it to future conditions */
continue;
case RC_CONDITION_SUB_HITS:
eval_state->add_hits -= condition->current_hits;
reset_next = 0; /* ResetNextIf was applied to this AddHits condition; don't apply it to future conditions */
continue;
case RC_CONDITION_RESET_NEXT_IF:
reset_next = cond_valid;
continue;
case RC_CONDITION_AND_NEXT:
and_next = cond_valid;
continue;
case RC_CONDITION_OR_NEXT:
or_next = cond_valid;
continue;
default:
break;
}
/* reset logic flags for next condition */
reset_next = 0;
/* STEP 4: calculate total hits */
total_hits = condition->current_hits;
if (eval_state->add_hits) {
if (condition->required_hits != 0) {
/* if the condition has a target hit count, we have to recalculate cond_valid including the AddHits counter */
const int signed_hits = (int)condition->current_hits + eval_state->add_hits;
total_hits = (signed_hits >= 0) ? (unsigned)signed_hits : 0;
cond_valid = (total_hits >= condition->required_hits);
}
else {
/* no target hit count. we can't tell if the add_hits value is from this frame or not, so ignore it.
complex condition will only be true if the current condition is true */
}
eval_state->add_hits = 0;
}
/* STEP 5: handle special flags */
switch (condition->type) {
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(condition->next, 1, eval_state);
/* then, update all indirect memrefs in the non-pause subset */
rc_condset_update_indirect_memrefs(self->conditions, 0, eval_state);
}
return 1;
}
/* if we make it to the end of the function, make sure we indicate that nothing matched. if we do find
a later PauseIf match, it'll automatically return true via the previous condition. */
set_valid = 0;
if (condition->required_hits == 0) {
/* PauseIf didn't evaluate true, and doesn't have a HitCount, reset the HitCount to indicate the condition didn't match */
condition->current_hits = 0;
}
else {
/* PauseIf has a HitCount that hasn't been met, ignore it for now. */
}
continue;
case RC_CONDITION_RESET_IF:
if (cond_valid) {
eval_state->was_reset = 1; /* let caller know to reset all hit counts */
set_valid = 0; /* cannot be valid if we've hit a reset condition */
}
continue;
case RC_CONDITION_MEASURED:
if (condition->required_hits != 0) {
/* if there's a hit target, capture the current hits for recording Measured value later */
measured_from_hits = 1;
if (can_measure) {
measured_value.value.u32 = total_hits;
measured_value.type = RC_VALUE_TYPE_UNSIGNED;
}
}
break;
case RC_CONDITION_MEASURED_IF:
if (!cond_valid) {
measured_value.value.u32 = 0;
measured_value.type = RC_VALUE_TYPE_UNSIGNED;
can_measure = 0;
}
break;
case RC_CONDITION_TRIGGER:
/* update truthiness of set, but do not update truthiness of primed state */
set_valid &= cond_valid;
continue;
default:
break;
}
/* STEP 5: update overall truthiness of set and primed state */
eval_state->primed &= cond_valid;
set_valid &= cond_valid;
}
if (measured_value.type != RC_VALUE_TYPE_NONE) {
/* if no previous Measured value was captured, or the new one is greater, keep the new one */
if (eval_state->measured_value.type == RC_VALUE_TYPE_NONE ||
rc_typed_value_compare(&measured_value, &eval_state->measured_value, RC_OPERATOR_GT)) {
memcpy(&eval_state->measured_value, &measured_value, sizeof(measured_value));
eval_state->measured_from_hits = (char)measured_from_hits;
}
}
return set_valid;
}
int rc_test_condset(rc_condset_t* self, rc_eval_state_t* eval_state) {
if (self->conditions == 0) {
/* important: empty group must evaluate true */
return 1;
}
if (self->has_pause) {
/* one or more Pause conditions exists, if any of them are true, stop processing this group */
self->is_paused = (char)rc_test_condset_internal(self, 1, eval_state);
if (self->is_paused) {
eval_state->primed = 0;
return 0;
}
}
return rc_test_condset_internal(self, 0, eval_state);
}
void rc_reset_condset(rc_condset_t* self) {
rc_condition_t* condition;
for (condition = self->conditions; condition != 0; condition = condition->next) {
condition->current_hits = 0;
}
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,206 @@
#include "rc_internal.h"
#include "rc_compat.h"
#include <string.h>
#include <stdio.h>
int rc_parse_format(const char* format_str) {
switch (*format_str++) {
case 'F':
if (!strcmp(format_str, "RAMES")) {
return RC_FORMAT_FRAMES;
}
if (!strncmp(format_str, "LOAT", 4) && format_str[4] >= '1' && format_str[4] <= '6' && format_str[5] == '\0') {
return RC_FORMAT_FLOAT1 + (format_str[4] - '1');
}
break;
case 'T':
if (!strcmp(format_str, "IME")) {
return RC_FORMAT_FRAMES;
}
if (!strcmp(format_str, "IMESECS")) {
return RC_FORMAT_SECONDS;
}
break;
case 'S':
if (!strcmp(format_str, "ECS")) {
return RC_FORMAT_SECONDS;
}
if (!strcmp(format_str, "CORE")) {
return RC_FORMAT_SCORE;
}
if (!strcmp(format_str, "ECS_AS_MINS")) {
return RC_FORMAT_SECONDS_AS_MINUTES;
}
break;
case 'M':
if (!strcmp(format_str, "ILLISECS")) {
return RC_FORMAT_CENTISECS;
}
if (!strcmp(format_str, "INUTES")) {
return RC_FORMAT_MINUTES;
}
break;
case 'P':
if (!strcmp(format_str, "OINTS")) {
return RC_FORMAT_SCORE;
}
break;
case 'V':
if (!strcmp(format_str, "ALUE")) {
return RC_FORMAT_VALUE;
}
break;
case 'O':
if (!strcmp(format_str, "THER")) {
return RC_FORMAT_SCORE;
}
break;
}
return RC_FORMAT_VALUE;
}
static int rc_format_value_minutes(char* buffer, int size, unsigned minutes) {
unsigned hours;
hours = minutes / 60;
minutes -= hours * 60;
return snprintf(buffer, size, "%uh%02u", hours, minutes);
}
static int rc_format_value_seconds(char* buffer, int size, unsigned seconds) {
unsigned hours, minutes;
/* apply modulus math to split the seconds into hours/minutes/seconds */
minutes = seconds / 60;
seconds -= minutes * 60;
if (minutes < 60) {
return snprintf(buffer, size, "%u:%02u", minutes, seconds);
}
hours = minutes / 60;
minutes -= hours * 60;
return snprintf(buffer, size, "%uh%02u:%02u", hours, minutes, seconds);
}
static int rc_format_value_centiseconds(char* buffer, int size, unsigned centiseconds) {
unsigned seconds;
int chars, chars2;
/* modulus off the centiseconds */
seconds = centiseconds / 100;
centiseconds -= seconds * 100;
chars = rc_format_value_seconds(buffer, size, seconds);
if (chars > 0) {
chars2 = snprintf(buffer + chars, size - chars, ".%02u", centiseconds);
if (chars2 > 0) {
chars += chars2;
} else {
chars = chars2;
}
}
return chars;
}
int rc_format_typed_value(char* buffer, int size, const rc_typed_value_t* value, int format) {
int chars;
rc_typed_value_t converted_value;
memcpy(&converted_value, value, sizeof(converted_value));
switch (format) {
default:
case RC_FORMAT_VALUE:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
chars = snprintf(buffer, size, "%d", converted_value.value.i32);
break;
case RC_FORMAT_FRAMES:
/* 60 frames per second = 100 centiseconds / 60 frames; multiply frames by 100 / 60 */
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
chars = rc_format_value_centiseconds(buffer, size, converted_value.value.u32 * 10 / 6);
break;
case RC_FORMAT_CENTISECS:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
chars = rc_format_value_centiseconds(buffer, size, converted_value.value.u32);
break;
case RC_FORMAT_SECONDS:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
chars = rc_format_value_seconds(buffer, size, converted_value.value.u32);
break;
case RC_FORMAT_SECONDS_AS_MINUTES:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
chars = rc_format_value_minutes(buffer, size, converted_value.value.u32 / 60);
break;
case RC_FORMAT_MINUTES:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
chars = rc_format_value_minutes(buffer, size, converted_value.value.u32);
break;
case RC_FORMAT_SCORE:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
chars = snprintf(buffer, size, "%06d", converted_value.value.i32);
break;
case RC_FORMAT_FLOAT1:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT);
chars = snprintf(buffer, size, "%.1f", converted_value.value.f32);
break;
case RC_FORMAT_FLOAT2:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT);
chars = snprintf(buffer, size, "%.2f", converted_value.value.f32);
break;
case RC_FORMAT_FLOAT3:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT);
chars = snprintf(buffer, size, "%.3f", converted_value.value.f32);
break;
case RC_FORMAT_FLOAT4:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT);
chars = snprintf(buffer, size, "%.4f", converted_value.value.f32);
break;
case RC_FORMAT_FLOAT5:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT);
chars = snprintf(buffer, size, "%.5f", converted_value.value.f32);
break;
case RC_FORMAT_FLOAT6:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_FLOAT);
chars = snprintf(buffer, size, "%.6f", converted_value.value.f32);
break;
}
return chars;
}
int rc_format_value(char* buffer, int size, int value, int format) {
rc_typed_value_t typed_value;
typed_value.value.i32 = value;
typed_value.type = RC_VALUE_TYPE_SIGNED;
return rc_format_typed_value(buffer, size, &typed_value, format);
}

@ -0,0 +1,275 @@
#include "rc_internal.h"
enum {
RC_LBOARD_START = 1 << 0,
RC_LBOARD_CANCEL = 1 << 1,
RC_LBOARD_SUBMIT = 1 << 2,
RC_LBOARD_VALUE = 1 << 3,
RC_LBOARD_PROGRESS = 1 << 4,
RC_LBOARD_COMPLETE = RC_LBOARD_START | RC_LBOARD_CANCEL | RC_LBOARD_SUBMIT | RC_LBOARD_VALUE
};
void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_state_t* parse) {
int found;
self->progress = 0;
found = 0;
for (;;)
{
if ((memaddr[0] == 's' || memaddr[0] == 'S') &&
(memaddr[1] == 't' || memaddr[1] == 'T') &&
(memaddr[2] == 'a' || memaddr[2] == 'A') && memaddr[3] == ':') {
if ((found & RC_LBOARD_START) != 0) {
parse->offset = RC_DUPLICATED_START;
return;
}
memaddr += 4;
if (*memaddr && *memaddr != ':') {
found |= RC_LBOARD_START;
rc_parse_trigger_internal(&self->start, &memaddr, parse);
self->start.memrefs = 0;
}
}
else if ((memaddr[0] == 'c' || memaddr[0] == 'C') &&
(memaddr[1] == 'a' || memaddr[1] == 'A') &&
(memaddr[2] == 'n' || memaddr[2] == 'N') && memaddr[3] == ':') {
if ((found & RC_LBOARD_CANCEL) != 0) {
parse->offset = RC_DUPLICATED_CANCEL;
return;
}
memaddr += 4;
if (*memaddr && *memaddr != ':') {
found |= RC_LBOARD_CANCEL;
rc_parse_trigger_internal(&self->cancel, &memaddr, parse);
self->cancel.memrefs = 0;
}
}
else if ((memaddr[0] == 's' || memaddr[0] == 'S') &&
(memaddr[1] == 'u' || memaddr[1] == 'U') &&
(memaddr[2] == 'b' || memaddr[2] == 'B') && memaddr[3] == ':') {
if ((found & RC_LBOARD_SUBMIT) != 0) {
parse->offset = RC_DUPLICATED_SUBMIT;
return;
}
memaddr += 4;
if (*memaddr && *memaddr != ':') {
found |= RC_LBOARD_SUBMIT;
rc_parse_trigger_internal(&self->submit, &memaddr, parse);
self->submit.memrefs = 0;
}
}
else if ((memaddr[0] == 'v' || memaddr[0] == 'V') &&
(memaddr[1] == 'a' || memaddr[1] == 'A') &&
(memaddr[2] == 'l' || memaddr[2] == 'L') && memaddr[3] == ':') {
if ((found & RC_LBOARD_VALUE) != 0) {
parse->offset = RC_DUPLICATED_VALUE;
return;
}
memaddr += 4;
if (*memaddr && *memaddr != ':') {
found |= RC_LBOARD_VALUE;
rc_parse_value_internal(&self->value, &memaddr, parse);
self->value.memrefs = 0;
}
}
else if ((memaddr[0] == 'p' || memaddr[0] == 'P') &&
(memaddr[1] == 'r' || memaddr[1] == 'R') &&
(memaddr[2] == 'o' || memaddr[2] == 'O') && memaddr[3] == ':') {
if ((found & RC_LBOARD_PROGRESS) != 0) {
parse->offset = RC_DUPLICATED_PROGRESS;
return;
}
memaddr += 4;
if (*memaddr && *memaddr != ':') {
found |= RC_LBOARD_PROGRESS;
self->progress = RC_ALLOC(rc_value_t, parse);
rc_parse_value_internal(self->progress, &memaddr, parse);
self->progress->memrefs = 0;
}
}
/* encountered an error parsing one of the parts */
if (parse->offset < 0)
return;
/* end of string, or end of quoted string - stop processing */
if (memaddr[0] == '\0' || memaddr[0] == '\"')
break;
/* expect two colons between fields */
if (memaddr[0] != ':' || memaddr[1] != ':') {
parse->offset = RC_INVALID_LBOARD_FIELD;
return;
}
memaddr += 2;
}
if ((found & RC_LBOARD_COMPLETE) != RC_LBOARD_COMPLETE) {
if ((found & RC_LBOARD_START) == 0) {
parse->offset = RC_MISSING_START;
}
else if ((found & RC_LBOARD_CANCEL) == 0) {
parse->offset = RC_MISSING_CANCEL;
}
else if ((found & RC_LBOARD_SUBMIT) == 0) {
parse->offset = RC_MISSING_SUBMIT;
}
else if ((found & RC_LBOARD_VALUE) == 0) {
parse->offset = RC_MISSING_VALUE;
}
return;
}
self->state = RC_LBOARD_STATE_WAITING;
}
int rc_lboard_size(const char* memaddr) {
rc_lboard_t* self;
rc_parse_state_t parse;
rc_memref_t* first_memref;
rc_init_parse_state(&parse, 0, 0, 0);
rc_init_parse_state_memrefs(&parse, &first_memref);
self = RC_ALLOC(rc_lboard_t, &parse);
rc_parse_lboard_internal(self, memaddr, &parse);
rc_destroy_parse_state(&parse);
return parse.offset;
}
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);
rc_init_parse_state_memrefs(&parse, &self->memrefs);
rc_parse_lboard_internal(self, memaddr, &parse);
rc_destroy_parse_state(&parse);
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) {
int start_ok, cancel_ok, submit_ok;
rc_update_memref_values(self->memrefs, peek, peek_ud);
if (self->state == RC_LBOARD_STATE_INACTIVE || self->state == RC_LBOARD_STATE_DISABLED)
return RC_LBOARD_STATE_INACTIVE;
/* these are always tested once every frame, to ensure hit counts work properly */
start_ok = rc_test_trigger(&self->start, peek, peek_ud, L);
cancel_ok = rc_test_trigger(&self->cancel, peek, peek_ud, L);
submit_ok = rc_test_trigger(&self->submit, peek, peek_ud, L);
switch (self->state)
{
case RC_LBOARD_STATE_WAITING:
case RC_LBOARD_STATE_TRIGGERED:
case RC_LBOARD_STATE_CANCELED:
/* don't activate/reactivate until the start condition becomes false */
if (start_ok) {
*value = 0;
return RC_LBOARD_STATE_INACTIVE; /* just return inactive for all of these */
}
/* start condition is false, allow the leaderboard to start on future frames */
self->state = RC_LBOARD_STATE_ACTIVE;
break;
case RC_LBOARD_STATE_ACTIVE:
/* leaderboard attempt is not in progress. if the start condition is true and the cancel condition is not, start the attempt */
if (start_ok && !cancel_ok) {
if (submit_ok) {
/* start and submit are both true in the same frame, just submit without announcing the leaderboard is available */
self->state = RC_LBOARD_STATE_TRIGGERED;
}
else if (self->start.requirement == 0 && self->start.alternative == 0) {
/* start condition is empty - this leaderboard is submit-only with no measured progress */
}
else {
/* start the leaderboard attempt */
self->state = RC_LBOARD_STATE_STARTED;
/* reset any hit counts in the value */
if (self->progress)
rc_reset_value(self->progress);
rc_reset_value(&self->value);
}
}
break;
case RC_LBOARD_STATE_STARTED:
/* leaderboard attempt in progress */
if (cancel_ok) {
/* cancel condition is true, abort the attempt */
self->state = RC_LBOARD_STATE_CANCELED;
}
else if (submit_ok) {
/* submit condition is true, submit the current value */
self->state = RC_LBOARD_STATE_TRIGGERED;
}
break;
}
/* Calculate the value */
switch (self->state) {
case RC_LBOARD_STATE_STARTED:
if (self->progress) {
*value = rc_evaluate_value(self->progress, peek, peek_ud, L);
break;
}
/* fallthrough to RC_LBOARD_STATE_TRIGGERED */
case RC_LBOARD_STATE_TRIGGERED:
*value = rc_evaluate_value(&self->value, peek, peek_ud, L);
break;
default:
*value = 0;
break;
}
return self->state;
}
int rc_lboard_state_active(int state) {
switch (state)
{
case RC_LBOARD_STATE_DISABLED:
case RC_LBOARD_STATE_INACTIVE:
return 0;
default:
return 1;
}
}
void rc_reset_lboard(rc_lboard_t* self) {
self->state = RC_LBOARD_STATE_WAITING;
rc_reset_trigger(&self->start);
rc_reset_trigger(&self->submit);
rc_reset_trigger(&self->cancel);
if (self->progress)
rc_reset_value(self->progress);
rc_reset_value(&self->value);
}

@ -0,0 +1,475 @@
#include "rc_internal.h"
#include <stdlib.h> /* malloc/realloc */
#include <string.h> /* memcpy */
#include <math.h> /* INFINITY/NAN */
#define MEMREF_PLACEHOLDER_ADDRESS 0xFFFFFFFF
rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, unsigned address, char size, char is_indirect) {
rc_memref_t** next_memref;
rc_memref_t* memref;
if (!is_indirect) {
/* attempt to find an existing memref that can be shared */
next_memref = parse->first_memref;
while (*next_memref) {
memref = *next_memref;
if (!memref->value.is_indirect && memref->address == address && memref->value.size == size)
return memref;
next_memref = &memref->next;
}
/* no match found, create a new entry */
memref = RC_ALLOC_SCRATCH(rc_memref_t, parse);
*next_memref = memref;
}
else {
/* indirect references always create a new entry because we can't guarantee that the
* indirection amount will be the same between references. because they aren't shared,
* don't bother putting them in the chain.
*/
memref = RC_ALLOC(rc_memref_t, parse);
}
memset(memref, 0, sizeof(*memref));
memref->address = address;
memref->value.size = size;
memref->value.is_indirect = is_indirect;
return memref;
}
int rc_parse_memref(const char** memaddr, char* size, unsigned* address) {
const char* aux = *memaddr;
char* end;
unsigned long value;
if (aux[0] == '0') {
if (aux[1] != 'x' && aux[1] != 'X')
return RC_INVALID_MEMORY_OPERAND;
aux += 2;
switch (*aux++) {
/* ordered by estimated frequency in case compiler doesn't build a jump table */
case 'h': case 'H': *size = RC_MEMSIZE_8_BITS; break;
case ' ': *size = RC_MEMSIZE_16_BITS; break;
case 'x': case 'X': *size = RC_MEMSIZE_32_BITS; break;
case 'm': case 'M': *size = RC_MEMSIZE_BIT_0; break;
case 'n': case 'N': *size = RC_MEMSIZE_BIT_1; break;
case 'o': case 'O': *size = RC_MEMSIZE_BIT_2; break;
case 'p': case 'P': *size = RC_MEMSIZE_BIT_3; break;
case 'q': case 'Q': *size = RC_MEMSIZE_BIT_4; break;
case 'r': case 'R': *size = RC_MEMSIZE_BIT_5; break;
case 's': case 'S': *size = RC_MEMSIZE_BIT_6; break;
case 't': case 'T': *size = RC_MEMSIZE_BIT_7; break;
case 'l': case 'L': *size = RC_MEMSIZE_LOW; break;
case 'u': case 'U': *size = RC_MEMSIZE_HIGH; break;
case 'k': case 'K': *size = RC_MEMSIZE_BITCOUNT; break;
case 'w': case 'W': *size = RC_MEMSIZE_24_BITS; break;
case 'g': case 'G': *size = RC_MEMSIZE_32_BITS_BE; break;
case 'i': case 'I': *size = RC_MEMSIZE_16_BITS_BE; break;
case 'j': case 'J': *size = RC_MEMSIZE_24_BITS_BE; break;
/* case 'v': case 'V': */
/* case 'y': case 'Y': 64 bit? */
/* case 'z': case 'Z': 128 bit? */
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
case 'a': case 'b': case 'c': case 'd': case 'e': case 'f':
case 'A': case 'B': case 'C': case 'D': case 'E': case 'F':
/* legacy support - addresses without a size prefix are assumed to be 16-bit */
aux--;
*size = RC_MEMSIZE_16_BITS;
break;
default:
return RC_INVALID_MEMORY_OPERAND;
}
}
else if (aux[0] == 'f' || aux[0] == 'F') {
++aux;
switch (*aux++) {
case 'f': case 'F': *size = RC_MEMSIZE_FLOAT; break;
case 'm': case 'M': *size = RC_MEMSIZE_MBF32; break;
case 'l': case 'L': *size = RC_MEMSIZE_MBF32_LE; break;
default:
return RC_INVALID_FP_OPERAND;
}
}
else {
return RC_INVALID_MEMORY_OPERAND;
}
value = strtoul(aux, &end, 16);
if (end == aux)
return RC_INVALID_MEMORY_OPERAND;
if (value > 0xffffffffU)
value = 0xffffffffU;
*address = (unsigned)value;
*memaddr = end;
return RC_OK;
}
static float rc_build_float(unsigned mantissa_bits, int exponent, int sign) {
/* 32-bit float has a 23-bit mantissa and 8-bit exponent */
const unsigned implied_bit = 1 << 23;
const unsigned mantissa = mantissa_bits | implied_bit;
double dbl = ((double)mantissa) / ((double)implied_bit);
if (exponent > 127) {
/* exponent above 127 is a special number */
if (mantissa_bits == 0) {
/* infinity */
#ifdef INFINITY /* INFINITY and NAN #defines require C99 */
dbl = INFINITY;
#else
dbl = -log(0.0);
#endif
}
else {
/* NaN */
#ifdef NAN
dbl = NAN;
#else
dbl = -sqrt(-1);
#endif
}
}
else if (exponent > 0) {
/* exponent from 1 to 127 is a number greater than 1 */
while (exponent > 30) {
dbl *= (double)(1 << 30);
exponent -= 30;
}
dbl *= (double)((long long)1 << exponent);
}
else if (exponent < 0) {
/* exponent from -1 to -127 is a number less than 1 */
if (exponent == -127) {
/* exponent -127 (all exponent bits were zero) is a denormalized value
* (no implied leading bit) with exponent -126 */
dbl = ((double)mantissa_bits) / ((double)implied_bit);
exponent = 126;
} else {
exponent = -exponent;
}
while (exponent > 30) {
dbl /= (double)(1 << 30);
exponent -= 30;
}
dbl /= (double)((long long)1 << exponent);
}
else {
/* exponent of 0 requires no adjustment */
}
return (sign) ? (float)-dbl : (float)dbl;
}
static void rc_transform_memref_float(rc_typed_value_t* value) {
/* decodes an IEEE 754 float */
const unsigned mantissa = (value->value.u32 & 0x7FFFFF);
const int exponent = (int)((value->value.u32 >> 23) & 0xFF) - 127;
const int sign = (value->value.u32 & 0x80000000);
value->value.f32 = rc_build_float(mantissa, exponent, sign);
value->type = RC_VALUE_TYPE_FLOAT;
}
static void rc_transform_memref_mbf32(rc_typed_value_t* value) {
/* decodes a Microsoft Binary Format float */
/* NOTE: 32-bit MBF is stored in memory as big endian (at least for Apple II) */
const unsigned mantissa = ((value->value.u32 & 0xFF000000) >> 24) |
((value->value.u32 & 0x00FF0000) >> 8) |
((value->value.u32 & 0x00007F00) << 8);
const int exponent = (int)(value->value.u32 & 0xFF) - 129;
const int sign = (value->value.u32 & 0x00008000);
if (mantissa == 0 && exponent == -129)
value->value.f32 = (sign) ? -0.0f : 0.0f;
else
value->value.f32 = rc_build_float(mantissa, exponent, sign);
value->type = RC_VALUE_TYPE_FLOAT;
}
static void rc_transform_memref_mbf32_le(rc_typed_value_t* value) {
/* decodes a Microsoft Binary Format float */
/* Locomotive BASIC (CPC) uses MBF40, but in little endian format */
const unsigned mantissa = value->value.u32 & 0x007FFFFF;
const int exponent = (int)(value->value.u32 >> 24) - 129;
const int sign = (value->value.u32 & 0x00800000);
if (mantissa == 0 && exponent == -129)
value->value.f32 = (sign) ? -0.0f : 0.0f;
else
value->value.f32 = rc_build_float(mantissa, exponent, sign);
value->type = RC_VALUE_TYPE_FLOAT;
}
static const unsigned char rc_bits_set[16] = { 0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4 };
void rc_transform_memref_value(rc_typed_value_t* value, char size) {
/* ASSERT: value->type == RC_VALUE_TYPE_UNSIGNED */
switch (size)
{
case RC_MEMSIZE_8_BITS:
value->value.u32 = (value->value.u32 & 0x000000ff);
break;
case RC_MEMSIZE_16_BITS:
value->value.u32 = (value->value.u32 & 0x0000ffff);
break;
case RC_MEMSIZE_24_BITS:
value->value.u32 = (value->value.u32 & 0x00ffffff);
break;
case RC_MEMSIZE_32_BITS:
break;
case RC_MEMSIZE_BIT_0:
value->value.u32 = (value->value.u32 >> 0) & 1;
break;
case RC_MEMSIZE_BIT_1:
value->value.u32 = (value->value.u32 >> 1) & 1;
break;
case RC_MEMSIZE_BIT_2:
value->value.u32 = (value->value.u32 >> 2) & 1;
break;
case RC_MEMSIZE_BIT_3:
value->value.u32 = (value->value.u32 >> 3) & 1;
break;
case RC_MEMSIZE_BIT_4:
value->value.u32 = (value->value.u32 >> 4) & 1;
break;
case RC_MEMSIZE_BIT_5:
value->value.u32 = (value->value.u32 >> 5) & 1;
break;
case RC_MEMSIZE_BIT_6:
value->value.u32 = (value->value.u32 >> 6) & 1;
break;
case RC_MEMSIZE_BIT_7:
value->value.u32 = (value->value.u32 >> 7) & 1;
break;
case RC_MEMSIZE_LOW:
value->value.u32 = value->value.u32 & 0x0f;
break;
case RC_MEMSIZE_HIGH:
value->value.u32 = (value->value.u32 >> 4) & 0x0f;
break;
case RC_MEMSIZE_BITCOUNT:
value->value.u32 = rc_bits_set[(value->value.u32 & 0x0F)]
+ rc_bits_set[((value->value.u32 >> 4) & 0x0F)];
break;
case RC_MEMSIZE_16_BITS_BE:
value->value.u32 = ((value->value.u32 & 0xFF00) >> 8) |
((value->value.u32 & 0x00FF) << 8);
break;
case RC_MEMSIZE_24_BITS_BE:
value->value.u32 = ((value->value.u32 & 0xFF0000) >> 16) |
(value->value.u32 & 0x00FF00) |
((value->value.u32 & 0x0000FF) << 16);
break;
case RC_MEMSIZE_32_BITS_BE:
value->value.u32 = ((value->value.u32 & 0xFF000000) >> 24) |
((value->value.u32 & 0x00FF0000) >> 8) |
((value->value.u32 & 0x0000FF00) << 8) |
((value->value.u32 & 0x000000FF) << 24);
break;
case RC_MEMSIZE_FLOAT:
rc_transform_memref_float(value);
break;
case RC_MEMSIZE_MBF32:
rc_transform_memref_mbf32(value);
break;
case RC_MEMSIZE_MBF32_LE:
rc_transform_memref_mbf32_le(value);
break;
default:
break;
}
}
static const unsigned rc_memref_masks[] = {
0x000000ff, /* RC_MEMSIZE_8_BITS */
0x0000ffff, /* RC_MEMSIZE_16_BITS */
0x00ffffff, /* RC_MEMSIZE_24_BITS */
0xffffffff, /* RC_MEMSIZE_32_BITS */
0x0000000f, /* RC_MEMSIZE_LOW */
0x000000f0, /* RC_MEMSIZE_HIGH */
0x00000001, /* RC_MEMSIZE_BIT_0 */
0x00000002, /* RC_MEMSIZE_BIT_1 */
0x00000004, /* RC_MEMSIZE_BIT_2 */
0x00000008, /* RC_MEMSIZE_BIT_3 */
0x00000010, /* RC_MEMSIZE_BIT_4 */
0x00000020, /* RC_MEMSIZE_BIT_5 */
0x00000040, /* RC_MEMSIZE_BIT_6 */
0x00000080, /* RC_MEMSIZE_BIT_7 */
0x000000ff, /* RC_MEMSIZE_BITCOUNT */
0x0000ffff, /* RC_MEMSIZE_16_BITS_BE */
0x00ffffff, /* RC_MEMSIZE_24_BITS_BE */
0xffffffff, /* RC_MEMSIZE_32_BITS_BE */
0xffffffff, /* RC_MEMSIZE_FLOAT */
0xffffffff, /* RC_MEMSIZE_MBF32 */
0xffffffff, /* RC_MEMSIZE_MBF32_LE */
0xffffffff /* RC_MEMSIZE_VARIABLE */
};
unsigned rc_memref_mask(char size) {
const size_t index = (size_t)size;
if (index >= sizeof(rc_memref_masks) / sizeof(rc_memref_masks[0]))
return 0xffffffff;
return rc_memref_masks[index];
}
/* all sizes less than 8-bits (1 byte) are mapped to 8-bits. 24-bit is mapped to 32-bit
* as we don't expect the client to understand a request for 3 bytes. all other reads are
* mapped to the little-endian read of the same size. */
static const char rc_memref_shared_sizes[] = {
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_8_BITS */
RC_MEMSIZE_16_BITS, /* RC_MEMSIZE_16_BITS */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_24_BITS */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_32_BITS */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_LOW */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_HIGH */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_0 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_1 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_2 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_3 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_4 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_5 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_6 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BIT_7 */
RC_MEMSIZE_8_BITS, /* RC_MEMSIZE_BITCOUNT */
RC_MEMSIZE_16_BITS, /* RC_MEMSIZE_16_BITS_BE */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_24_BITS_BE */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_32_BITS_BE */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_FLOAT */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32 */
RC_MEMSIZE_32_BITS, /* RC_MEMSIZE_MBF32_LE */
RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */
};
char rc_memref_shared_size(char size) {
const size_t index = (size_t)size;
if (index >= sizeof(rc_memref_shared_sizes) / sizeof(rc_memref_shared_sizes[0]))
return size;
return rc_memref_shared_sizes[index];
}
static unsigned rc_peek_value(unsigned address, char size, rc_peek_t peek, void* ud) {
if (!peek)
return 0;
switch (size)
{
case RC_MEMSIZE_8_BITS:
return peek(address, 1, ud);
case RC_MEMSIZE_16_BITS:
return peek(address, 2, ud);
case RC_MEMSIZE_32_BITS:
return peek(address, 4, ud);
default:
{
unsigned value;
const size_t index = (size_t)size;
if (index >= sizeof(rc_memref_shared_sizes) / sizeof(rc_memref_shared_sizes[0]))
return 0;
/* fetch the larger value and mask off the bits associated to the specified size
* for correct deduction of prior value. non-prior memrefs should already be using
* shared size memrefs to minimize the total number of memory reads required. */
value = rc_peek_value(address, rc_memref_shared_sizes[index], peek, ud);
return value & rc_memref_masks[index];
}
}
}
void rc_update_memref_value(rc_memref_value_t* memref, unsigned new_value) {
if (memref->value == new_value) {
memref->changed = 0;
}
else {
memref->prior = memref->value;
memref->value = new_value;
memref->changed = 1;
}
}
void rc_update_memref_values(rc_memref_t* memref, rc_peek_t peek, void* ud) {
while (memref) {
/* indirect memory references are not shared and will be updated in rc_get_memref_value */
if (!memref->value.is_indirect)
rc_update_memref_value(&memref->value, rc_peek_value(memref->address, memref->value.size, peek, ud));
memref = memref->next;
}
}
void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs) {
parse->first_memref = memrefs;
*memrefs = 0;
}
static unsigned rc_get_memref_value_value(rc_memref_value_t* memref, int operand_type) {
switch (operand_type)
{
/* most common case explicitly first, even though it could be handled by default case.
* this helps the compiler to optimize if it turns the switch into a series of if/elses */
case RC_OPERAND_ADDRESS:
return memref->value;
case RC_OPERAND_DELTA:
if (!memref->changed) {
/* fallthrough */
default:
return memref->value;
}
/* fallthrough */
case RC_OPERAND_PRIOR:
return memref->prior;
}
}
unsigned rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state) {
/* if this is an indirect reference, handle the indirection. */
if (memref->value.is_indirect) {
const unsigned new_address = memref->address + eval_state->add_address;
rc_update_memref_value(&memref->value, rc_peek_value(new_address, memref->value.size, eval_state->peek, eval_state->peek_userdata));
}
return rc_get_memref_value_value(&memref->value, operand_type);
}

@ -0,0 +1,477 @@
#include "rc_internal.h"
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#ifndef RC_DISABLE_LUA
#ifdef __cplusplus
extern "C" {
#endif
#include <lua.h>
#include <lauxlib.h>
#ifdef __cplusplus
}
#endif
#endif /* RC_DISABLE_LUA */
static int rc_parse_operand_lua(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse) {
const char* aux = *memaddr;
#ifndef RC_DISABLE_LUA
const char* id;
#endif
if (*aux++ != '@') {
return RC_INVALID_LUA_OPERAND;
}
if (!isalpha((unsigned char)*aux)) {
return RC_INVALID_LUA_OPERAND;
}
#ifndef RC_DISABLE_LUA
id = aux;
#endif
while (isalnum((unsigned char)*aux) || *aux == '_') {
aux++;
}
#ifndef RC_DISABLE_LUA
if (parse->L != 0) {
if (!lua_istable(parse->L, parse->funcs_ndx)) {
return RC_INVALID_LUA_OPERAND;
}
lua_pushlstring(parse->L, id, aux - id);
lua_gettable(parse->L, parse->funcs_ndx);
if (!lua_isfunction(parse->L, -1)) {
lua_pop(parse->L, 1);
return RC_INVALID_LUA_OPERAND;
}
self->value.luafunc = luaL_ref(parse->L, LUA_REGISTRYINDEX);
}
#endif /* RC_DISABLE_LUA */
self->type = RC_OPERAND_LUA;
*memaddr = aux;
return RC_OK;
}
static int rc_parse_operand_memory(rc_operand_t* self, const char** memaddr, rc_parse_state_t* parse, int is_indirect) {
const char* aux = *memaddr;
unsigned address;
char size;
int ret;
switch (*aux) {
case 'd': case 'D':
self->type = RC_OPERAND_DELTA;
++aux;
break;
case 'p': case 'P':
self->type = RC_OPERAND_PRIOR;
++aux;
break;
case 'b': case 'B':
self->type = RC_OPERAND_BCD;
++aux;
break;
case '~':
self->type = RC_OPERAND_INVERTED;
++aux;
break;
default:
self->type = RC_OPERAND_ADDRESS;
break;
}
ret = rc_parse_memref(&aux, &self->size, &address);
if (ret != RC_OK)
return ret;
size = rc_memref_shared_size(self->size);
if (size != self->size && self->type == RC_OPERAND_PRIOR) {
/* if the shared size differs from the requested size and it's a prior operation, we
* have to check to make sure both sizes use the same mask, or the prior value may be
* updated when bits outside the mask are modified, which would make it look like the
* current value once the mask is applied. if the mask differs, create a new
* non-shared record for tracking the prior data. */
if (rc_memref_mask(size) != rc_memref_mask(self->size))
size = self->size;
}
self->value.memref = rc_alloc_memref(parse, address, size, (char)is_indirect);
if (parse->offset < 0)
return parse->offset;
*memaddr = aux;
return RC_OK;
}
int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_indirect, rc_parse_state_t* parse) {
const char* aux = *memaddr;
char* end;
int ret;
unsigned long value;
int negative;
int allow_decimal = 0;
self->size = RC_MEMSIZE_32_BITS;
switch (*aux) {
case 'h': case 'H': /* hex constant */
if (aux[2] == 'x' || aux[2] == 'X') {
/* H0x1234 is a typo - either H1234 or 0xH1234 was probably meant */
return RC_INVALID_CONST_OPERAND;
}
value = strtoul(++aux, &end, 16);
if (end == aux)
return RC_INVALID_CONST_OPERAND;
if (value > 0xffffffffU)
value = 0xffffffffU;
self->type = RC_OPERAND_CONST;
self->value.num = (unsigned)value;
aux = end;
break;
case 'f': case 'F': /* floating point constant */
if (isalpha((unsigned char)aux[1])) {
ret = rc_parse_operand_memory(self, &aux, parse, is_indirect);
if (ret < 0)
return ret;
break;
}
allow_decimal = 1;
/* fall through */
case 'v': case 'V': /* signed integer constant */
++aux;
/* fall through */
case '+': case '-': /* signed integer constant */
negative = 0;
if (*aux == '-') {
negative = 1;
++aux;
}
else if (*aux == '+') {
++aux;
}
value = strtoul(aux, &end, 10);
if (*end == '.' && allow_decimal) {
/* custom parser for decimal values to ignore locale */
unsigned long shift = 1;
unsigned long fraction = 0;
aux = end + 1;
if (*aux < '0' || *aux > '9')
return RC_INVALID_FP_OPERAND;
do {
/* only keep as many digits as will fit in a 32-bit value to prevent overflow.
* float only has around 7 digits of precision anyway. */
if (shift < 1000000000) {
fraction *= 10;
fraction += (*aux - '0');
shift *= 10;
}
++aux;
} while (*aux >= '0' && *aux <= '9');
if (fraction != 0) {
/* non-zero fractional part, convert to double and merge in integer portion */
const double dbl_fraction = ((double)fraction) / ((double)shift);
if (negative)
self->value.dbl = ((double)(-((long)value))) - dbl_fraction;
else
self->value.dbl = (double)value + dbl_fraction;
}
else {
/* fractional part is 0, just convert the integer portion */
if (negative)
self->value.dbl = (double)(-((long)value));
else
self->value.dbl = (double)value;
}
self->type = RC_OPERAND_FP;
}
else {
/* not a floating point value, make sure something was read and advance the read pointer */
if (end == aux)
return allow_decimal ? RC_INVALID_FP_OPERAND : RC_INVALID_CONST_OPERAND;
aux = end;
if (value > 0x7fffffffU)
value = 0x7fffffffU;
self->type = RC_OPERAND_CONST;
if (negative)
self->value.num = (unsigned)(-((long)value));
else
self->value.num = (unsigned)value;
}
break;
case '0':
if (aux[1] == 'x' || aux[1] == 'X') { /* hex integer constant */
/* fall through */
default:
ret = rc_parse_operand_memory(self, &aux, parse, is_indirect);
if (ret < 0)
return ret;
break;
}
/* fall through for case '0' where not '0x' */
case '1': case '2': case '3': case '4': case '5': /* unsigned integer constant */
case '6': case '7': case '8': case '9':
value = strtoul(aux, &end, 10);
if (end == aux)
return RC_INVALID_CONST_OPERAND;
if (value > 0xffffffffU)
value = 0xffffffffU;
self->type = RC_OPERAND_CONST;
self->value.num = (unsigned)value;
aux = end;
break;
case '@':
ret = rc_parse_operand_lua(self, &aux, parse);
if (ret < 0)
return ret;
break;
}
*memaddr = aux;
return RC_OK;
}
#ifndef RC_DISABLE_LUA
typedef struct {
rc_peek_t peek;
void* ud;
}
rc_luapeek_t;
static int rc_luapeek(lua_State* L) {
unsigned address = (unsigned)luaL_checkinteger(L, 1);
unsigned num_bytes = (unsigned)luaL_checkinteger(L, 2);
rc_luapeek_t* luapeek = (rc_luapeek_t*)lua_touserdata(L, 3);
unsigned value = luapeek->peek(address, num_bytes, luapeek->ud);
lua_pushinteger(L, value);
return 1;
}
#endif /* RC_DISABLE_LUA */
int rc_operand_is_float_memref(const rc_operand_t* self) {
switch (self->size) {
case RC_MEMSIZE_FLOAT:
case RC_MEMSIZE_MBF32:
case RC_MEMSIZE_MBF32_LE:
return 1;
default:
return 0;
}
}
int rc_operand_is_memref(const rc_operand_t* self) {
switch (self->type) {
case RC_OPERAND_CONST:
case RC_OPERAND_FP:
case RC_OPERAND_LUA:
return 0;
default:
return 1;
}
}
int rc_operand_is_float(const rc_operand_t* self) {
if (self->type == RC_OPERAND_FP)
return 1;
return rc_operand_is_float_memref(self);
}
unsigned rc_transform_operand_value(unsigned value, const rc_operand_t* self) {
switch (self->type)
{
case RC_OPERAND_BCD:
switch (self->size)
{
case RC_MEMSIZE_8_BITS:
value = ((value >> 4) & 0x0f) * 10
+ ((value ) & 0x0f);
break;
case RC_MEMSIZE_16_BITS:
case RC_MEMSIZE_16_BITS_BE:
value = ((value >> 12) & 0x0f) * 1000
+ ((value >> 8) & 0x0f) * 100
+ ((value >> 4) & 0x0f) * 10
+ ((value ) & 0x0f);
break;
case RC_MEMSIZE_24_BITS:
case RC_MEMSIZE_24_BITS_BE:
value = ((value >> 20) & 0x0f) * 100000
+ ((value >> 16) & 0x0f) * 10000
+ ((value >> 12) & 0x0f) * 1000
+ ((value >> 8) & 0x0f) * 100
+ ((value >> 4) & 0x0f) * 10
+ ((value ) & 0x0f);
break;
case RC_MEMSIZE_32_BITS:
case RC_MEMSIZE_32_BITS_BE:
case RC_MEMSIZE_VARIABLE:
value = ((value >> 28) & 0x0f) * 10000000
+ ((value >> 24) & 0x0f) * 1000000
+ ((value >> 20) & 0x0f) * 100000
+ ((value >> 16) & 0x0f) * 10000
+ ((value >> 12) & 0x0f) * 1000
+ ((value >> 8) & 0x0f) * 100
+ ((value >> 4) & 0x0f) * 10
+ ((value ) & 0x0f);
break;
default:
break;
}
break;
case RC_OPERAND_INVERTED:
switch (self->size)
{
case RC_MEMSIZE_LOW:
case RC_MEMSIZE_HIGH:
value ^= 0x0f;
break;
case RC_MEMSIZE_8_BITS:
value ^= 0xff;
break;
case RC_MEMSIZE_16_BITS:
case RC_MEMSIZE_16_BITS_BE:
value ^= 0xffff;
break;
case RC_MEMSIZE_24_BITS:
case RC_MEMSIZE_24_BITS_BE:
value ^= 0xffffff;
break;
case RC_MEMSIZE_32_BITS:
case RC_MEMSIZE_32_BITS_BE:
case RC_MEMSIZE_VARIABLE:
value ^= 0xffffffff;
break;
default:
value ^= 0x01;
break;
}
break;
default:
break;
}
return value;
}
void rc_evaluate_operand(rc_typed_value_t* result, rc_operand_t* self, rc_eval_state_t* eval_state) {
#ifndef RC_DISABLE_LUA
rc_luapeek_t luapeek;
#endif /* RC_DISABLE_LUA */
/* step 1: read memory */
switch (self->type) {
case RC_OPERAND_CONST:
result->type = RC_VALUE_TYPE_UNSIGNED;
result->value.u32 = self->value.num;
return;
case RC_OPERAND_FP:
result->type = RC_VALUE_TYPE_FLOAT;
result->value.f32 = (float)self->value.dbl;
return;
case RC_OPERAND_LUA:
result->type = RC_VALUE_TYPE_UNSIGNED;
result->value.u32 = 0;
#ifndef RC_DISABLE_LUA
if (eval_state->L != 0) {
lua_rawgeti(eval_state->L, LUA_REGISTRYINDEX, self->value.luafunc);
lua_pushcfunction(eval_state->L, rc_luapeek);
luapeek.peek = eval_state->peek;
luapeek.ud = eval_state->peek_userdata;
lua_pushlightuserdata(eval_state->L, &luapeek);
if (lua_pcall(eval_state->L, 2, 1, 0) == LUA_OK) {
if (lua_isboolean(eval_state->L, -1)) {
result->value.u32 = (unsigned)lua_toboolean(eval_state->L, -1);
}
else {
result->value.u32 = (unsigned)lua_tonumber(eval_state->L, -1);
}
}
lua_pop(eval_state->L, 1);
}
#endif /* RC_DISABLE_LUA */
break;
default:
result->type = RC_VALUE_TYPE_UNSIGNED;
result->value.u32 = rc_get_memref_value(self->value.memref, self->type, eval_state);
break;
}
/* step 2: convert read memory to desired format */
rc_transform_memref_value(result, self->size);
/* step 3: apply logic (BCD/invert) */
if (result->type == RC_VALUE_TYPE_UNSIGNED)
result->value.u32 = rc_transform_operand_value(result->value.u32, self);
}

@ -0,0 +1,60 @@
#ifndef RC_COMPAT_H
#define RC_COMPAT_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(MINGW) || defined(__MINGW32__) || defined(__MINGW64__)
/* MinGW redefinitions */
#elif defined(_MSC_VER)
/* Visual Studio redefinitions */
#ifndef strcasecmp
#define strcasecmp _stricmp
#endif
#ifndef strncasecmp
#define strncasecmp _strnicmp
#endif
#ifndef strdup
#define strdup _strdup
#endif
#elif __STDC_VERSION__ < 199901L
/* C89 redefinitions */
#ifndef snprintf
extern int rc_snprintf(char* buffer, size_t size, const char* format, ...);
#define snprintf rc_snprintf
#endif
#ifndef strncasecmp
extern int rc_strncasecmp(const char* left, const char* right, size_t length);
#define strncasecmp rc_strncasecmp
#endif
#ifndef strcasecmp
extern int rc_strcasecmp(const char* left, const char* right);
#define strcasecmp rc_strcasecmp
#endif
#ifndef strdup
extern char* rc_strdup(const char* str);
#define strdup rc_strdup
#endif
#endif /* __STDC_VERSION__ < 199901L */
#ifdef __cplusplus
}
#endif
#endif /* RC_COMPAT_H */

@ -0,0 +1,183 @@
#ifndef INTERNAL_H
#define INTERNAL_H
#include "rc_runtime_types.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef struct rc_scratch_string {
char* value;
struct rc_scratch_string* left;
struct rc_scratch_string* right;
}
rc_scratch_string_t;
#define RC_ALLOW_ALIGN(T) struct __align_ ## T { char ch; T t; };
RC_ALLOW_ALIGN(rc_condition_t)
RC_ALLOW_ALIGN(rc_condset_t)
RC_ALLOW_ALIGN(rc_lboard_t)
RC_ALLOW_ALIGN(rc_memref_t)
RC_ALLOW_ALIGN(rc_operand_t)
RC_ALLOW_ALIGN(rc_richpresence_t)
RC_ALLOW_ALIGN(rc_richpresence_display_t)
RC_ALLOW_ALIGN(rc_richpresence_display_part_t)
RC_ALLOW_ALIGN(rc_richpresence_lookup_t)
RC_ALLOW_ALIGN(rc_richpresence_lookup_item_t)
RC_ALLOW_ALIGN(rc_scratch_string_t)
RC_ALLOW_ALIGN(rc_trigger_t)
RC_ALLOW_ALIGN(rc_value_t)
RC_ALLOW_ALIGN(char)
#define RC_ALIGNOF(T) (sizeof(struct __align_ ## T) - sizeof(T))
#define RC_OFFSETOF(o, t) (int)((char*)&(o.t) - (char*)&(o))
#define RC_ALLOC(t, p) ((t*)rc_alloc((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
#define RC_ALLOC_SCRATCH(t, p) ((t*)rc_alloc_scratch((p)->buffer, &(p)->offset, sizeof(t), RC_ALIGNOF(t), &(p)->scratch, RC_OFFSETOF((p)->scratch.objs, __ ## t)))
typedef struct rc_scratch_buffer {
struct rc_scratch_buffer* next;
int offset;
unsigned char buffer[512 - 16];
}
rc_scratch_buffer_t;
typedef struct {
rc_scratch_buffer_t buffer;
rc_scratch_string_t* strings;
struct objs {
rc_condition_t* __rc_condition_t;
rc_condset_t* __rc_condset_t;
rc_lboard_t* __rc_lboard_t;
rc_memref_t* __rc_memref_t;
rc_operand_t* __rc_operand_t;
rc_richpresence_t* __rc_richpresence_t;
rc_richpresence_display_t* __rc_richpresence_display_t;
rc_richpresence_display_part_t* __rc_richpresence_display_part_t;
rc_richpresence_lookup_t* __rc_richpresence_lookup_t;
rc_richpresence_lookup_item_t* __rc_richpresence_lookup_item_t;
rc_scratch_string_t __rc_scratch_string_t;
rc_trigger_t* __rc_trigger_t;
rc_value_t* __rc_value_t;
} objs;
}
rc_scratch_t;
enum {
RC_VALUE_TYPE_NONE,
RC_VALUE_TYPE_UNSIGNED,
RC_VALUE_TYPE_SIGNED,
RC_VALUE_TYPE_FLOAT
};
typedef struct {
union {
unsigned u32;
int i32;
float f32;
} value;
char type;
}
rc_typed_value_t;
typedef struct {
rc_typed_value_t add_value;/* AddSource/SubSource */
int add_hits; /* AddHits */
unsigned add_address; /* AddAddress */
rc_peek_t peek;
void* peek_userdata;
lua_State* L;
rc_typed_value_t measured_value; /* Measured */
char was_reset; /* ResetIf triggered */
char has_hits; /* one of more hit counts is non-zero */
char primed; /* true if all non-Trigger conditions are true */
char measured_from_hits; /* true if the measured_value came from a condition's hit count */
char was_cond_reset; /* ResetNextIf triggered */
}
rc_eval_state_t;
typedef struct {
int offset;
lua_State* L;
int funcs_ndx;
void* buffer;
rc_scratch_t scratch;
rc_memref_t** first_memref;
rc_value_t** variables;
unsigned measured_target;
int lines_read;
char has_required_hits;
char measured_as_percent;
}
rc_parse_state_t;
void rc_init_parse_state(rc_parse_state_t* parse, void* buffer, lua_State* L, int funcs_ndx);
void rc_init_parse_state_memrefs(rc_parse_state_t* parse, rc_memref_t** memrefs);
void rc_init_parse_state_variables(rc_parse_state_t* parse, rc_value_t** variables);
void rc_destroy_parse_state(rc_parse_state_t* parse);
void* rc_alloc(void* pointer, int* offset, int size, int alignment, rc_scratch_t* scratch, int scratch_object_pointer_offset);
void* rc_alloc_scratch(void* pointer, int* offset, int size, int alignment, rc_scratch_t* scratch, int scratch_object_pointer_offset);
char* rc_alloc_str(rc_parse_state_t* parse, const char* text, int length);
rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, unsigned address, char size, char is_indirect);
int rc_parse_memref(const char** memaddr, char* size, unsigned* address);
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);
char rc_memref_shared_size(char size);
unsigned rc_memref_mask(char size);
void rc_transform_memref_value(rc_typed_value_t* 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);
void rc_reset_condset(rc_condset_t* self);
rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, int is_indirect);
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state);
void rc_evaluate_condition_value(rc_typed_value_t* value, rc_condition_t* self, rc_eval_state_t* eval_state);
int rc_condition_is_combining(const rc_condition_t* self);
int rc_parse_operand(rc_operand_t* self, const char** memaddr, int is_indirect, rc_parse_state_t* parse);
void rc_evaluate_operand(rc_typed_value_t* value, rc_operand_t* self, rc_eval_state_t* eval_state);
int rc_operand_is_float_memref(const rc_operand_t* self);
int rc_operand_is_float(const rc_operand_t* self);
void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse);
int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t peek, void* ud, lua_State* L);
void rc_reset_value(rc_value_t* self);
rc_value_t* rc_alloc_helper_variable(const char* memaddr, int memaddr_len, rc_parse_state_t* parse);
void rc_update_variables(rc_value_t* variable, rc_peek_t peek, void* ud, lua_State* L);
void rc_typed_value_convert(rc_typed_value_t* value, char new_type);
void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount);
void rc_typed_value_multiply(rc_typed_value_t* value, const rc_typed_value_t* amount);
void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amount);
int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper);
void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref);
int rc_format_typed_value(char* buffer, int size, const rc_typed_value_t* value, int format);
void rc_parse_lboard_internal(rc_lboard_t* self, const char* memaddr, rc_parse_state_t* parse);
int rc_lboard_state_active(int state);
void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, rc_parse_state_t* parse);
#ifdef __cplusplus
}
#endif
#endif /* INTERNAL_H */

@ -0,0 +1,755 @@
#include "rc_validate.h"
#include "rc_compat.h"
#include "rc_internal.h"
#include <stddef.h>
#include <stdlib.h>
static unsigned rc_max_value(const rc_operand_t* operand)
{
if (operand->type == RC_OPERAND_CONST)
return operand->value.num;
if (!rc_operand_is_memref(operand))
return 0xFFFFFFFF;
switch (operand->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:
return 1;
case RC_MEMSIZE_LOW:
case RC_MEMSIZE_HIGH:
return 0xF;
case RC_MEMSIZE_BITCOUNT:
return 8;
case RC_MEMSIZE_8_BITS:
return (operand->type == RC_OPERAND_BCD) ? 165 : 0xFF;
case RC_MEMSIZE_16_BITS:
case RC_MEMSIZE_16_BITS_BE:
return (operand->type == RC_OPERAND_BCD) ? 16665 : 0xFFFF;
case RC_MEMSIZE_24_BITS:
case RC_MEMSIZE_24_BITS_BE:
return (operand->type == RC_OPERAND_BCD) ? 1666665 : 0xFFFFFF;
default:
return (operand->type == RC_OPERAND_BCD) ? 166666665 : 0xFFFFFFFF;
}
}
static unsigned rc_scale_value(unsigned value, char oper, const rc_operand_t* operand)
{
switch (oper) {
case RC_OPERATOR_MULT:
{
unsigned long long scaled = ((unsigned long long)value) * rc_max_value(operand);
if (scaled > 0xFFFFFFFF)
return 0xFFFFFFFF;
return (unsigned)scaled;
}
case RC_OPERATOR_DIV:
{
const unsigned min_val = (operand->type == RC_OPERAND_CONST) ? operand->value.num : 1;
return value / min_val;
}
case RC_OPERATOR_AND:
return rc_max_value(operand);
case RC_OPERATOR_XOR:
return value | rc_max_value(operand);
default:
return value;
}
}
static int rc_validate_get_condition_index(const rc_condset_t* condset, const rc_condition_t* condition)
{
int index = 1;
const rc_condition_t* scan;
for (scan = condset->conditions; scan != NULL; scan = scan->next)
{
if (scan == condition)
return index;
++index;
}
return 0;
}
static int rc_validate_range(unsigned min_val, unsigned max_val, char oper, unsigned max, char result[], const size_t result_size) {
switch (oper) {
case RC_OPERATOR_AND:
if (min_val > max) {
snprintf(result, result_size, "Mask has more bits than source");
return 0;
}
else if (min_val == 0 && max_val == 0) {
snprintf(result, result_size, "Result of mask always 0");
return 0;
}
break;
case RC_OPERATOR_EQ:
if (min_val > max) {
snprintf(result, result_size, "Comparison is never true");
return 0;
}
break;
case RC_OPERATOR_NE:
if (min_val > max) {
snprintf(result, result_size, "Comparison is always true");
return 0;
}
break;
case RC_OPERATOR_GE:
if (min_val > max) {
snprintf(result, result_size, "Comparison is never true");
return 0;
}
if (max_val == 0) {
snprintf(result, result_size, "Comparison is always true");
return 0;
}
break;
case RC_OPERATOR_GT:
if (min_val >= max) {
snprintf(result, result_size, "Comparison is never true");
return 0;
}
break;
case RC_OPERATOR_LE:
if (min_val >= max) {
snprintf(result, result_size, "Comparison is always true");
return 0;
}
break;
case RC_OPERATOR_LT:
if (min_val > max) {
snprintf(result, result_size, "Comparison is always true");
return 0;
}
if (max_val == 0) {
snprintf(result, result_size, "Comparison is never true");
return 0;
}
break;
}
return 1;
}
int rc_validate_condset(const rc_condset_t* condset, char result[], const size_t result_size, unsigned max_address) {
const rc_condition_t* cond;
unsigned max_val;
int index = 1;
unsigned long long add_source_max = 0;
int in_add_hits = 0;
int in_add_address = 0;
int is_combining = 0;
if (!condset) {
*result = '\0';
return 1;
}
for (cond = condset->conditions; cond; cond = cond->next, ++index) {
unsigned max = rc_max_value(&cond->operand1);
const int is_memref1 = rc_operand_is_memref(&cond->operand1);
const int is_memref2 = rc_operand_is_memref(&cond->operand2);
if (!in_add_address) {
if (is_memref1 && cond->operand1.value.memref->address > max_address) {
snprintf(result, result_size, "Condition %d: Address %04X out of range (max %04X)",
index, cond->operand1.value.memref->address, max_address);
return 0;
}
if (is_memref2 && cond->operand2.value.memref->address > max_address) {
snprintf(result, result_size, "Condition %d: Address %04X out of range (max %04X)",
index, cond->operand2.value.memref->address, max_address);
return 0;
}
}
else {
in_add_address = 0;
}
switch (cond->type) {
case RC_CONDITION_ADD_SOURCE:
max = rc_scale_value(max, cond->oper, &cond->operand2);
add_source_max += max;
is_combining = 1;
continue;
case RC_CONDITION_SUB_SOURCE:
max = rc_scale_value(max, cond->oper, &cond->operand2);
if (add_source_max < max) /* potential underflow - may be expected */
add_source_max = 0xFFFFFFFF;
is_combining = 1;
continue;
case RC_CONDITION_ADD_ADDRESS:
if (cond->operand1.type == RC_OPERAND_DELTA || cond->operand1.type == RC_OPERAND_PRIOR) {
snprintf(result, result_size, "Condition %d: Using pointer from previous frame", index);
return 0;
}
in_add_address = 1;
is_combining = 1;
continue;
case RC_CONDITION_ADD_HITS:
case RC_CONDITION_SUB_HITS:
in_add_hits = 1;
is_combining = 1;
break;
case RC_CONDITION_AND_NEXT:
case RC_CONDITION_OR_NEXT:
case RC_CONDITION_RESET_NEXT_IF:
is_combining = 1;
break;
default:
if (in_add_hits) {
if (cond->required_hits == 0) {
snprintf(result, result_size, "Condition %d: Final condition in AddHits chain must have a hit target", index);
return 0;
}
in_add_hits = 0;
}
is_combining = 0;
break;
}
/* if we're in an add source chain, check for overflow */
if (add_source_max) {
const unsigned long long overflow = add_source_max + max;
if (overflow > 0xFFFFFFFFUL)
max = 0xFFFFFFFF;
else
max += (unsigned)add_source_max;
}
/* check for comparing two differently sized memrefs */
max_val = rc_max_value(&cond->operand2);
if (max_val != max && add_source_max == 0 && is_memref1 && is_memref2) {
snprintf(result, result_size, "Condition %d: Comparing different memory sizes", index);
return 0;
}
/* if either side is a memref, or there's a running add source chain, check for impossible comparisons */
if (is_memref1 || is_memref2 || add_source_max) {
const size_t prefix_length = snprintf(result, result_size, "Condition %d: ", index);
unsigned min_val;
switch (cond->operand2.type) {
case RC_OPERAND_CONST:
min_val = cond->operand2.value.num;
break;
case RC_OPERAND_FP:
min_val = (int)cond->operand2.value.dbl;
/* cannot compare an integer memory reference to a non-integral floating point value */
/* assert: is_memref1 (because operand2==FP means !is_memref2) */
if (!add_source_max && !rc_operand_is_float_memref(&cond->operand1) &&
(float)min_val != cond->operand2.value.dbl) {
snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true");
return 0;
}
break;
default:
min_val = 0;
/* cannot compare an integer memory reference to a non-integral floating point value */
/* assert: is_memref2 (because operand1==FP means !is_memref1) */
if (cond->operand1.type == RC_OPERAND_FP && !add_source_max && !rc_operand_is_float_memref(&cond->operand2) &&
(float)((int)cond->operand1.value.dbl) != cond->operand1.value.dbl) {
snprintf(result + prefix_length, result_size - prefix_length, "Comparison is never true");
return 0;
}
break;
}
if (rc_operand_is_float(&cond->operand2) && rc_operand_is_float(&cond->operand1)) {
/* both sides are floats, don't validate range*/
} else if (!rc_validate_range(min_val, max_val, cond->oper, max, result + prefix_length, result_size - prefix_length)) {
return 0;
}
}
add_source_max = 0;
}
if (is_combining) {
snprintf(result, result_size, "Final condition type expects another condition to follow");
return 0;
}
*result = '\0';
return 1;
}
int rc_validate_memrefs(const rc_memref_t* memref, char result[], const size_t result_size, unsigned max_address) {
if (max_address < 0xFFFFFFFF) {
while (memref) {
if (memref->address > max_address) {
snprintf(result, result_size, "Address %04X out of range (max %04X)", memref->address, max_address);
return 0;
}
memref = memref->next;
}
}
return 1;
}
static int rc_validate_is_combining_condition(const rc_condition_t* condition)
{
switch (condition->type)
{
case RC_CONDITION_ADD_ADDRESS:
case RC_CONDITION_ADD_HITS:
case RC_CONDITION_ADD_SOURCE:
case RC_CONDITION_AND_NEXT:
case RC_CONDITION_OR_NEXT:
case RC_CONDITION_RESET_NEXT_IF:
case RC_CONDITION_SUB_HITS:
case RC_CONDITION_SUB_SOURCE:
return 1;
default:
return 0;
}
}
static const rc_condition_t* rc_validate_next_non_combining_condition(const rc_condition_t* condition)
{
int is_combining = rc_validate_is_combining_condition(condition);
for (condition = condition->next; condition != NULL; condition = condition->next)
{
if (rc_validate_is_combining_condition(condition))
is_combining = 1;
else if (is_combining)
is_combining = 0;
else
return condition;
}
return NULL;
}
static int rc_validate_get_opposite_comparison(int oper)
{
switch (oper)
{
case RC_OPERATOR_EQ: return RC_OPERATOR_NE;
case RC_OPERATOR_NE: return RC_OPERATOR_EQ;
case RC_OPERATOR_LT: return RC_OPERATOR_GE;
case RC_OPERATOR_LE: return RC_OPERATOR_GT;
case RC_OPERATOR_GT: return RC_OPERATOR_LE;
case RC_OPERATOR_GE: return RC_OPERATOR_LT;
default: return oper;
}
}
static const rc_operand_t* rc_validate_get_comparison(const rc_condition_t* condition, int* comparison, unsigned* value)
{
if (rc_operand_is_memref(&condition->operand1))
{
if (condition->operand2.type != RC_OPERAND_CONST)
return NULL;
*comparison = condition->oper;
*value = condition->operand2.value.num;
return &condition->operand1;
}
if (condition->operand1.type != RC_OPERAND_CONST)
return NULL;
if (!rc_operand_is_memref(&condition->operand2))
return NULL;
*comparison = rc_validate_get_opposite_comparison(condition->oper);
*value = condition->operand1.value.num;
return &condition->operand2;
}
enum {
RC_OVERLAP_NONE = 0,
RC_OVERLAP_CONFLICTING,
RC_OVERLAP_REDUNDANT,
RC_OVERLAP_DEFER
};
static int rc_validate_comparison_overlap(int comparison1, unsigned value1, int comparison2, unsigned value2)
{
/* NOTE: this only cares if comp2 conflicts with comp1.
* If comp1 conflicts with comp2, we'll catch that later (return RC_OVERLAP_NONE for now) */
switch (comparison2)
{
case RC_OPERATOR_EQ:
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
{ /* value1 = value2 value1 < value2 value1 > value2 */
case RC_OPERATOR_EQ: /* a == 1 && a == 1 | a == 1 && a == 2 | a == 2 && a == 1 */
/* redundant conflict conflict */
return (value1 == value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
case RC_OPERATOR_LE: /* a <= 1 && a == 1 | a <= 1 && a == 2 | a <= 2 && a == 1 */
/* defer conflict defer */
return (value1 < value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
case RC_OPERATOR_GE: /* a >= 1 && a == 1 | a >= 1 && a == 2 | a >= 2 && a == 1 */
/* defer defer conflict */
return (value1 > value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
case RC_OPERATOR_NE: /* a != 1 && a == 1 | a != 1 && a == 2 | a != 2 && a == 1 */
/* conflict defer defer */
return (value1 == value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
case RC_OPERATOR_LT: /* a < 1 && a == 1 | a < 1 && a == 2 | a < 2 && a == 1 */
/* conflict conflict defer */
return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
case RC_OPERATOR_GT: /* a > 1 && a == 1 | a > 1 && a == 2 | a > 2 && a == 1 */
/* conflict defer conflict */
return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
}
break;
case RC_OPERATOR_NE:
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
{ /* value1 = value2 value1 < value2 value1 > value2 */
case RC_OPERATOR_EQ: /* a == 1 && a != 1 | a == 1 && a != 2 | a == 2 && a != 1 */
/* conflict redundant redundant */
return (value1 == value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_REDUNDANT;
case RC_OPERATOR_LE: /* a <= 1 && a != 1 | a <= 1 && a != 2 | a <= 2 && a != 1 */
/* none redundant none */
return (value1 < value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
case RC_OPERATOR_GE: /* a >= 1 && a != 1 | a >= 1 && a != 2 | a >= 2 && a != 1 */
/* none none redundant */
return (value1 > value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
case RC_OPERATOR_NE: /* a != 1 && a != 1 | a != 1 && a != 2 | a != 2 && a != 1 */
/* redundant none none */
return (value1 == value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
case RC_OPERATOR_LT: /* a < 1 && a != 1 | a < 1 && a != 2 | a < 2 && a != 1 */
/* redundant redundant none */
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
case RC_OPERATOR_GT: /* a > 1 && a != 1 | a > 1 && a != 2 | a > 2 && a != 1 */
/* redundant none redundant */
return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_NONE;
}
break;
case RC_OPERATOR_LT:
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
{ /* value1 = value2 value1 < value2 value1 > value2 */
case RC_OPERATOR_EQ: /* a == 1 && a < 1 | a == 1 && a < 2 | a == 2 && a < 1 */
/* conflict redundant conflict */
return (value1 < value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
case RC_OPERATOR_LE: /* a <= 1 && a < 1 | a <= 1 && a < 2 | a <= 2 && a < 1 */
/* defer redundant defer */
return (value1 < value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
case RC_OPERATOR_GE: /* a >= 1 && a < 1 | a >= 1 && a < 2 | a >= 2 && a < 1 */
/* conflict none conflict */
return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
case RC_OPERATOR_NE: /* a != 1 && a < 1 | a != 1 && a < 2 | a != 2 && a < 1 */
/* defer none defer */
return (value1 >= value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE;
case RC_OPERATOR_LT: /* a < 1 && a < 1 | a < 1 && a < 2 | a < 2 && a < 1 */
/* redundant redundant defer */
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
case RC_OPERATOR_GT: /* a > 1 && a < 1 | a > 1 && a < 2 | a > 2 && a < 1 */
/* conflict none conflict */
return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
}
break;
case RC_OPERATOR_LE:
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
{ /* value1 = value2 value1 < value2 value1 > value2 */
case RC_OPERATOR_EQ: /* a == 1 && a <= 1 | a == 1 && a <= 2 | a == 2 && a <= 1 */
/* redundant redundant conflict */
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
case RC_OPERATOR_LE: /* a <= 1 && a <= 1 | a <= 1 && a <= 2 | a <= 2 && a <= 1 */
/* redundant redundant defer */
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
case RC_OPERATOR_GE: /* a >= 1 && a <= 1 | a >= 1 && a <= 2 | a >= 2 && a <= 1 */
/* none none conflict */
return (value1 > value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
case RC_OPERATOR_NE: /* a != 1 && a <= 1 | a != 1 && a <= 2 | a != 2 && a <= 1 */
/* none none defer */
return (value1 > value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE;
case RC_OPERATOR_LT: /* a < 1 && a <= 1 | a < 1 && a <= 2 | a < 2 && a <= 1 */
/* redundant redundant defer */
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
case RC_OPERATOR_GT: /* a > 1 && a <= 1 | a > 1 && a <= 2 | a > 2 && a <= 1 */
/* conflict none conflict */
return (value1 >= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
}
break;
case RC_OPERATOR_GT:
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
{ /* value1 = value2 value1 < value2 value1 > value2 */
case RC_OPERATOR_EQ: /* a == 1 && a > 1 | a == 1 && a > 2 | a == 2 && a > 1 */
/* conflict conflict redundant */
return (value1 > value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
case RC_OPERATOR_LE: /* a <= 1 && a > 1 | a <= 1 && a > 2 | a <= 2 && a > 1 */
/* conflict conflict defer */
return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_DEFER;
case RC_OPERATOR_GE: /* a >= 1 && a > 1 | a >= 1 && a > 2 | a >= 2 && a > 1 */
/* defer defer redundant */
return (value1 > value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
case RC_OPERATOR_NE: /* a != 1 && a > 1 | a != 1 && a > 2 | a != 2 && a > 1 */
/* defer defer none */
return (value1 <= value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE;
case RC_OPERATOR_LT: /* a < 1 && a > 1 | a < 1 && a > 2 | a < 2 && a > 1 */
/* conflict conflict none */
return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
case RC_OPERATOR_GT: /* a > 1 && a > 1 | a > 1 && a > 2 | a > 2 && a > 1 */
/* redundant defer redundant */
return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
}
break;
case RC_OPERATOR_GE:
switch (comparison1) /* comp1 comp2 comp1 comp2 comp1 comp2 */
{ /* value1 = value2 value1 < value2 value1 > value2 */
case RC_OPERATOR_EQ: /* a == 1 && a >= 1 | a == 1 && a >= 2 | a == 2 && a >= 1 */
/* redundant conflict redundant */
return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_CONFLICTING;
case RC_OPERATOR_LE: /* a <= 1 && a >= 1 | a <= 1 && a >= 2 | a <= 2 && a >= 1 */
/* none conflict none */
return (value1 < value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
case RC_OPERATOR_GE: /* a >= 1 && a >= 1 | a >= 1 && a >= 2 | a >= 2 && a >= 1 */
/* redundant redundant defer */
return (value1 <= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
case RC_OPERATOR_NE: /* a != 1 && a >= 1 | a != 1 && a >= 2 | a != 2 && a >= 1 */
/* none defer none */
return (value1 < value2) ? RC_OVERLAP_DEFER : RC_OVERLAP_NONE;
case RC_OPERATOR_LT: /* a < 1 && a >= 1 | a < 1 && a >= 2 | a < 2 && a >= 1 */
/* conflict conflict none */
return (value1 <= value2) ? RC_OVERLAP_CONFLICTING : RC_OVERLAP_NONE;
case RC_OPERATOR_GT: /* a > 1 && a >= 1 | a > 1 && a >= 2 | a > 2 && a >= 1 */
/* redundant defer redundant */
return (value1 >= value2) ? RC_OVERLAP_REDUNDANT : RC_OVERLAP_DEFER;
}
break;
}
return RC_OVERLAP_NONE;
}
static int rc_validate_conflicting_conditions(const rc_condset_t* conditions, const rc_condset_t* compare_conditions,
const char* prefix, const char* compare_prefix, char result[], const size_t result_size)
{
int comparison1, comparison2;
unsigned value1, value2;
const rc_operand_t* operand1;
const rc_operand_t* operand2;
const rc_condition_t* compare_condition;
const rc_condition_t* condition;
int overlap;
/* empty group */
if (conditions == NULL || compare_conditions == NULL)
return 1;
/* outer loop is the source conditions */
for (condition = conditions->conditions; condition != NULL;
condition = rc_validate_next_non_combining_condition(condition))
{
/* hits can be captured at any time, so any potential conflict will not be conflicting at another time */
if (condition->required_hits)
continue;
operand1 = rc_validate_get_comparison(condition, &comparison1, &value1);
if (!operand1)
continue;
switch (condition->type)
{
case RC_CONDITION_PAUSE_IF:
if (conditions != compare_conditions)
break;
/* fallthrough */
case RC_CONDITION_RESET_IF:
comparison1 = rc_validate_get_opposite_comparison(comparison1);
break;
default:
if (rc_validate_is_combining_condition(condition))
continue;
break;
}
/* inner loop is the potentially conflicting conditions */
for (compare_condition = compare_conditions->conditions; compare_condition != NULL;
compare_condition = rc_validate_next_non_combining_condition(compare_condition))
{
if (compare_condition == condition)
continue;
if (compare_condition->required_hits)
continue;
operand2 = rc_validate_get_comparison(compare_condition, &comparison2, &value2);
if (!operand2 || operand2->value.memref->address != operand1->value.memref->address ||
operand2->size != operand1->size || operand2->type != operand1->type)
continue;
switch (compare_condition->type)
{
case RC_CONDITION_PAUSE_IF:
if (conditions != compare_conditions)
break;
/* fallthrough */
case RC_CONDITION_RESET_IF:
comparison2 = rc_validate_get_opposite_comparison(comparison2);
break;
default:
if (rc_validate_is_combining_condition(compare_condition))
continue;
break;
}
overlap = rc_validate_comparison_overlap(comparison1, value1, comparison2, value2);
switch (overlap)
{
case RC_OVERLAP_CONFLICTING:
if (compare_condition->type == RC_CONDITION_PAUSE_IF || condition->type == RC_CONDITION_PAUSE_IF)
{
/* ignore PauseIf conflicts between groups, unless both conditions are PauseIfs */
if (conditions != compare_conditions && compare_condition->type != condition->type)
continue;
}
break;
case RC_OVERLAP_REDUNDANT:
if (compare_condition->type == RC_CONDITION_PAUSE_IF || condition->type == RC_CONDITION_PAUSE_IF)
{
/* ignore PauseIf redundancies between groups */
if (conditions != compare_conditions)
continue;
/* if the PauseIf is less restrictive than the other condition, it's just a guard. ignore it */
if (rc_validate_comparison_overlap(comparison2, value2, comparison1, value1) != RC_OVERLAP_REDUNDANT)
continue;
/* PauseIf redundant with ResetIf is a conflict (both are inverted comparisons) */
if (compare_condition->type == RC_CONDITION_RESET_IF || condition->type == RC_CONDITION_RESET_IF)
overlap = RC_OVERLAP_CONFLICTING;
}
else if (compare_condition->type == RC_CONDITION_RESET_IF && condition->type != RC_CONDITION_RESET_IF)
{
/* only ever report the redundancy on the non-ResetIf condition. The ResetIf is allowed to
* fire when the non-ResetIf condition is not true. */
continue;
}
else if (compare_condition->type == RC_CONDITION_MEASURED_IF || condition->type == RC_CONDITION_MEASURED_IF)
{
/* MeasuredIf is a meta tag and allowed to be redundant */
if (compare_condition->type != condition->type)
continue;
}
else if (compare_condition->type == RC_CONDITION_TRIGGER || condition->type == RC_CONDITION_TRIGGER)
{
/* Trigger is allowed to be redundant with non-trigger conditions as there may be limits that start a
* challenge that are furhter reduced for the completion of the challenge */
if (compare_condition->type != condition->type)
continue;
}
break;
default:
continue;
}
if (compare_prefix && *compare_prefix)
{
snprintf(result, result_size, "%s Condition %d: %s with %s Condition %d",
compare_prefix, rc_validate_get_condition_index(compare_conditions, compare_condition),
(overlap == RC_OVERLAP_REDUNDANT) ? "Redundant" : "Conflicts",
prefix, rc_validate_get_condition_index(conditions, condition));
}
else
{
snprintf(result, result_size, "Condition %d: %s with Condition %d",
rc_validate_get_condition_index(compare_conditions, compare_condition),
(overlap == RC_OVERLAP_REDUNDANT) ? "Redundant" : "Conflicts",
rc_validate_get_condition_index(conditions, condition));
}
return 0;
}
}
return 1;
}
int rc_validate_trigger(const rc_trigger_t* trigger, char result[], const size_t result_size, unsigned max_address) {
const rc_condset_t* alt;
int index;
if (!trigger->alternative)
{
if (!rc_validate_condset(trigger->requirement, result, result_size, max_address))
return 0;
return rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, "", "", result, result_size);
}
snprintf(result, result_size, "Core ");
if (!rc_validate_condset(trigger->requirement, result + 5, result_size - 5, max_address))
return 0;
/* compare core to itself */
if (!rc_validate_conflicting_conditions(trigger->requirement, trigger->requirement, "Core", "Core", result, result_size))
return 0;
index = 1;
for (alt = trigger->alternative; alt; alt = alt->next, ++index) {
char altname[16];
const size_t prefix_length = snprintf(result, result_size, "Alt%d ", index);
if (!rc_validate_condset(alt, result + prefix_length, result_size - prefix_length, max_address))
return 0;
/* compare alt to itself */
snprintf(altname, sizeof(altname), "Alt%d", index);
if (!rc_validate_conflicting_conditions(alt, alt, altname, altname, result, result_size))
return 0;
/* compare alt to core */
if (!rc_validate_conflicting_conditions(trigger->requirement, alt, "Core", altname, result, result_size))
return 0;
/* compare core to alt */
if (!rc_validate_conflicting_conditions(alt, trigger->requirement, altname, "Core", result, result_size))
return 0;
}
*result = '\0';
return 1;
}

@ -0,0 +1,21 @@
#ifndef RC_VALIDATE_H
#define RC_VALIDATE_H
#include "rc_runtime_types.h"
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
int rc_validate_memrefs(const rc_memref_t* memref, char result[], const size_t result_size, unsigned max_address);
int rc_validate_condset(const rc_condset_t* condset, char result[], const size_t result_size, unsigned max_address);
int rc_validate_trigger(const rc_trigger_t* trigger, char result[], const size_t result_size, unsigned max_address);
#ifdef __cplusplus
}
#endif
#endif /* RC_VALIDATE_H */

@ -0,0 +1,846 @@
#include "rc_internal.h"
#include "rc_compat.h"
#include <ctype.h>
/* special formats only used by rc_richpresence_display_part_t.display_type. must not overlap other RC_FORMAT values */
enum {
RC_FORMAT_STRING = 101,
RC_FORMAT_LOOKUP = 102,
RC_FORMAT_UNKNOWN_MACRO = 103,
RC_FORMAT_ASCIICHAR = 104,
RC_FORMAT_UNICODECHAR = 105
};
static rc_memref_value_t* rc_alloc_helper_variable_memref_value(const char* memaddr, int memaddr_len, rc_parse_state_t* parse) {
const char* end;
rc_value_t* variable;
unsigned address;
char size;
/* single memory reference lookups without a modifier flag can be handled without a variable */
end = memaddr;
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]) {
/* 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;
}
}
/* not a simple memory reference, need to create a variable */
variable = rc_alloc_helper_variable(memaddr, memaddr_len, parse);
if (!variable)
return NULL;
return &variable->value;
}
static const char* rc_parse_line(const char* line, const char** end, rc_parse_state_t* parse) {
const char* nextline;
const char* endline;
/* get a single line */
nextline = line;
while (*nextline && *nextline != '\n')
++nextline;
/* if a trailing comment marker (//) exists, the line stops there */
endline = line;
while (endline < nextline && (endline[0] != '/' || endline[1] != '/' || (endline > line && endline[-1] == '\\')))
++endline;
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 {
/* remove trailing whitespace before the comment marker */
while (endline > line && isspace((int)((unsigned char*)endline)[-1]))
--endline;
}
/* 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;
}
typedef struct rc_richpresence_builtin_macro_t {
const char* name;
size_t name_len;
unsigned short display_type;
} rc_richpresence_builtin_macro_t;
static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const char* line, const char* endline, rc_parse_state_t* parse, rc_richpresence_lookup_t* first_lookup) {
rc_richpresence_display_t* self;
rc_richpresence_display_part_t* part;
rc_richpresence_display_part_t** next;
rc_richpresence_lookup_t* lookup;
const char* ptr;
const char* in;
char* out;
if (endline - line < 1) {
parse->offset = RC_MISSING_DISPLAY_STRING;
return 0;
}
{
self = RC_ALLOC(rc_richpresence_display_t, parse);
memset(self, 0, sizeof(rc_richpresence_display_t));
next = &self->display;
}
/* break the string up on macros: text @macro() moretext */
do {
ptr = line;
while (ptr < endline) {
if (*ptr == '@' && (ptr == line || ptr[-1] != '\\')) /* ignore escaped @s */
break;
++ptr;
}
if (ptr > line) {
part = RC_ALLOC(rc_richpresence_display_part_t, parse);
memset(part, 0, sizeof(rc_richpresence_display_part_t));
*next = part;
next = &part->next;
/* handle string part */
part->display_type = RC_FORMAT_STRING;
part->text = rc_alloc_str(parse, line, (int)(ptr - line));
if (part->text) {
/* remove backslashes used for escaping */
in = part->text;
while (*in && *in != '\\')
++in;
if (*in == '\\') {
out = (char*)in++;
while (*in) {
*out++ = *in++;
if (*in == '\\')
++in;
}
*out = '\0';
}
}
}
if (*ptr == '@') {
/* handle macro part */
size_t macro_len;
line = ++ptr;
while (ptr < endline && *ptr != '(')
++ptr;
if (ptr == endline) {
parse->offset = RC_MISSING_VALUE;
return 0;
}
macro_len = ptr - line;
part = RC_ALLOC(rc_richpresence_display_part_t, parse);
memset(part, 0, sizeof(rc_richpresence_display_part_t));
*next = part;
next = &part->next;
part->display_type = RC_FORMAT_UNKNOWN_MACRO;
/* find the lookup and hook it up */
lookup = first_lookup;
while (lookup) {
if (strncmp(lookup->name, line, macro_len) == 0 && lookup->name[macro_len] == '\0') {
part->text = lookup->name;
part->lookup = lookup;
part->display_type = lookup->format;
break;
}
lookup = lookup->next;
}
if (!lookup) {
static const rc_richpresence_builtin_macro_t builtin_macros[] = {
{"Number", 6, RC_FORMAT_VALUE},
{"Score", 5, RC_FORMAT_SCORE},
{"Centiseconds", 12, RC_FORMAT_CENTISECS},
{"Seconds", 7, RC_FORMAT_SECONDS},
{"Minutes", 7, RC_FORMAT_MINUTES},
{"SecondsAsMinutes", 16, RC_FORMAT_SECONDS_AS_MINUTES},
{"ASCIIChar", 9, RC_FORMAT_ASCIICHAR},
{"UnicodeChar", 11, RC_FORMAT_UNICODECHAR},
{"Float1", 6, RC_FORMAT_FLOAT1},
{"Float2", 6, RC_FORMAT_FLOAT2},
{"Float3", 6, RC_FORMAT_FLOAT3},
{"Float4", 6, RC_FORMAT_FLOAT4},
{"Float5", 6, RC_FORMAT_FLOAT5},
{"Float6", 6, RC_FORMAT_FLOAT6},
};
size_t i;
for (i = 0; i < sizeof(builtin_macros) / sizeof(builtin_macros[0]); ++i) {
if (macro_len == builtin_macros[i].name_len &&
memcmp(builtin_macros[i].name, line, builtin_macros[i].name_len) == 0) {
part->text = builtin_macros[i].name;
part->lookup = NULL;
part->display_type = builtin_macros[i].display_type;
break;
}
}
}
/* find the closing parenthesis */
in = line;
line = ++ptr;
while (ptr < endline && *ptr != ')')
++ptr;
if (*ptr != ')') {
/* non-terminated macro, dump the macro and the remaining portion of the line */
--in; /* already skipped over @ */
part->display_type = RC_FORMAT_STRING;
part->text = rc_alloc_str(parse, in, (int)(ptr - in));
}
else if (part->display_type != RC_FORMAT_UNKNOWN_MACRO) {
part->value = rc_alloc_helper_variable_memref_value(line, (int)(ptr - line), parse);
if (parse->offset < 0)
return 0;
++ptr;
}
else {
/* assert: the allocated string is going to be smaller than the memory used for the parameter of the macro */
++ptr;
part->text = rc_alloc_str(parse, in, (int)(ptr - in));
}
}
line = ptr;
} while (line < endline);
*next = 0;
return self;
}
static int rc_richpresence_lookup_item_count(rc_richpresence_lookup_item_t* item)
{
if (item == NULL)
return 0;
return (rc_richpresence_lookup_item_count(item->left) + rc_richpresence_lookup_item_count(item->right) + 1);
}
static void rc_rebalance_richpresence_lookup_get_items(rc_richpresence_lookup_item_t* root,
rc_richpresence_lookup_item_t** items, int* index)
{
if (root->left != NULL)
rc_rebalance_richpresence_lookup_get_items(root->left, items, index);
items[*index] = root;
++(*index);
if (root->right != NULL)
rc_rebalance_richpresence_lookup_get_items(root->right, items, index);
}
static void rc_rebalance_richpresence_lookup_rebuild(rc_richpresence_lookup_item_t** root,
rc_richpresence_lookup_item_t** items, int first, int last)
{
int mid = (first + last) / 2;
rc_richpresence_lookup_item_t* item = items[mid];
*root = item;
if (mid == first)
item->left = NULL;
else
rc_rebalance_richpresence_lookup_rebuild(&item->left, items, first, mid - 1);
if (mid == last)
item->right = NULL;
else
rc_rebalance_richpresence_lookup_rebuild(&item->right, items, mid + 1, last);
}
static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** root, rc_parse_state_t* parse)
{
rc_richpresence_lookup_item_t** items;
rc_scratch_buffer_t* buffer;
const int alignment = sizeof(rc_richpresence_lookup_item_t*);
int index;
int size;
/* don't bother rebalancing one or two items */
int count = rc_richpresence_lookup_item_count(*root);
if (count < 3)
return;
/* allocate space for the flattened list - prefer scratch memory if available */
size = count * sizeof(rc_richpresence_lookup_item_t*);
buffer = &parse->scratch.buffer;
do {
const int aligned_offset = (buffer->offset + alignment - 1) & ~(alignment - 1);
const int remaining = sizeof(buffer->buffer) - aligned_offset;
if (remaining >= size) {
items = (rc_richpresence_lookup_item_t**)&buffer->buffer[aligned_offset];
break;
}
buffer = buffer->next;
if (buffer == NULL) {
/* could not find large enough block of scratch memory; allocate. if allocation fails,
* we can still use the unbalanced tree, so just bail out */
items = (rc_richpresence_lookup_item_t**)malloc(size);
if (items == NULL)
return;
break;
}
} while (1);
/* flatten the list */
index = 0;
rc_rebalance_richpresence_lookup_get_items(*root, items, &index);
/* and rebuild it as a balanced tree */
rc_rebalance_richpresence_lookup_rebuild(root, items, 0, count - 1);
if (buffer == NULL)
free(items);
}
static void rc_insert_richpresence_lookup_item(rc_richpresence_lookup_t* lookup,
unsigned first, unsigned last, const char* label, int label_len, rc_parse_state_t* parse)
{
rc_richpresence_lookup_item_t** next;
rc_richpresence_lookup_item_t* item;
next = &lookup->root;
while ((item = *next) != NULL) {
if (first > item->last) {
if (first == item->last + 1 &&
strncmp(label, item->label, label_len) == 0 && item->label[label_len] == '\0') {
item->last = last;
return;
}
next = &item->right;
}
else if (last < item->first) {
if (last == item->first - 1 &&
strncmp(label, item->label, label_len) == 0 && item->label[label_len] == '\0') {
item->first = first;
return;
}
next = &item->left;
}
else {
parse->offset = RC_DUPLICATED_VALUE;
return;
}
}
item = RC_ALLOC_SCRATCH(rc_richpresence_lookup_item_t, parse);
item->first = first;
item->last = last;
item->label = rc_alloc_str(parse, label, label_len);
item->left = item->right = NULL;
*next = item;
}
static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup, const char* nextline, rc_parse_state_t* parse)
{
const char* line;
const char* endline;
const char* label;
char* endptr = 0;
unsigned first, last;
int base;
do
{
line = nextline;
nextline = rc_parse_line(line, &endline, parse);
if (endline - line < 2) {
/* ignore full line comments inside a lookup */
if (line[0] == '/' && line[1] == '/')
continue;
/* empty line indicates end of lookup */
if (lookup->root)
rc_rebalance_richpresence_lookup(&lookup->root, parse);
break;
}
/* "*=XXX" specifies default label if lookup does not provide a mapping for the value */
if (line[0] == '*' && line[1] == '=') {
line += 2;
lookup->default_label = rc_alloc_str(parse, line, (int)(endline - line));
continue;
}
label = line;
while (label < endline && *label != '=')
++label;
if (label == endline) {
parse->offset = RC_MISSING_VALUE;
break;
}
++label;
do {
/* get the value for the mapping */
if (line[0] == '0' && line[1] == 'x') {
line += 2;
base = 16;
} else {
base = 10;
}
first = (unsigned)strtoul(line, &endptr, base);
/* check for a range */
if (*endptr != '-') {
/* no range, just set last to first */
last = first;
}
else {
/* range, get last value */
line = endptr + 1;
if (line[0] == '0' && line[1] == 'x') {
line += 2;
base = 16;
} else {
base = 10;
}
last = (unsigned)strtoul(line, &endptr, base);
}
/* ignore spaces after the number - was previously ignored as string was split on equals */
while (*endptr == ' ')
++endptr;
/* if we've found the equal sign, this is the last item */
if (*endptr == '=') {
rc_insert_richpresence_lookup_item(lookup, first, last, label, (int)(endline - label), parse);
break;
}
/* otherwise, if it's not a comma, it's an error */
if (*endptr != ',') {
parse->offset = RC_INVALID_CONST_OPERAND;
break;
}
/* insert the current item and continue scanning the next one */
rc_insert_richpresence_lookup_item(lookup, first, last, label, (int)(endline - label), parse);
line = endptr + 1;
} while (line < endline);
} while (parse->offset > 0);
return nextline;
}
void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, rc_parse_state_t* parse) {
rc_richpresence_display_t** nextdisplay;
rc_richpresence_lookup_t* firstlookup = NULL;
rc_richpresence_lookup_t** nextlookup = &firstlookup;
rc_richpresence_lookup_t* lookup;
rc_trigger_t* trigger;
char format[64];
const char* display = 0;
const char* line;
const char* nextline;
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, parse);
if (strncmp(line, "Lookup:", 7) == 0) {
line += 7;
lookup = RC_ALLOC_SCRATCH(rc_richpresence_lookup_t, parse);
lookup->name = rc_alloc_str(parse, line, (int)(endline - line));
lookup->format = RC_FORMAT_LOOKUP;
lookup->root = NULL;
lookup->default_label = "";
*nextlookup = lookup;
nextlookup = &lookup->next;
nextline = rc_parse_richpresence_lookup(lookup, nextline, parse);
if (parse->offset < 0)
return;
} else if (strncmp(line, "Format:", 7) == 0) {
line += 7;
lookup = RC_ALLOC_SCRATCH(rc_richpresence_lookup_t, parse);
lookup->name = rc_alloc_str(parse, line, (int)(endline - line));
lookup->root = NULL;
lookup->default_label = "";
*nextlookup = lookup;
nextlookup = &lookup->next;
line = nextline;
nextline = rc_parse_line(line, &endline, parse);
if (parse->buffer && strncmp(line, "FormatType=", 11) == 0) {
line += 11;
chars = (int)(endline - line);
if (chars > 63)
chars = 63;
memcpy(format, line, chars);
format[chars] = '\0';
lookup->format = (unsigned short)rc_parse_format(format);
} else {
lookup->format = RC_FORMAT_VALUE;
}
} else if (strncmp(line, "Display:", 8) == 0) {
display = nextline;
display_line = parse->lines_read;
/* scan as long as we find conditional lines or full line comments */
do {
line = nextline;
nextline = rc_parse_line(line, &endline, parse);
} while (*line == '?' || (line[0] == '/' && line[1] == '/'));
}
line = nextline;
}
*nextlookup = 0;
self->first_lookup = firstlookup;
nextdisplay = &self->first_display;
/* 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, parse);
do {
if (line[0] == '?') {
/* conditional display: ?trigger?string */
ptr = ++line;
while (ptr < endline && *ptr != '?')
++ptr;
if (ptr < endline) {
*nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, firstlookup);
if (parse->offset < 0)
return;
trigger = &((*nextdisplay)->trigger);
rc_parse_trigger_internal(trigger, &line, parse);
trigger->memrefs = 0;
if (parse->offset < 0)
return;
if (parse->buffer)
nextdisplay = &((*nextdisplay)->next);
}
}
else if (line[0] != '/' || line[1] != '/') {
break;
}
line = nextline;
nextline = rc_parse_line(line, &endline, parse);
} while (1);
/* non-conditional display: string */
*nextdisplay = rc_parse_richpresence_display_internal(line, endline, parse, firstlookup);
if (*nextdisplay) {
hasdisplay = 1;
nextdisplay = &((*nextdisplay)->next);
/* restore the parser state */
parse->lines_read = lines_read;
}
else {
/* this should only happen if the line is blank.
* expect parse->offset to be RC_MISSING_DISPLAY_STRING and leave parse->lines_read
* on the current line for error tracking. */
}
}
/* finalize */
*nextdisplay = 0;
if (!hasdisplay && parse->offset > 0) {
parse->offset = RC_MISSING_DISPLAY_STRING;
}
}
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;
rc_value_t* variables;
rc_init_parse_state(&parse, 0, 0, 0);
rc_init_parse_state_memrefs(&parse, &first_memref);
rc_init_parse_state_variables(&parse, &variables);
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);
rc_init_parse_state_memrefs(&parse, &self->memrefs);
rc_init_parse_state_variables(&parse, &self->variables);
rc_parse_richpresence_internal(self, script, &parse);
rc_destroy_parse_state(&parse);
return (parse.offset >= 0) ? self : NULL;
}
void rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L) {
rc_richpresence_display_t* display;
rc_update_memref_values(richpresence->memrefs, peek, peek_ud);
rc_update_variables(richpresence->variables, peek, peek_ud, L);
for (display = richpresence->first_display; display; display = display->next) {
if (display->trigger.has_required_hits)
rc_test_trigger(&display->trigger, peek, peek_ud, L);
}
}
static int rc_evaluate_richpresence_display(rc_richpresence_display_part_t* part, char* buffer, unsigned buffersize)
{
rc_richpresence_lookup_item_t* item;
rc_typed_value_t value;
char tmp[256];
char* ptr = buffer;
const char* text;
size_t chars;
*ptr = '\0';
while (part) {
switch (part->display_type) {
case RC_FORMAT_STRING:
text = part->text;
chars = strlen(text);
break;
case RC_FORMAT_LOOKUP:
rc_typed_value_from_memref_value(&value, part->value);
rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED);
text = part->lookup->default_label;
item = part->lookup->root;
while (item) {
if (value.value.u32 > item->last) {
item = item->right;
}
else if (value.value.u32 < item->first) {
item = item->left;
}
else {
text = item->label;
break;
}
}
chars = strlen(text);
break;
case RC_FORMAT_ASCIICHAR:
chars = 0;
text = tmp;
value.type = RC_VALUE_TYPE_UNSIGNED;
do {
value.value.u32 = part->value->value;
if (value.value.u32 == 0) {
/* null terminator - skip over remaining character macros */
while (part->next && part->next->display_type == RC_FORMAT_ASCIICHAR)
part = part->next;
break;
}
if (value.value.u32 < 32 || value.value.u32 >= 127)
value.value.u32 = '?';
tmp[chars++] = (char)value.value.u32;
if (chars == sizeof(tmp) || !part->next || part->next->display_type != RC_FORMAT_ASCIICHAR)
break;
part = part->next;
} while (1);
tmp[chars] = '\0';
break;
case RC_FORMAT_UNICODECHAR:
chars = 0;
text = tmp;
value.type = RC_VALUE_TYPE_UNSIGNED;
do {
value.value.u32 = part->value->value;
if (value.value.u32 == 0) {
/* null terminator - skip over remaining character macros */
while (part->next && part->next->display_type == RC_FORMAT_UNICODECHAR)
part = part->next;
break;
}
if (value.value.u32 < 32 || value.value.u32 > 65535)
value.value.u32 = 0xFFFD; /* unicode replacement char */
if (value.value.u32 < 0x80) {
tmp[chars++] = (char)value.value.u32;
}
else if (value.value.u32 < 0x0800) {
tmp[chars + 1] = (char)(0x80 | (value.value.u32 & 0x3F)); value.value.u32 >>= 6;
tmp[chars] = (char)(0xC0 | (value.value.u32 & 0x1F));
chars += 2;
}
else {
/* surrogate pair not supported, convert to replacement char */
if (value.value.u32 >= 0xD800 && value.value.u32 < 0xE000)
value.value.u32 = 0xFFFD;
tmp[chars + 2] = (char)(0x80 | (value.value.u32 & 0x3F)); value.value.u32 >>= 6;
tmp[chars + 1] = (char)(0x80 | (value.value.u32 & 0x3F)); value.value.u32 >>= 6;
tmp[chars] = (char)(0xE0 | (value.value.u32 & 0x1F));
chars += 3;
}
if (chars >= sizeof(tmp) - 3 || !part->next || part->next->display_type != RC_FORMAT_UNICODECHAR)
break;
part = part->next;
} while (1);
tmp[chars] = '\0';
break;
case RC_FORMAT_UNKNOWN_MACRO:
chars = snprintf(tmp, sizeof(tmp), "[Unknown macro]%s", part->text);
text = tmp;
break;
default:
rc_typed_value_from_memref_value(&value, part->value);
chars = rc_format_typed_value(tmp, sizeof(tmp), &value, part->display_type);
text = tmp;
break;
}
if (chars > 0 && buffersize > 0) {
if ((unsigned)chars >= buffersize) {
/* prevent write past end of buffer */
memcpy(ptr, text, buffersize - 1);
ptr[buffersize - 1] = '\0';
buffersize = 0;
}
else {
memcpy(ptr, text, chars);
ptr[chars] = '\0';
buffersize -= (unsigned)chars;
}
}
ptr += chars;
part = part->next;
}
return (int)(ptr - buffer);
}
int rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, unsigned buffersize, rc_peek_t peek, void* peek_ud, lua_State* L) {
rc_richpresence_display_t* display;
for (display = richpresence->first_display; display; display = display->next) {
/* if we've reached the end of the condition list, process it */
if (!display->next)
return rc_evaluate_richpresence_display(display->display, buffer, buffersize);
/* triggers with required hits will be updated in rc_update_richpresence */
if (!display->trigger.has_required_hits)
rc_test_trigger(&display->trigger, peek, peek_ud, L);
/* if we've found a valid condition, process it */
if (display->trigger.state == RC_TRIGGER_STATE_TRIGGERED)
return rc_evaluate_richpresence_display(display->display, buffer, buffersize);
}
buffer[0] = '\0';
return 0;
}
int rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, unsigned buffersize, rc_peek_t peek, void* peek_ud, lua_State* L) {
rc_update_richpresence(richpresence, peek, peek_ud, L);
return rc_get_richpresence_display_string(richpresence, buffer, buffersize, peek, peek_ud, L);
}
void rc_reset_richpresence(rc_richpresence_t* self) {
rc_richpresence_display_t* display;
rc_value_t* variable;
for (display = self->first_display; display; display = display->next)
rc_reset_trigger(&display->trigger);
for (variable = self->variables; variable; variable = variable->next)
rc_reset_value(variable);
}

@ -0,0 +1,855 @@
#include "rc_runtime.h"
#include "rc_internal.h"
#include "rc_compat.h"
#include "../rhash/md5.h"
#include <stdlib.h>
#include <string.h>
#define RC_RICHPRESENCE_DISPLAY_BUFFER_SIZE 256
void rc_runtime_init(rc_runtime_t* self) {
memset(self, 0, sizeof(rc_runtime_t));
self->next_memref = &self->memrefs;
self->next_variable = &self->variables;
}
void rc_runtime_destroy(rc_runtime_t* self) {
unsigned i;
if (self->triggers) {
for (i = 0; i < self->trigger_count; ++i)
free(self->triggers[i].buffer);
free(self->triggers);
self->triggers = NULL;
self->trigger_count = self->trigger_capacity = 0;
}
if (self->lboards) {
for (i = 0; i < self->lboard_count; ++i)
free(self->lboards[i].buffer);
free(self->lboards);
self->lboards = NULL;
self->lboard_count = self->lboard_capacity = 0;
}
while (self->richpresence) {
rc_runtime_richpresence_t* previous = self->richpresence->previous;
free(self->richpresence->buffer);
free(self->richpresence);
self->richpresence = previous;
}
self->next_memref = 0;
self->memrefs = 0;
}
static void rc_runtime_checksum(const char* memaddr, unsigned char* md5) {
md5_state_t state;
md5_init(&state);
md5_append(&state, (unsigned char*)memaddr, (int)strlen(memaddr));
md5_finish(&state, md5);
}
static char rc_runtime_allocated_memrefs(rc_runtime_t* self) {
char owns_memref = 0;
/* if at least one memref was allocated within the object, we can't free the buffer when the object is deactivated */
if (*self->next_memref != NULL) {
owns_memref = 1;
/* advance through the new memrefs so we're ready for the next allocation */
do {
self->next_memref = &(*self->next_memref)->next;
} while (*self->next_memref != NULL);
}
/* if at least one variable was allocated within the object, we can't free the buffer when the object is deactivated */
if (*self->next_variable != NULL) {
owns_memref = 1;
/* advance through the new variables so we're ready for the next allocation */
do {
self->next_variable = &(*self->next_variable)->next;
} while (*self->next_variable != NULL);
}
return owns_memref;
}
static void rc_runtime_deactivate_trigger_by_index(rc_runtime_t* self, unsigned index) {
if (self->triggers[index].owns_memrefs) {
/* if the trigger has one or more memrefs in its buffer, we can't free the buffer.
* just null out the trigger so the runtime processor will skip it
*/
rc_reset_trigger(self->triggers[index].trigger);
self->triggers[index].trigger = NULL;
}
else {
/* trigger doesn't own any memrefs, go ahead and free it, then replace it with the last trigger */
free(self->triggers[index].buffer);
if (--self->trigger_count > index)
memcpy(&self->triggers[index], &self->triggers[self->trigger_count], sizeof(rc_runtime_trigger_t));
}
}
void rc_runtime_deactivate_achievement(rc_runtime_t* self, unsigned id) {
unsigned i;
for (i = 0; i < self->trigger_count; ++i) {
if (self->triggers[i].id == id && self->triggers[i].trigger != NULL)
rc_runtime_deactivate_trigger_by_index(self, i);
}
}
int rc_runtime_activate_achievement(rc_runtime_t* self, unsigned id, const char* memaddr, lua_State* L, int funcs_idx) {
void* trigger_buffer;
rc_trigger_t* trigger;
rc_runtime_trigger_t* runtime_trigger;
rc_parse_state_t parse;
unsigned char md5[16];
int size;
unsigned i;
if (memaddr == NULL)
return RC_INVALID_MEMORY_OPERAND;
rc_runtime_checksum(memaddr, md5);
/* check to see if the id is already registered with an active trigger */
for (i = 0; i < self->trigger_count; ++i) {
if (self->triggers[i].id == id && self->triggers[i].trigger != NULL) {
if (memcmp(self->triggers[i].md5, md5, 16) == 0) {
/* if the checksum hasn't changed, we can reuse the existing item */
rc_reset_trigger(self->triggers[i].trigger);
return RC_OK;
}
/* checksum has changed, deactivate the the item */
rc_runtime_deactivate_trigger_by_index(self, i);
/* deactivate may reorder the list so we should continue from the current index. however, we
* assume that only one trigger is active per id, so having found that, just stop scanning.
*/
break;
}
}
/* check to see if a disabled trigger for the specific id matches the trigger being registered */
for (i = 0; i < self->trigger_count; ++i) {
if (self->triggers[i].id == id && memcmp(self->triggers[i].md5, md5, 16) == 0) {
/* retrieve the trigger pointer from the buffer */
size = 0;
trigger = (rc_trigger_t*)rc_alloc(self->triggers[i].buffer, &size, sizeof(rc_trigger_t), RC_ALIGNOF(rc_trigger_t), NULL, -1);
self->triggers[i].trigger = trigger;
rc_reset_trigger(trigger);
return RC_OK;
}
}
/* item has not been previously registered, determine how much space we need for it, and allocate it */
size = rc_trigger_size(memaddr);
if (size < 0)
return size;
trigger_buffer = malloc(size);
if (!trigger_buffer)
return RC_OUT_OF_MEMORY;
/* populate the item, using the communal memrefs pool */
rc_init_parse_state(&parse, trigger_buffer, L, funcs_idx);
parse.first_memref = &self->memrefs;
trigger = RC_ALLOC(rc_trigger_t, &parse);
rc_parse_trigger_internal(trigger, &memaddr, &parse);
rc_destroy_parse_state(&parse);
if (parse.offset < 0) {
free(trigger_buffer);
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
return parse.offset;
}
/* grow the trigger buffer if necessary */
if (self->trigger_count == self->trigger_capacity) {
self->trigger_capacity += 32;
if (!self->triggers)
self->triggers = (rc_runtime_trigger_t*)malloc(self->trigger_capacity * sizeof(rc_runtime_trigger_t));
else
self->triggers = (rc_runtime_trigger_t*)realloc(self->triggers, self->trigger_capacity * sizeof(rc_runtime_trigger_t));
if (!self->triggers) {
free(trigger_buffer);
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
return RC_OUT_OF_MEMORY;
}
}
/* assign the new trigger */
runtime_trigger = &self->triggers[self->trigger_count];
runtime_trigger->id = id;
runtime_trigger->trigger = trigger;
runtime_trigger->buffer = trigger_buffer;
runtime_trigger->invalid_memref = NULL;
memcpy(runtime_trigger->md5, md5, 16);
runtime_trigger->serialized_size = 0;
runtime_trigger->owns_memrefs = rc_runtime_allocated_memrefs(self);
++self->trigger_count;
/* reset it, and return it */
trigger->memrefs = NULL;
rc_reset_trigger(trigger);
return RC_OK;
}
rc_trigger_t* rc_runtime_get_achievement(const rc_runtime_t* self, unsigned id)
{
unsigned i;
for (i = 0; i < self->trigger_count; ++i) {
if (self->triggers[i].id == id && self->triggers[i].trigger != NULL)
return self->triggers[i].trigger;
}
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;
}
int rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, unsigned id, char* buffer, size_t buffer_size)
{
const rc_trigger_t* trigger = rc_runtime_get_achievement(runtime, id);
unsigned value;
if (!buffer || !buffer_size)
return 0;
if (!trigger || /* no trigger */
trigger->measured_target == 0 || /* not measured */
!rc_trigger_state_active(trigger->state)) { /* don't report measured value for inactive triggers */
*buffer = '\0';
return 0;
}
/* cap the value at the target so we can count past the target: "107 >= 100" */
value = trigger->measured_value;
if (value > trigger->measured_target)
value = trigger->measured_target;
if (trigger->measured_as_percent) {
unsigned percent = (unsigned)(((unsigned long long)value * 100) / trigger->measured_target);
return snprintf(buffer, buffer_size, "%u%%", percent);
}
return snprintf(buffer, buffer_size, "%u/%u", value, trigger->measured_target);
}
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.
* just null out the lboard so the runtime processor will skip it
*/
rc_reset_lboard(self->lboards[index].lboard);
self->lboards[index].lboard = NULL;
}
else {
/* lboard doesn't own any memrefs, go ahead and free it, then replace it with the last lboard */
free(self->lboards[index].buffer);
if (--self->lboard_count > index)
memcpy(&self->lboards[index], &self->lboards[self->lboard_count], sizeof(rc_runtime_lboard_t));
}
}
void rc_runtime_deactivate_lboard(rc_runtime_t* self, unsigned id) {
unsigned i;
for (i = 0; i < self->lboard_count; ++i) {
if (self->lboards[i].id == id && self->lboards[i].lboard != NULL)
rc_runtime_deactivate_lboard_by_index(self, i);
}
}
int rc_runtime_activate_lboard(rc_runtime_t* self, unsigned id, const char* memaddr, lua_State* L, int funcs_idx) {
void* lboard_buffer;
unsigned char md5[16];
rc_lboard_t* lboard;
rc_parse_state_t parse;
rc_runtime_lboard_t* runtime_lboard;
int size;
unsigned i;
if (memaddr == 0)
return RC_INVALID_MEMORY_OPERAND;
rc_runtime_checksum(memaddr, md5);
/* check to see if the id is already registered with an active lboard */
for (i = 0; i < self->lboard_count; ++i) {
if (self->lboards[i].id == id && self->lboards[i].lboard != NULL) {
if (memcmp(self->lboards[i].md5, md5, 16) == 0) {
/* if the checksum hasn't changed, we can reuse the existing item */
rc_reset_lboard(self->lboards[i].lboard);
return RC_OK;
}
/* checksum has changed, deactivate the the item */
rc_runtime_deactivate_lboard_by_index(self, i);
/* deactivate may reorder the list so we should continue from the current index. however, we
* assume that only one trigger is active per id, so having found that, just stop scanning.
*/
break;
}
}
/* check to see if a disabled lboard for the specific id matches the lboard being registered */
for (i = 0; i < self->lboard_count; ++i) {
if (self->lboards[i].id == id && memcmp(self->lboards[i].md5, md5, 16) == 0) {
/* retrieve the lboard pointer from the buffer */
size = 0;
lboard = (rc_lboard_t*)rc_alloc(self->lboards[i].buffer, &size, sizeof(rc_lboard_t), RC_ALIGNOF(rc_lboard_t), NULL, -1);
self->lboards[i].lboard = lboard;
rc_reset_lboard(lboard);
return RC_OK;
}
}
/* item has not been previously registered, determine how much space we need for it, and allocate it */
size = rc_lboard_size(memaddr);
if (size < 0)
return size;
lboard_buffer = malloc(size);
if (!lboard_buffer)
return RC_OUT_OF_MEMORY;
/* populate the item, using the communal memrefs pool */
rc_init_parse_state(&parse, lboard_buffer, L, funcs_idx);
lboard = RC_ALLOC(rc_lboard_t, &parse);
parse.first_memref = &self->memrefs;
rc_parse_lboard_internal(lboard, memaddr, &parse);
rc_destroy_parse_state(&parse);
if (parse.offset < 0) {
free(lboard_buffer);
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
return parse.offset;
}
/* grow the lboard buffer if necessary */
if (self->lboard_count == self->lboard_capacity) {
self->lboard_capacity += 16;
if (!self->lboards)
self->lboards = (rc_runtime_lboard_t*)malloc(self->lboard_capacity * sizeof(rc_runtime_lboard_t));
else
self->lboards = (rc_runtime_lboard_t*)realloc(self->lboards, self->lboard_capacity * sizeof(rc_runtime_lboard_t));
if (!self->lboards) {
free(lboard_buffer);
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
return RC_OUT_OF_MEMORY;
}
}
/* assign the new lboard */
runtime_lboard = &self->lboards[self->lboard_count++];
runtime_lboard->id = id;
runtime_lboard->value = 0;
runtime_lboard->lboard = lboard;
runtime_lboard->buffer = lboard_buffer;
runtime_lboard->invalid_memref = NULL;
memcpy(runtime_lboard->md5, md5, 16);
runtime_lboard->serialized_size = 0;
runtime_lboard->owns_memrefs = rc_runtime_allocated_memrefs(self);
/* reset it, and return it */
lboard->memrefs = NULL;
rc_reset_lboard(lboard);
return RC_OK;
}
rc_lboard_t* rc_runtime_get_lboard(const rc_runtime_t* self, unsigned id)
{
unsigned i;
for (i = 0; i < self->lboard_count; ++i) {
if (self->lboards[i].id == id && self->lboards[i].lboard != NULL)
return self->lboards[i].lboard;
}
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;
rc_runtime_richpresence_t** previous_ptr;
rc_parse_state_t parse;
unsigned char md5[16];
int size;
if (script == NULL)
return RC_MISSING_DISPLAY_STRING;
rc_runtime_checksum(script, md5);
/* look for existing match */
previous_ptr = NULL;
previous = self->richpresence;
while (previous) {
if (previous && self->richpresence->richpresence && memcmp(self->richpresence->md5, md5, 16) == 0) {
/* unchanged. reset all of the conditions */
rc_reset_richpresence(self->richpresence->richpresence);
/* move to front of linked list*/
if (previous_ptr) {
*previous_ptr = previous->previous;
if (!self->richpresence->owns_memrefs) {
free(self->richpresence->buffer);
previous->previous = self->richpresence->previous;
}
else {
previous->previous = self->richpresence;
}
self->richpresence = previous;
}
/* return success*/
return RC_OK;
}
previous_ptr = &previous->previous;
previous = previous->previous;
}
/* no existing match found, parse script */
size = rc_richpresence_size(script);
if (size < 0)
return size;
/* if the previous script doesn't have any memrefs, free it */
previous = self->richpresence;
if (previous) {
if (!previous->owns_memrefs) {
free(previous->buffer);
previous = previous->previous;
}
}
/* allocate and process the new script */
self->richpresence = (rc_runtime_richpresence_t*)malloc(sizeof(rc_runtime_richpresence_t));
if (!self->richpresence)
return RC_OUT_OF_MEMORY;
self->richpresence->previous = previous;
self->richpresence->owns_memrefs = 0;
memcpy(self->richpresence->md5, md5, sizeof(md5));
self->richpresence->buffer = malloc(size);
if (!self->richpresence->buffer)
return RC_OUT_OF_MEMORY;
rc_init_parse_state(&parse, self->richpresence->buffer, L, funcs_idx);
self->richpresence->richpresence = richpresence = RC_ALLOC(rc_richpresence_t, &parse);
parse.first_memref = &self->memrefs;
parse.variables = &self->variables;
rc_parse_richpresence_internal(richpresence, script, &parse);
rc_destroy_parse_state(&parse);
if (parse.offset < 0) {
free(self->richpresence->buffer);
free(self->richpresence);
self->richpresence = previous;
*self->next_memref = NULL; /* disassociate any memrefs allocated by the failed parse */
return parse.offset;
}
self->richpresence->owns_memrefs = rc_runtime_allocated_memrefs(self);
richpresence->memrefs = NULL;
richpresence->variables = NULL;
if (!richpresence->first_display || !richpresence->first_display->display) {
/* non-existant rich presence */
self->richpresence->richpresence = NULL;
}
else {
/* reset all of the conditions */
rc_reset_richpresence(richpresence);
}
return RC_OK;
}
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);
*buffer = '\0';
return 0;
}
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;
runtime_event.value = 0;
rc_update_memref_values(self->memrefs, peek, ud);
rc_update_variables(self->variables, peek, ud, L);
for (i = self->trigger_count - 1; i >= 0; --i) {
rc_trigger_t* trigger = self->triggers[i].trigger;
int old_state, new_state;
if (!trigger)
continue;
if (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);
runtime_event.value = 0; /* achievement loop expects this to stay at 0 */
continue;
}
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 PRIMED 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;
event_handler(&runtime_event);
break;
case RC_TRIGGER_STATE_PAUSED:
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:
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:
/* 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);
}
break;
}
}
for (i = self->lboard_count - 1; i >= 0; --i) {
rc_lboard_t* lboard = self->lboards[i].lboard;
int lboard_state;
if (!lboard)
continue;
if (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);
continue;
}
lboard_state = lboard->state;
switch (rc_evaluate_lboard(lboard, &runtime_event.value, peek, ud, L))
{
case RC_LBOARD_STATE_STARTED: /* leaderboard is running */
if (lboard_state != RC_LBOARD_STATE_STARTED) {
self->lboards[i].value = runtime_event.value;
runtime_event.type = RC_RUNTIME_EVENT_LBOARD_STARTED;
runtime_event.id = self->lboards[i].id;
event_handler(&runtime_event);
}
else if (runtime_event.value != self->lboards[i].value) {
self->lboards[i].value = runtime_event.value;
runtime_event.type = RC_RUNTIME_EVENT_LBOARD_UPDATED;
runtime_event.id = self->lboards[i].id;
event_handler(&runtime_event);
}
break;
case RC_LBOARD_STATE_CANCELED:
if (lboard_state != RC_LBOARD_STATE_CANCELED) {
runtime_event.type = RC_RUNTIME_EVENT_LBOARD_CANCELED;
runtime_event.id = self->lboards[i].id;
event_handler(&runtime_event);
}
break;
case RC_LBOARD_STATE_TRIGGERED:
if (lboard_state != RC_RUNTIME_EVENT_LBOARD_TRIGGERED) {
runtime_event.type = RC_RUNTIME_EVENT_LBOARD_TRIGGERED;
runtime_event.id = self->lboards[i].id;
event_handler(&runtime_event);
}
break;
}
}
if (self->richpresence && self->richpresence->richpresence)
rc_update_richpresence(self->richpresence->richpresence, peek, ud, L);
}
void rc_runtime_reset(rc_runtime_t* self) {
rc_value_t* variable;
unsigned i;
for (i = 0; i < self->trigger_count; ++i) {
if (self->triggers[i].trigger)
rc_reset_trigger(self->triggers[i].trigger);
}
for (i = 0; i < self->lboard_count; ++i) {
if (self->lboards[i].lboard)
rc_reset_lboard(self->lboards[i].lboard);
}
if (self->richpresence && self->richpresence->richpresence) {
rc_richpresence_display_t* display = self->richpresence->richpresence->first_display;
while (display != 0) {
rc_reset_trigger(&display->trigger);
display = display->next;
}
}
for (variable = self->variables; variable; variable = variable->next)
rc_reset_value(variable);
}
static int rc_condset_contains_memref(const rc_condset_t* condset, const rc_memref_t* memref) {
rc_condition_t* cond;
if (!condset)
return 0;
for (cond = condset->conditions; cond; cond = cond->next) {
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;
}
return 0;
}
static int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref) {
rc_condset_t* condset;
if (!value)
return 0;
for (condset = value->conditions; condset; condset = condset->next) {
if (rc_condset_contains_memref(condset, memref))
return 1;
}
return 0;
}
static int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memref_t* memref) {
rc_condset_t* condset;
if (!trigger)
return 0;
if (rc_condset_contains_memref(trigger->requirement, memref))
return 1;
for (condset = trigger->alternative; condset; condset = condset->next) {
if (rc_condset_contains_memref(condset, memref))
return 1;
}
return 0;
}
static void rc_runtime_invalidate_memref(rc_runtime_t* self, rc_memref_t* memref) {
unsigned i;
/* disable any achievements dependent on the address */
for (i = 0; i < self->trigger_count; ++i) {
if (!self->triggers[i].invalid_memref && rc_trigger_contains_memref(self->triggers[i].trigger, memref))
self->triggers[i].invalid_memref = memref;
}
/* disable any leaderboards dependent on the address */
for (i = 0; i < self->lboard_count; ++i) {
if (!self->lboards[i].invalid_memref) {
rc_lboard_t* lboard = self->lboards[i].lboard;
if (lboard) {
if (rc_trigger_contains_memref(&lboard->start, memref)) {
lboard->start.state = RC_TRIGGER_STATE_DISABLED;
self->lboards[i].invalid_memref = memref;
}
if (rc_trigger_contains_memref(&lboard->cancel, memref)) {
lboard->cancel.state = RC_TRIGGER_STATE_DISABLED;
self->lboards[i].invalid_memref = memref;
}
if (rc_trigger_contains_memref(&lboard->submit, memref)) {
lboard->submit.state = RC_TRIGGER_STATE_DISABLED;
self->lboards[i].invalid_memref = memref;
}
if (rc_value_contains_memref(&lboard->value, memref))
self->lboards[i].invalid_memref = memref;
}
}
}
}
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);
}
}
}
}

@ -0,0 +1,883 @@
#include "rc_runtime.h"
#include "rc_internal.h"
#include "../rhash/md5.h"
#include <stdlib.h>
#include <string.h>
#define RC_RUNTIME_MARKER 0x0A504152 /* RAP\n */
#define RC_RUNTIME_CHUNK_MEMREFS 0x4645524D /* MREF */
#define RC_RUNTIME_CHUNK_VARIABLES 0x53524156 /* VARS */
#define RC_RUNTIME_CHUNK_ACHIEVEMENT 0x56484341 /* ACHV */
#define RC_RUNTIME_CHUNK_LEADERBOARD 0x4452424C /* LBRD */
#define RC_RUNTIME_CHUNK_RICHPRESENCE 0x48434952 /* RICH */
#define RC_RUNTIME_CHUNK_DONE 0x454E4F44 /* DONE */
typedef struct rc_runtime_progress_t {
rc_runtime_t* runtime;
int offset;
unsigned char* buffer;
int chunk_size_offset;
lua_State* L;
} rc_runtime_progress_t;
#define RC_TRIGGER_STATE_UNUPDATED 0x7F
#define RC_MEMREF_FLAG_CHANGED_THIS_FRAME 0x00010000
#define RC_VAR_FLAG_HAS_COND_DATA 0x01000000
#define RC_COND_FLAG_IS_TRUE 0x00000001
#define RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF 0x00010000
#define RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME 0x00020000
#define RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF 0x00100000
#define RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME 0x00200000
static void rc_runtime_progress_write_uint(rc_runtime_progress_t* progress, unsigned value)
{
if (progress->buffer) {
progress->buffer[progress->offset + 0] = value & 0xFF; value >>= 8;
progress->buffer[progress->offset + 1] = value & 0xFF; value >>= 8;
progress->buffer[progress->offset + 2] = value & 0xFF; value >>= 8;
progress->buffer[progress->offset + 3] = value & 0xFF;
}
progress->offset += 4;
}
static unsigned rc_runtime_progress_read_uint(rc_runtime_progress_t* progress)
{
unsigned value = progress->buffer[progress->offset + 0] |
(progress->buffer[progress->offset + 1] << 8) |
(progress->buffer[progress->offset + 2] << 16) |
(progress->buffer[progress->offset + 3] << 24);
progress->offset += 4;
return value;
}
static void rc_runtime_progress_write_md5(rc_runtime_progress_t* progress, unsigned char* md5)
{
if (progress->buffer)
memcpy(&progress->buffer[progress->offset], md5, 16);
progress->offset += 16;
}
static int rc_runtime_progress_match_md5(rc_runtime_progress_t* progress, unsigned char* md5)
{
int result = 0;
if (progress->buffer)
result = (memcmp(&progress->buffer[progress->offset], md5, 16) == 0);
progress->offset += 16;
return result;
}
static unsigned rc_runtime_progress_djb2(const char* input)
{
unsigned result = 5381;
char c;
while ((c = *input++) != '\0')
result = ((result << 5) + result) + c; /* result = result * 33 + c */
return result;
}
static void rc_runtime_progress_start_chunk(rc_runtime_progress_t* progress, unsigned chunk_id)
{
rc_runtime_progress_write_uint(progress, chunk_id);
progress->chunk_size_offset = progress->offset;
progress->offset += 4;
}
static void rc_runtime_progress_end_chunk(rc_runtime_progress_t* progress)
{
unsigned length;
int offset;
progress->offset = (progress->offset + 3) & ~0x03; /* align to 4 byte boundary */
if (progress->buffer) {
/* ignore chunk size field when calculating chunk size */
length = (unsigned)(progress->offset - progress->chunk_size_offset - 4);
/* temporarily update the write pointer to write the chunk size field */
offset = progress->offset;
progress->offset = progress->chunk_size_offset;
rc_runtime_progress_write_uint(progress, length);
progress->offset = offset;
}
}
static void rc_runtime_progress_init(rc_runtime_progress_t* progress, rc_runtime_t* runtime, lua_State* L)
{
memset(progress, 0, sizeof(rc_runtime_progress_t));
progress->runtime = runtime;
progress->L = L;
}
static int rc_runtime_progress_write_memrefs(rc_runtime_progress_t* progress)
{
rc_memref_t* memref = progress->runtime->memrefs;
unsigned int flags = 0;
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_MEMREFS);
if (!progress->buffer) {
while (memref) {
progress->offset += 16;
memref = memref->next;
}
}
else {
while (memref) {
flags = memref->value.size;
if (memref->value.changed)
flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME;
rc_runtime_progress_write_uint(progress, memref->address);
rc_runtime_progress_write_uint(progress, flags);
rc_runtime_progress_write_uint(progress, memref->value.value);
rc_runtime_progress_write_uint(progress, memref->value.prior);
memref = memref->next;
}
}
rc_runtime_progress_end_chunk(progress);
return RC_OK;
}
static int rc_runtime_progress_read_memrefs(rc_runtime_progress_t* progress)
{
unsigned entries;
unsigned address, flags, value, prior;
char size;
rc_memref_t* memref;
rc_memref_t* first_unmatched_memref = progress->runtime->memrefs;
/* re-read the chunk size to determine how many memrefs are present */
progress->offset -= 4;
entries = rc_runtime_progress_read_uint(progress) / 16;
while (entries != 0) {
address = rc_runtime_progress_read_uint(progress);
flags = rc_runtime_progress_read_uint(progress);
value = rc_runtime_progress_read_uint(progress);
prior = rc_runtime_progress_read_uint(progress);
size = flags & 0xFF;
memref = first_unmatched_memref;
while (memref) {
if (memref->address == address && memref->value.size == size) {
memref->value.value = value;
memref->value.changed = (flags & RC_MEMREF_FLAG_CHANGED_THIS_FRAME) ? 1 : 0;
memref->value.prior = prior;
if (memref == first_unmatched_memref)
first_unmatched_memref = memref->next;
break;
}
memref = memref->next;
}
--entries;
}
return RC_OK;
}
static int rc_runtime_progress_is_indirect_memref(rc_operand_t* oper)
{
switch (oper->type)
{
case RC_OPERAND_CONST:
case RC_OPERAND_FP:
case RC_OPERAND_LUA:
return 0;
default:
return oper->value.memref->value.is_indirect;
}
}
static int rc_runtime_progress_write_condset(rc_runtime_progress_t* progress, rc_condset_t* condset)
{
rc_condition_t* cond;
unsigned flags;
rc_runtime_progress_write_uint(progress, condset->is_paused);
cond = condset->conditions;
while (cond) {
flags = 0;
if (cond->is_true)
flags |= RC_COND_FLAG_IS_TRUE;
if (rc_runtime_progress_is_indirect_memref(&cond->operand1)) {
flags |= RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF;
if (cond->operand1.value.memref->value.changed)
flags |= RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME;
}
if (rc_runtime_progress_is_indirect_memref(&cond->operand2)) {
flags |= RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF;
if (cond->operand2.value.memref->value.changed)
flags |= RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME;
}
rc_runtime_progress_write_uint(progress, cond->current_hits);
rc_runtime_progress_write_uint(progress, flags);
if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) {
rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.value);
rc_runtime_progress_write_uint(progress, cond->operand1.value.memref->value.prior);
}
if (flags & RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF) {
rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.value);
rc_runtime_progress_write_uint(progress, cond->operand2.value.memref->value.prior);
}
cond = cond->next;
}
return RC_OK;
}
static int rc_runtime_progress_read_condset(rc_runtime_progress_t* progress, rc_condset_t* condset)
{
rc_condition_t* cond;
unsigned flags;
condset->is_paused = (char)rc_runtime_progress_read_uint(progress);
cond = condset->conditions;
while (cond) {
cond->current_hits = rc_runtime_progress_read_uint(progress);
flags = rc_runtime_progress_read_uint(progress);
cond->is_true = (flags & RC_COND_FLAG_IS_TRUE) ? 1 : 0;
if (flags & RC_COND_FLAG_OPERAND1_IS_INDIRECT_MEMREF) {
if (!rc_operand_is_memref(&cond->operand1)) /* this should never happen, but better safe than sorry */
return RC_INVALID_STATE;
cond->operand1.value.memref->value.value = rc_runtime_progress_read_uint(progress);
cond->operand1.value.memref->value.prior = rc_runtime_progress_read_uint(progress);
cond->operand1.value.memref->value.changed = (flags & RC_COND_FLAG_OPERAND1_MEMREF_CHANGED_THIS_FRAME) ? 1 : 0;
}
if (flags & RC_COND_FLAG_OPERAND2_IS_INDIRECT_MEMREF) {
if (!rc_operand_is_memref(&cond->operand2)) /* this should never happen, but better safe than sorry */
return RC_INVALID_STATE;
cond->operand2.value.memref->value.value = rc_runtime_progress_read_uint(progress);
cond->operand2.value.memref->value.prior = rc_runtime_progress_read_uint(progress);
cond->operand2.value.memref->value.changed = (flags & RC_COND_FLAG_OPERAND2_MEMREF_CHANGED_THIS_FRAME) ? 1 : 0;
}
cond = cond->next;
}
return RC_OK;
}
static unsigned rc_runtime_progress_should_serialize_variable_condset(const rc_condset_t* conditions)
{
const rc_condition_t* condition;
/* predetermined presence of pause flag or indirect memrefs - must serialize */
if (conditions->has_pause || conditions->has_indirect_memrefs)
return RC_VAR_FLAG_HAS_COND_DATA;
/* if any conditions has required hits, must serialize */
/* ASSERT: Measured with comparison and no explicit target will set hit target to 0xFFFFFFFF */
for (condition = conditions->conditions; condition; condition = condition->next) {
if (condition->required_hits > 0)
return RC_VAR_FLAG_HAS_COND_DATA;
}
/* can safely be reset without affecting behavior */
return 0;
}
static int rc_runtime_progress_write_variable(rc_runtime_progress_t* progress, const rc_value_t* variable)
{
unsigned flags;
flags = rc_runtime_progress_should_serialize_variable_condset(variable->conditions);
if (variable->value.changed)
flags |= RC_MEMREF_FLAG_CHANGED_THIS_FRAME;
rc_runtime_progress_write_uint(progress, flags);
rc_runtime_progress_write_uint(progress, variable->value.value);
rc_runtime_progress_write_uint(progress, variable->value.prior);
if (flags & RC_VAR_FLAG_HAS_COND_DATA) {
int result = rc_runtime_progress_write_condset(progress, variable->conditions);
if (result != RC_OK)
return result;
}
return RC_OK;
}
static int rc_runtime_progress_write_variables(rc_runtime_progress_t* progress)
{
unsigned count = 0;
const rc_value_t* variable;
for (variable = progress->runtime->variables; variable; variable = variable->next)
++count;
if (count == 0)
return RC_OK;
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_VARIABLES);
rc_runtime_progress_write_uint(progress, count);
for (variable = progress->runtime->variables; variable; variable = variable->next)
{
unsigned djb2 = rc_runtime_progress_djb2(variable->name);
rc_runtime_progress_write_uint(progress, djb2);
rc_runtime_progress_write_variable(progress, variable);
}
rc_runtime_progress_end_chunk(progress);
return RC_OK;
}
static int rc_runtime_progress_read_variable(rc_runtime_progress_t* progress, rc_value_t* variable)
{
unsigned flags = rc_runtime_progress_read_uint(progress);
variable->value.changed = (flags & RC_MEMREF_FLAG_CHANGED_THIS_FRAME) ? 1 : 0;
variable->value.value = rc_runtime_progress_read_uint(progress);
variable->value.prior = rc_runtime_progress_read_uint(progress);
if (flags & RC_VAR_FLAG_HAS_COND_DATA) {
int result = rc_runtime_progress_read_condset(progress, variable->conditions);
if (result != RC_OK)
return result;
}
else {
rc_reset_condset(variable->conditions);
}
return RC_OK;
}
static int rc_runtime_progress_read_variables(rc_runtime_progress_t* progress)
{
struct rc_pending_value_t
{
rc_value_t* variable;
unsigned djb2;
};
struct rc_pending_value_t local_pending_variables[32];
struct rc_pending_value_t* pending_variables;
rc_value_t* variable;
unsigned count, serialized_count;
int result;
unsigned i;
serialized_count = rc_runtime_progress_read_uint(progress);
if (serialized_count == 0)
return RC_OK;
count = 0;
for (variable = progress->runtime->variables; variable; variable = variable->next)
++count;
if (count == 0)
return RC_OK;
if (count <= sizeof(local_pending_variables) / sizeof(local_pending_variables[0])) {
pending_variables = local_pending_variables;
}
else {
pending_variables = (struct rc_pending_value_t*)malloc(count * sizeof(struct rc_pending_value_t));
if (pending_variables == NULL)
return RC_OUT_OF_MEMORY;
}
count = 0;
for (variable = progress->runtime->variables; variable; variable = variable->next) {
pending_variables[count].variable = variable;
pending_variables[count].djb2 = rc_runtime_progress_djb2(variable->name);
++count;
}
result = RC_OK;
for (; serialized_count > 0 && result == RC_OK; --serialized_count) {
unsigned djb2 = rc_runtime_progress_read_uint(progress);
for (i = 0; i < count; ++i) {
if (pending_variables[i].djb2 == djb2) {
variable = pending_variables[i].variable;
result = rc_runtime_progress_read_variable(progress, variable);
if (result == RC_OK) {
if (i < count - 1)
memcpy(&pending_variables[i], &pending_variables[count - 1], sizeof(struct rc_pending_value_t));
count--;
}
break;
}
}
}
while (count > 0)
rc_reset_value(pending_variables[--count].variable);
if (pending_variables != local_pending_variables)
free(pending_variables);
return result;
}
static int rc_runtime_progress_write_trigger(rc_runtime_progress_t* progress, const rc_trigger_t* trigger)
{
rc_condset_t* condset;
int result;
rc_runtime_progress_write_uint(progress, trigger->state);
rc_runtime_progress_write_uint(progress, trigger->measured_value);
if (trigger->requirement) {
result = rc_runtime_progress_write_condset(progress, trigger->requirement);
if (result != RC_OK)
return result;
}
condset = trigger->alternative;
while (condset) {
result = rc_runtime_progress_write_condset(progress, condset);
if (result != RC_OK)
return result;
condset = condset->next;
}
return RC_OK;
}
static int rc_runtime_progress_read_trigger(rc_runtime_progress_t* progress, rc_trigger_t* trigger)
{
rc_condset_t* condset;
int result;
trigger->state = (char)rc_runtime_progress_read_uint(progress);
trigger->measured_value = rc_runtime_progress_read_uint(progress);
if (trigger->requirement) {
result = rc_runtime_progress_read_condset(progress, trigger->requirement);
if (result != RC_OK)
return result;
}
condset = trigger->alternative;
while (condset) {
result = rc_runtime_progress_read_condset(progress, condset);
if (result != RC_OK)
return result;
condset = condset->next;
}
return RC_OK;
}
static int rc_runtime_progress_write_achievements(rc_runtime_progress_t* progress)
{
unsigned i;
int offset = 0;
int result;
for (i = 0; i < progress->runtime->trigger_count; ++i) {
rc_runtime_trigger_t* runtime_trigger = &progress->runtime->triggers[i];
if (!runtime_trigger->trigger)
continue;
/* 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) {
progress->offset += runtime_trigger->serialized_size;
continue;
}
offset = progress->offset;
}
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_ACHIEVEMENT);
rc_runtime_progress_write_uint(progress, runtime_trigger->id);
rc_runtime_progress_write_md5(progress, runtime_trigger->md5);
result = rc_runtime_progress_write_trigger(progress, runtime_trigger->trigger);
if (result != RC_OK)
return result;
rc_runtime_progress_end_chunk(progress);
if (!progress->buffer)
runtime_trigger->serialized_size = progress->offset - offset;
}
return RC_OK;
}
static int rc_runtime_progress_read_achievement(rc_runtime_progress_t* progress)
{
unsigned id = rc_runtime_progress_read_uint(progress);
unsigned i;
for (i = 0; i < progress->runtime->trigger_count; ++i) {
rc_runtime_trigger_t* runtime_trigger = &progress->runtime->triggers[i];
if (runtime_trigger->id == id && runtime_trigger->trigger != NULL) {
/* ignore triggered and waiting achievements */
if (runtime_trigger->trigger->state == RC_TRIGGER_STATE_UNUPDATED) {
/* only update state if definition hasn't changed (md5 matches) */
if (rc_runtime_progress_match_md5(progress, runtime_trigger->md5))
return rc_runtime_progress_read_trigger(progress, runtime_trigger->trigger);
break;
}
}
}
return RC_OK;
}
static int rc_runtime_progress_write_leaderboards(rc_runtime_progress_t* progress)
{
unsigned i;
unsigned flags;
int offset = 0;
int result;
for (i = 0; i < progress->runtime->lboard_count; ++i) {
rc_runtime_lboard_t* runtime_lboard = &progress->runtime->lboards[i];
if (!runtime_lboard->lboard)
continue;
/* don't store state for inactive leaderboards */
if (!rc_lboard_state_active(runtime_lboard->lboard->state))
continue;
if (!progress->buffer) {
if (runtime_lboard->serialized_size) {
progress->offset += runtime_lboard->serialized_size;
continue;
}
offset = progress->offset;
}
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_LEADERBOARD);
rc_runtime_progress_write_uint(progress, runtime_lboard->id);
rc_runtime_progress_write_md5(progress, runtime_lboard->md5);
flags = runtime_lboard->lboard->state;
rc_runtime_progress_write_uint(progress, flags);
result = rc_runtime_progress_write_trigger(progress, &runtime_lboard->lboard->start);
if (result != RC_OK)
return result;
result = rc_runtime_progress_write_trigger(progress, &runtime_lboard->lboard->submit);
if (result != RC_OK)
return result;
result = rc_runtime_progress_write_trigger(progress, &runtime_lboard->lboard->cancel);
if (result != RC_OK)
return result;
result = rc_runtime_progress_write_variable(progress, &runtime_lboard->lboard->value);
if (result != RC_OK)
return result;
rc_runtime_progress_end_chunk(progress);
if (!progress->buffer)
runtime_lboard->serialized_size = progress->offset - offset;
}
return RC_OK;
}
static int rc_runtime_progress_read_leaderboard(rc_runtime_progress_t* progress)
{
unsigned id = rc_runtime_progress_read_uint(progress);
unsigned i;
int result;
for (i = 0; i < progress->runtime->lboard_count; ++i) {
rc_runtime_lboard_t* runtime_lboard = &progress->runtime->lboards[i];
if (runtime_lboard->id == id && runtime_lboard->lboard != NULL) {
/* ignore triggered and waiting achievements */
if (runtime_lboard->lboard->state == RC_TRIGGER_STATE_UNUPDATED) {
/* only update state if definition hasn't changed (md5 matches) */
if (rc_runtime_progress_match_md5(progress, runtime_lboard->md5)) {
unsigned flags = rc_runtime_progress_read_uint(progress);
result = rc_runtime_progress_read_trigger(progress, &runtime_lboard->lboard->start);
if (result != RC_OK)
return result;
result = rc_runtime_progress_read_trigger(progress, &runtime_lboard->lboard->submit);
if (result != RC_OK)
return result;
result = rc_runtime_progress_read_trigger(progress, &runtime_lboard->lboard->cancel);
if (result != RC_OK)
return result;
result = rc_runtime_progress_read_variable(progress, &runtime_lboard->lboard->value);
if (result != RC_OK)
return result;
runtime_lboard->lboard->state = (char)(flags & 0x7F);
}
break;
}
}
}
return RC_OK;
}
static int rc_runtime_progress_write_rich_presence(rc_runtime_progress_t* progress)
{
const rc_richpresence_display_t* display;
int result;
if (!progress->runtime->richpresence || !progress->runtime->richpresence->richpresence)
return RC_OK;
/* if there are no conditional display strings, there's nothing to capture */
display = progress->runtime->richpresence->richpresence->first_display;
if (!display->next)
return RC_OK;
rc_runtime_progress_start_chunk(progress, RC_RUNTIME_CHUNK_RICHPRESENCE);
rc_runtime_progress_write_md5(progress, progress->runtime->richpresence->md5);
for (; display->next; display = display->next) {
result = rc_runtime_progress_write_trigger(progress, &display->trigger);
if (result != RC_OK)
return result;
}
rc_runtime_progress_end_chunk(progress);
return RC_OK;
}
static int rc_runtime_progress_read_rich_presence(rc_runtime_progress_t* progress)
{
rc_richpresence_display_t* display;
int result;
if (!progress->runtime->richpresence || !progress->runtime->richpresence->richpresence)
return RC_OK;
if (!rc_runtime_progress_match_md5(progress, progress->runtime->richpresence->md5)) {
rc_reset_richpresence(progress->runtime->richpresence->richpresence);
return RC_OK;
}
display = progress->runtime->richpresence->richpresence->first_display;
for (; display->next; display = display->next) {
result = rc_runtime_progress_read_trigger(progress, &display->trigger);
if (result != RC_OK)
return result;
}
return RC_OK;
}
static int rc_runtime_progress_serialize_internal(rc_runtime_progress_t* progress)
{
md5_state_t state;
unsigned char md5[16];
int result;
rc_runtime_progress_write_uint(progress, RC_RUNTIME_MARKER);
if ((result = rc_runtime_progress_write_memrefs(progress)) != RC_OK)
return result;
if ((result = rc_runtime_progress_write_variables(progress)) != RC_OK)
return result;
if ((result = rc_runtime_progress_write_achievements(progress)) != RC_OK)
return result;
if ((result = rc_runtime_progress_write_leaderboards(progress)) != RC_OK)
return result;
if ((result = rc_runtime_progress_write_rich_presence(progress)) != RC_OK)
return result;
rc_runtime_progress_write_uint(progress, RC_RUNTIME_CHUNK_DONE);
rc_runtime_progress_write_uint(progress, 16);
if (progress->buffer) {
md5_init(&state);
md5_append(&state, progress->buffer, progress->offset);
md5_finish(&state, md5);
}
rc_runtime_progress_write_md5(progress, md5);
return RC_OK;
}
int rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L)
{
rc_runtime_progress_t progress;
int result;
rc_runtime_progress_init(&progress, (rc_runtime_t*)runtime, L);
result = rc_runtime_progress_serialize_internal(&progress);
if (result != RC_OK)
return result;
return progress.offset;
}
int rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L)
{
rc_runtime_progress_t progress;
rc_runtime_progress_init(&progress, (rc_runtime_t*)runtime, L);
progress.buffer = (unsigned char*)buffer;
return rc_runtime_progress_serialize_internal(&progress);
}
int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const unsigned char* serialized, lua_State* L)
{
rc_runtime_progress_t progress;
md5_state_t state;
unsigned char md5[16];
unsigned chunk_id;
unsigned chunk_size;
unsigned next_chunk_offset;
unsigned i;
int seen_rich_presence = 0;
int result = RC_OK;
rc_runtime_progress_init(&progress, runtime, L);
progress.buffer = (unsigned char*)serialized;
if (rc_runtime_progress_read_uint(&progress) != RC_RUNTIME_MARKER) {
rc_runtime_reset(runtime);
return RC_INVALID_STATE;
}
for (i = 0; i < runtime->trigger_count; ++i) {
rc_runtime_trigger_t* runtime_trigger = &runtime->triggers[i];
if (runtime_trigger->trigger) {
/* don't update state for inactive or triggered achievements */
if (rc_trigger_state_active(runtime_trigger->trigger->state)) {
/* 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;
}
}
}
for (i = 0; i < runtime->lboard_count; ++i) {
rc_runtime_lboard_t* runtime_lboard = &runtime->lboards[i];
if (runtime_lboard->lboard) {
/* don't update state for inactive or triggered achievements */
if (rc_lboard_state_active(runtime_lboard->lboard->state)) {
/* mark active achievements as unupdated. anything that's still unupdated
* after deserializing the progress will be reset to waiting */
runtime_lboard->lboard->state = RC_TRIGGER_STATE_UNUPDATED;
}
}
}
do {
chunk_id = rc_runtime_progress_read_uint(&progress);
chunk_size = rc_runtime_progress_read_uint(&progress);
next_chunk_offset = progress.offset + chunk_size;
switch (chunk_id)
{
case RC_RUNTIME_CHUNK_MEMREFS:
result = rc_runtime_progress_read_memrefs(&progress);
break;
case RC_RUNTIME_CHUNK_VARIABLES:
result = rc_runtime_progress_read_variables(&progress);
break;
case RC_RUNTIME_CHUNK_ACHIEVEMENT:
result = rc_runtime_progress_read_achievement(&progress);
break;
case RC_RUNTIME_CHUNK_LEADERBOARD:
result = rc_runtime_progress_read_leaderboard(&progress);
break;
case RC_RUNTIME_CHUNK_RICHPRESENCE:
seen_rich_presence = 1;
result = rc_runtime_progress_read_rich_presence(&progress);
break;
case RC_RUNTIME_CHUNK_DONE:
md5_init(&state);
md5_append(&state, progress.buffer, progress.offset);
md5_finish(&state, md5);
if (!rc_runtime_progress_match_md5(&progress, md5))
result = RC_INVALID_STATE;
break;
default:
if (chunk_size & 0xFFFF0000)
result = RC_INVALID_STATE; /* assume unknown chunk > 64KB is invalid */
break;
}
progress.offset = next_chunk_offset;
} while (result == RC_OK && chunk_id != RC_RUNTIME_CHUNK_DONE);
if (result != RC_OK) {
rc_runtime_reset(runtime);
}
else {
for (i = 0; i < runtime->trigger_count; ++i) {
rc_trigger_t* trigger = runtime->triggers[i].trigger;
if (trigger && trigger->state == RC_TRIGGER_STATE_UNUPDATED)
rc_reset_trigger(trigger);
}
for (i = 0; i < runtime->lboard_count; ++i) {
rc_lboard_t* lboard = runtime->lboards[i].lboard;
if (lboard && lboard->state == RC_TRIGGER_STATE_UNUPDATED)
rc_reset_lboard(lboard);
}
if (!seen_rich_presence && runtime->richpresence && runtime->richpresence->richpresence)
rc_reset_richpresence(runtime->richpresence->richpresence);
}
return result;
}

@ -0,0 +1,289 @@
#include "rc_internal.h"
#include <stddef.h>
#include <string.h> /* memset */
void rc_parse_trigger_internal(rc_trigger_t* self, const char** memaddr, rc_parse_state_t* parse) {
rc_condset_t** next;
const char* aux;
aux = *memaddr;
next = &self->alternative;
/* reset in case multiple triggers are parsed by the same parse_state */
parse->measured_target = 0;
parse->has_required_hits = 0;
parse->measured_as_percent = 0;
if (*aux == 's' || *aux == 'S') {
self->requirement = 0;
}
else {
self->requirement = rc_parse_condset(&aux, parse, 0);
if (parse->offset < 0) {
return;
}
self->requirement->next = 0;
}
while (*aux == 's' || *aux == 'S') {
aux++;
*next = rc_parse_condset(&aux, parse, 0);
if (parse->offset < 0) {
return;
}
next = &(*next)->next;
}
*next = 0;
*memaddr = aux;
self->measured_value = 0;
self->measured_target = parse->measured_target;
self->measured_as_percent = parse->measured_as_percent;
self->state = RC_TRIGGER_STATE_WAITING;
self->has_hits = 0;
self->has_required_hits = parse->has_required_hits;
}
int rc_trigger_size(const char* memaddr) {
rc_trigger_t* self;
rc_parse_state_t parse;
rc_memref_t* memrefs;
rc_init_parse_state(&parse, 0, 0, 0);
rc_init_parse_state_memrefs(&parse, &memrefs);
self = RC_ALLOC(rc_trigger_t, &parse);
rc_parse_trigger_internal(self, &memaddr, &parse);
rc_destroy_parse_state(&parse);
return parse.offset;
}
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);
rc_init_parse_state_memrefs(&parse, &self->memrefs);
rc_parse_trigger_internal(self, &memaddr, &parse);
rc_destroy_parse_state(&parse);
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 int rc_condset_is_measured_from_hitcount(const rc_condset_t* condset, unsigned measured_value)
{
const rc_condition_t* condition;
for (condition = condset->conditions; condition; condition = condition->next) {
if (condition->type == RC_CONDITION_MEASURED && condition->required_hits &&
condition->current_hits == measured_value) {
return 1;
}
}
return 0;
}
static void rc_reset_trigger_hitcounts(rc_trigger_t* self) {
rc_condset_t* condset;
if (self->requirement) {
rc_reset_condset(self->requirement);
}
condset = self->alternative;
while (condset) {
rc_reset_condset(condset);
condset = condset->next;
}
}
int rc_evaluate_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* L) {
rc_eval_state_t eval_state;
rc_condset_t* condset;
int ret;
char is_paused;
char is_primed;
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;
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);
/* process the trigger */
memset(&eval_state, 0, sizeof(eval_state));
eval_state.peek = peek;
eval_state.peek_userdata = ud;
eval_state.L = L;
if (self->requirement != NULL) {
ret = rc_test_condset(self->requirement, &eval_state);
is_paused = self->requirement->is_paused;
is_primed = eval_state.primed;
} else {
ret = 1;
is_paused = 0;
is_primed = 1;
}
condset = self->alternative;
if (condset) {
int sub = 0;
char sub_paused = 1;
char sub_primed = 0;
do {
sub |= rc_test_condset(condset, &eval_state);
sub_paused &= condset->is_paused;
sub_primed |= eval_state.primed;
condset = condset->next;
} while (condset);
/* to trigger, the core must be true and at least one alt must be true */
ret &= sub;
is_primed &= sub_primed;
/* if the core is not paused, all alts must be paused to count as a paused trigger */
is_paused |= sub_paused;
}
/* if paused, the measured value may not be captured, keep the old value */
if (!is_paused) {
rc_typed_value_convert(&eval_state.measured_value, RC_VALUE_TYPE_UNSIGNED);
self->measured_value = eval_state.measured_value.value.u32;
}
/* if the state is WAITING and the trigger is ready to fire, ignore it and reset the hit counts */
/* otherwise, if the state is WAITING, proceed to activating the trigger */
if (self->state == RC_TRIGGER_STATE_WAITING && ret) {
rc_reset_trigger(self);
self->has_hits = 0;
return RC_TRIGGER_STATE_WAITING;
}
/* if any ResetIf condition was true, reset the hit counts */
if (eval_state.was_reset) {
/* if the measured value came from a hit count, reset it. do this before calling
* rc_reset_trigger_hitcounts in case we need to call rc_condset_is_measured_from_hitcount */
if (eval_state.measured_from_hits) {
self->measured_value = 0;
}
else if (is_paused && self->measured_value) {
/* if the measured value is in a paused group, measured_from_hits won't have been set.
* attempt to determine if it should have been */
if (self->requirement && self->requirement->is_paused &&
rc_condset_is_measured_from_hitcount(self->requirement, self->measured_value)) {
self->measured_value = 0;
}
else {
for (condset = self->alternative; condset; condset = condset->next) {
if (condset->is_paused && rc_condset_is_measured_from_hitcount(condset, self->measured_value)) {
self->measured_value = 0;
break;
}
}
}
}
rc_reset_trigger_hitcounts(self);
/* 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;
}
/* any hits that were tallied were just reset */
eval_state.has_hits = 0;
is_primed = 0;
}
else if (ret) {
/* trigger was triggered */
self->state = RC_TRIGGER_STATE_TRIGGERED;
return RC_TRIGGER_STATE_TRIGGERED;
}
/* did not trigger this frame - update the information we'll need for next time */
self->has_hits = eval_state.has_hits;
if (is_paused) {
self->state = RC_TRIGGER_STATE_PAUSED;
}
else if (is_primed) {
self->state = RC_TRIGGER_STATE_PRIMED;
}
else {
self->state = RC_TRIGGER_STATE_ACTIVE;
}
/* if an individual condition was reset, notify the caller */
if (eval_state.was_cond_reset)
return RC_TRIGGER_STATE_RESET;
/* otherwise, just return the current state */
return self->state;
}
int rc_test_trigger(rc_trigger_t* self, rc_peek_t peek, void* ud, lua_State* L) {
/* for backwards compatibilty, rc_test_trigger always assumes the achievement is active */
self->state = RC_TRIGGER_STATE_ACTIVE;
return (rc_evaluate_trigger(self, peek, ud, L) == RC_TRIGGER_STATE_TRIGGERED);
}
void rc_reset_trigger(rc_trigger_t* self) {
rc_reset_trigger_hitcounts(self);
self->state = RC_TRIGGER_STATE_WAITING;
self->measured_value = 0;
self->has_hits = 0;
}

@ -0,0 +1,685 @@
#include "rc_internal.h"
#include <string.h> /* memset */
#include <ctype.h> /* isdigit */
#include <float.h> /* FLT_EPSILON */
static void rc_parse_cond_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
rc_condset_t** next_clause;
next_clause = &self->conditions;
do
{
parse->measured_target = 0; /* passing is_value=1 should prevent any conflicts, but clear it out anyway */
*next_clause = rc_parse_condset(memaddr, parse, 1);
if (parse->offset < 0) {
return;
}
if (**memaddr == 'S' || **memaddr == 's') {
/* alt groups not supported */
parse->offset = RC_INVALID_VALUE_FLAG;
}
else if (parse->measured_target == 0) {
parse->offset = RC_MISSING_VALUE_MEASURED;
}
else if (**memaddr == '$') {
/* maximum of */
++(*memaddr);
next_clause = &(*next_clause)->next;
continue;
}
break;
} while (1);
(*next_clause)->next = 0;
}
void rc_parse_legacy_value(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
rc_condition_t** next;
rc_condset_t** next_clause;
rc_condition_t* cond;
char buffer[64] = "A:";
const char* buffer_ptr;
char* ptr;
/* convert legacy format into condset */
self->conditions = RC_ALLOC(rc_condset_t, parse);
memset(self->conditions, 0, sizeof(rc_condset_t));
next = &self->conditions->conditions;
next_clause = &self->conditions->next;
for (;; ++(*memaddr)) {
buffer[0] = 'A'; /* reset to AddSource */
ptr = &buffer[2];
/* extract the next clause */
for (;; ++(*memaddr)) {
switch (**memaddr) {
case '_': /* add next */
case '$': /* maximum of */
case '\0': /* end of string */
case ':': /* end of leaderboard clause */
case ')': /* end of rich presence macro */
*ptr = '\0';
break;
case '*':
*ptr++ = '*';
buffer_ptr = *memaddr + 1;
if (*buffer_ptr == '-') {
buffer[0] = 'B'; /* change to SubSource */
++(*memaddr); /* don't copy sign */
++buffer_ptr; /* ignore sign when doing floating point check */
}
else if (*buffer_ptr == '+') {
++buffer_ptr; /* ignore sign when doing floating point check */
}
/* if it looks like a floating point number, add the 'f' prefix */
while (isdigit((unsigned char)*buffer_ptr))
++buffer_ptr;
if (*buffer_ptr == '.')
*ptr++ = 'f';
continue;
default:
*ptr++ = **memaddr;
continue;
}
break;
}
/* process the clause */
buffer_ptr = buffer;
cond = rc_parse_condition(&buffer_ptr, parse, 0);
if (parse->offset < 0)
return;
if (*buffer_ptr) {
/* whatever we copied as a single condition was not fully consumed */
parse->offset = RC_INVALID_COMPARISON;
return;
}
switch (cond->oper) {
case RC_OPERATOR_MULT:
case RC_OPERATOR_DIV:
case RC_OPERATOR_AND:
case RC_OPERATOR_XOR:
case RC_OPERATOR_NONE:
break;
default:
parse->offset = RC_INVALID_OPERATOR;
return;
}
*next = cond;
if (**memaddr == '_') {
/* add next */
next = &cond->next;
continue;
}
if (cond->type == RC_CONDITION_SUB_SOURCE) {
/* cannot change SubSource to Measured. add a dummy condition */
next = &cond->next;
buffer_ptr = "A:0";
cond = rc_parse_condition(&buffer_ptr, parse, 0);
*next = cond;
}
/* convert final AddSource condition to Measured */
cond->type = RC_CONDITION_MEASURED;
cond->next = 0;
if (**memaddr != '$') {
/* end of valid string */
*next_clause = 0;
break;
}
/* max of ($), start a new clause */
*next_clause = RC_ALLOC(rc_condset_t, parse);
if (parse->buffer) /* don't clear in sizing mode or pointer will break */
memset(*next_clause, 0, sizeof(rc_condset_t));
next = &(*next_clause)->conditions;
next_clause = &(*next_clause)->next;
}
}
void rc_parse_value_internal(rc_value_t* self, const char** memaddr, rc_parse_state_t* parse) {
/* if it starts with a condition flag (M: A: B: C:), parse the conditions */
if ((*memaddr)[1] == ':') {
rc_parse_cond_value(self, memaddr, parse);
}
else {
rc_parse_legacy_value(self, memaddr, parse);
}
self->name = "(unnamed)";
self->value.value = self->value.prior = 0;
self->value.changed = 0;
self->next = 0;
}
int rc_value_size(const char* memaddr) {
rc_value_t* self;
rc_parse_state_t parse;
rc_memref_t* first_memref;
rc_init_parse_state(&parse, 0, 0, 0);
rc_init_parse_state_memrefs(&parse, &first_memref);
self = RC_ALLOC(rc_value_t, &parse);
rc_parse_value_internal(self, &memaddr, &parse);
rc_destroy_parse_state(&parse);
return parse.offset;
}
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);
rc_init_parse_state_memrefs(&parse, &self->memrefs);
rc_parse_value_internal(self, &memaddr, &parse);
rc_destroy_parse_state(&parse);
return (parse.offset >= 0) ? self : NULL;
}
int rc_evaluate_value_typed(rc_value_t* self, rc_typed_value_t* value, rc_peek_t peek, void* ud, lua_State* L) {
rc_eval_state_t eval_state;
rc_condset_t* condset;
int valid = 0;
rc_update_memref_values(self->memrefs, peek, ud);
value->value.i32 = 0;
value->type = RC_VALUE_TYPE_SIGNED;
for (condset = self->conditions; condset != NULL; condset = condset->next) {
memset(&eval_state, 0, sizeof(eval_state));
eval_state.peek = peek;
eval_state.peek_userdata = ud;
eval_state.L = L;
rc_test_condset(condset, &eval_state);
if (condset->is_paused)
continue;
if (eval_state.was_reset) {
/* if any ResetIf condition was true, reset the hit counts
* NOTE: ResetIf only affects the current condset when used in values!
*/
rc_reset_condset(condset);
/* if the measured value came from a hit count, reset it too */
if (eval_state.measured_from_hits) {
eval_state.measured_value.value.u32 = 0;
eval_state.measured_value.type = RC_VALUE_TYPE_UNSIGNED;
}
}
if (!valid) {
/* capture the first valid measurement */
memcpy(value, &eval_state.measured_value, sizeof(*value));
valid = 1;
}
else {
/* multiple condsets are currently only used for the MAX_OF operation.
* only keep the condset's value if it's higher than the current highest value.
*/
if (rc_typed_value_compare(&eval_state.measured_value, value, RC_OPERATOR_GT))
memcpy(value, &eval_state.measured_value, sizeof(*value));
}
}
return valid;
}
int rc_evaluate_value(rc_value_t* self, rc_peek_t peek, void* ud, lua_State* L) {
rc_typed_value_t result;
int valid = rc_evaluate_value_typed(self, &result, peek, ud, L);
if (valid) {
/* if not paused, store the value so that it's available when paused. */
rc_typed_value_convert(&result, RC_VALUE_TYPE_UNSIGNED);
rc_update_memref_value(&self->value, result.value.u32);
}
else {
/* when paused, the Measured value will not be captured, use the last captured value. */
result.value.u32 = self->value.value;
result.type = RC_VALUE_TYPE_UNSIGNED;
}
rc_typed_value_convert(&result, RC_VALUE_TYPE_SIGNED);
return result.value.i32;
}
void rc_reset_value(rc_value_t* self) {
rc_condset_t* condset = self->conditions;
while (condset != NULL) {
rc_reset_condset(condset);
condset = condset->next;
}
self->value.value = self->value.prior = 0;
self->value.changed = 0;
}
void rc_init_parse_state_variables(rc_parse_state_t* parse, rc_value_t** variables) {
parse->variables = variables;
*variables = 0;
}
rc_value_t* rc_alloc_helper_variable(const char* memaddr, int memaddr_len, rc_parse_state_t* parse)
{
rc_value_t** variables = parse->variables;
rc_value_t* value;
const char* name;
unsigned measured_target;
while ((value = *variables) != NULL) {
if (strncmp(value->name, memaddr, memaddr_len) == 0 && value->name[memaddr_len] == 0)
return value;
variables = &value->next;
}
value = RC_ALLOC_SCRATCH(rc_value_t, parse);
memset(&value->value, 0, sizeof(value->value));
value->value.size = RC_MEMSIZE_VARIABLE;
value->memrefs = NULL;
/* capture name before calling parse as parse will update memaddr pointer */
name = rc_alloc_str(parse, memaddr, memaddr_len);
if (!name)
return NULL;
/* the helper variable likely has a Measured condition. capture the current measured_target so we can restore it
* after generating the variable so the variable's Measured target doesn't conflict with the rest of the trigger. */
measured_target = parse->measured_target;
/* disable variable resolution when defining a variable to prevent infinite recursion */
variables = parse->variables;
parse->variables = NULL;
rc_parse_value_internal(value, &memaddr, parse);
parse->variables = variables;
/* restore the measured target */
parse->measured_target = measured_target;
/* store name after calling parse as parse will set name to (unnamed) */
value->name = name;
/* append the new variable to the end of the list (have to re-evaluate in case any others were added) */
while (*variables != NULL)
variables = &(*variables)->next;
*variables = value;
return value;
}
void rc_update_variables(rc_value_t* variable, rc_peek_t peek, void* ud, lua_State* L) {
rc_typed_value_t result;
while (variable) {
if (rc_evaluate_value_typed(variable, &result, peek, ud, L)) {
/* store the raw bytes and type to be restored by rc_typed_value_from_memref_value */
rc_update_memref_value(&variable->value, result.value.u32);
variable->value.type = result.type;
}
variable = variable->next;
}
}
void rc_typed_value_from_memref_value(rc_typed_value_t* value, const rc_memref_value_t* memref) {
value->value.u32 = memref->value;
if (memref->size == RC_MEMSIZE_VARIABLE) {
/* a variable can be any of the supported types, but the raw data was copied into u32 */
value->type = memref->type;
}
else {
/* not a variable, only u32 is supported */
value->type = RC_VALUE_TYPE_UNSIGNED;
}
}
void rc_typed_value_convert(rc_typed_value_t* value, char new_type) {
switch (new_type) {
case RC_VALUE_TYPE_UNSIGNED:
switch (value->type) {
case RC_VALUE_TYPE_UNSIGNED:
return;
case RC_VALUE_TYPE_SIGNED:
value->value.u32 = (unsigned)value->value.i32;
break;
case RC_VALUE_TYPE_FLOAT:
value->value.u32 = (unsigned)value->value.f32;
break;
default:
value->value.u32 = 0;
break;
}
break;
case RC_VALUE_TYPE_SIGNED:
switch (value->type) {
case RC_VALUE_TYPE_SIGNED:
return;
case RC_VALUE_TYPE_UNSIGNED:
value->value.i32 = (int)value->value.u32;
break;
case RC_VALUE_TYPE_FLOAT:
value->value.i32 = (int)value->value.f32;
break;
default:
value->value.i32 = 0;
break;
}
break;
case RC_VALUE_TYPE_FLOAT:
switch (value->type) {
case RC_VALUE_TYPE_FLOAT:
return;
case RC_VALUE_TYPE_UNSIGNED:
value->value.f32 = (float)value->value.u32;
break;
case RC_VALUE_TYPE_SIGNED:
value->value.f32 = (float)value->value.i32;
break;
default:
value->value.f32 = 0.0;
break;
}
break;
default:
break;
}
value->type = new_type;
}
static rc_typed_value_t* rc_typed_value_convert_into(rc_typed_value_t* dest, const rc_typed_value_t* source, char new_type) {
memcpy(dest, source, sizeof(rc_typed_value_t));
rc_typed_value_convert(dest, new_type);
return dest;
}
void rc_typed_value_add(rc_typed_value_t* value, const rc_typed_value_t* amount) {
rc_typed_value_t converted;
if (amount->type != value->type && value->type != RC_VALUE_TYPE_NONE)
amount = rc_typed_value_convert_into(&converted, amount, value->type);
switch (value->type)
{
case RC_VALUE_TYPE_UNSIGNED:
value->value.u32 += amount->value.u32;
break;
case RC_VALUE_TYPE_SIGNED:
value->value.i32 += amount->value.i32;
break;
case RC_VALUE_TYPE_FLOAT:
value->value.f32 += amount->value.f32;
break;
case RC_VALUE_TYPE_NONE:
memcpy(value, amount, sizeof(rc_typed_value_t));
break;
default:
break;
}
}
void rc_typed_value_multiply(rc_typed_value_t* value, const rc_typed_value_t* amount) {
rc_typed_value_t converted;
switch (value->type)
{
case RC_VALUE_TYPE_UNSIGNED:
switch (amount->type)
{
case RC_VALUE_TYPE_UNSIGNED:
/* 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->value.u32 *= amount->value.u32;
break;
case RC_VALUE_TYPE_SIGNED:
value->value.u32 *= (unsigned)amount->value.i32;
break;
case RC_VALUE_TYPE_FLOAT:
rc_typed_value_convert(value, RC_VALUE_TYPE_FLOAT);
value->value.f32 *= amount->value.f32;
break;
default:
value->type = RC_VALUE_TYPE_NONE;
break;
}
break;
case RC_VALUE_TYPE_SIGNED:
switch (amount->type)
{
case RC_VALUE_TYPE_SIGNED:
value->value.i32 *= amount->value.i32;
break;
case RC_VALUE_TYPE_UNSIGNED:
value->value.i32 *= (int)amount->value.u32;
break;
case RC_VALUE_TYPE_FLOAT:
rc_typed_value_convert(value, RC_VALUE_TYPE_FLOAT);
value->value.f32 *= amount->value.f32;
break;
default:
value->type = RC_VALUE_TYPE_NONE;
break;
}
break;
case RC_VALUE_TYPE_FLOAT:
if (amount->type == RC_VALUE_TYPE_NONE) {
value->type = RC_VALUE_TYPE_NONE;
}
else {
amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT);
value->value.f32 *= amount->value.f32;
}
break;
default:
value->type = RC_VALUE_TYPE_NONE;
break;
}
}
void rc_typed_value_divide(rc_typed_value_t* value, const rc_typed_value_t* amount) {
rc_typed_value_t converted;
switch (amount->type)
{
case RC_VALUE_TYPE_UNSIGNED:
if (amount->value.u32 == 0) { /* divide by zero */
value->type = RC_VALUE_TYPE_NONE;
return;
}
switch (value->type) {
case RC_VALUE_TYPE_UNSIGNED: /* integer math */
value->value.u32 /= amount->value.u32;
return;
case RC_VALUE_TYPE_SIGNED: /* integer math */
value->value.i32 /= (int)amount->value.u32;
return;
case RC_VALUE_TYPE_FLOAT:
amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT);
break;
default:
value->type = RC_VALUE_TYPE_NONE;
return;
}
break;
case RC_VALUE_TYPE_SIGNED:
if (amount->value.i32 == 0) { /* divide by zero */
value->type = RC_VALUE_TYPE_NONE;
return;
}
switch (value->type) {
case RC_VALUE_TYPE_SIGNED: /* integer math */
value->value.i32 /= amount->value.i32;
return;
case RC_VALUE_TYPE_UNSIGNED: /* integer math */
value->value.u32 /= (unsigned)amount->value.i32;
return;
case RC_VALUE_TYPE_FLOAT:
amount = rc_typed_value_convert_into(&converted, amount, RC_VALUE_TYPE_FLOAT);
break;
default:
value->type = RC_VALUE_TYPE_NONE;
return;
}
break;
case RC_VALUE_TYPE_FLOAT:
break;
default:
value->type = RC_VALUE_TYPE_NONE;
return;
}
if (amount->value.f32 == 0.0) { /* divide by zero */
value->type = RC_VALUE_TYPE_NONE;
return;
}
rc_typed_value_convert(value, RC_VALUE_TYPE_FLOAT);
value->value.f32 /= amount->value.f32;
}
static int rc_typed_value_compare_floats(float f1, float f2, char oper) {
if (f1 == f2) {
/* exactly equal */
}
else {
/* attempt to match 7 significant digits (24-bit mantissa supports just over 7 significant decimal digits) */
/* https://stackoverflow.com/questions/17333/what-is-the-most-effective-way-for-float-and-double-comparison */
const float abs1 = (f1 < 0) ? -f1 : f1;
const float abs2 = (f2 < 0) ? -f2 : f2;
const float threshold = ((abs1 < abs2) ? abs1 : abs2) * FLT_EPSILON;
const float diff = f1 - f2;
const float abs_diff = (diff < 0) ? -diff : diff;
if (abs_diff <= threshold) {
/* approximately equal */
}
else if (diff > threshold) {
/* greater */
switch (oper) {
case RC_OPERATOR_NE:
case RC_OPERATOR_GT:
case RC_OPERATOR_GE:
return 1;
default:
return 0;
}
}
else {
/* lesser */
switch (oper) {
case RC_OPERATOR_NE:
case RC_OPERATOR_LT:
case RC_OPERATOR_LE:
return 1;
default:
return 0;
}
}
}
/* exactly or approximately equal */
switch (oper) {
case RC_OPERATOR_EQ:
case RC_OPERATOR_GE:
case RC_OPERATOR_LE:
return 1;
default:
return 0;
}
}
int rc_typed_value_compare(const rc_typed_value_t* value1, const rc_typed_value_t* value2, char oper) {
rc_typed_value_t converted_value2;
if (value2->type != value1->type)
value2 = rc_typed_value_convert_into(&converted_value2, value2, value1->type);
switch (value1->type) {
case RC_VALUE_TYPE_UNSIGNED:
switch (oper) {
case RC_OPERATOR_EQ: return value1->value.u32 == value2->value.u32;
case RC_OPERATOR_NE: return value1->value.u32 != value2->value.u32;
case RC_OPERATOR_LT: return value1->value.u32 < value2->value.u32;
case RC_OPERATOR_LE: return value1->value.u32 <= value2->value.u32;
case RC_OPERATOR_GT: return value1->value.u32 > value2->value.u32;
case RC_OPERATOR_GE: return value1->value.u32 >= value2->value.u32;
default: return 1;
}
case RC_VALUE_TYPE_SIGNED:
switch (oper) {
case RC_OPERATOR_EQ: return value1->value.i32 == value2->value.i32;
case RC_OPERATOR_NE: return value1->value.i32 != value2->value.i32;
case RC_OPERATOR_LT: return value1->value.i32 < value2->value.i32;
case RC_OPERATOR_LE: return value1->value.i32 <= value2->value.i32;
case RC_OPERATOR_GT: return value1->value.i32 > value2->value.i32;
case RC_OPERATOR_GE: return value1->value.i32 >= value2->value.i32;
default: return 1;
}
case RC_VALUE_TYPE_FLOAT:
return rc_typed_value_compare_floats(value1->value.f32, value2->value.f32, oper);
default:
return 1;
}
}

@ -0,0 +1,877 @@
#include "rc_hash.h"
#include "../rcheevos/rc_compat.h"
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
/* internal helper functions in hash.c */
extern void* rc_file_open(const char* path);
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);
extern const char* rc_path_get_filename(const char* path);
extern int rc_path_compare_extension(const char* path, const char* ext);
extern rc_hash_message_callback verbose_message_callback;
struct cdrom_t
{
void* file_handle; /* the file handle for reading the track data */
int sector_size; /* the size of each sector in the track data */
int sector_header_size; /* the offset to the raw data within a sector block */
int raw_data_size; /* the amount of raw data within a sector block */
int64_t file_track_offset;/* the offset of the track data within the file */
int track_first_sector; /* the first absolute sector associated to the track (includes pregap) */
int track_pregap_sectors; /* the number of pregap sectors */
#ifndef NDEBUG
uint32_t track_id; /* the index of the track */
#endif
};
static int cdreader_get_sector(unsigned char header[16])
{
int minutes = (header[12] >> 4) * 10 + (header[12] & 0x0F);
int seconds = (header[13] >> 4) * 10 + (header[13] & 0x0F);
int frames = (header[14] >> 4) * 10 + (header[14] & 0x0F);
/* convert the MSF value to a sector index, and subtract 150 (2 seconds) per:
* For data and mixed mode media (those conforming to ISO/IEC 10149), logical block address
* zero shall be assigned to the block at MSF address 00/02/00 */
return ((minutes * 60) + seconds) * 75 + frames - 150;
}
static void cdreader_determine_sector_size(struct cdrom_t* cdrom)
{
/* Attempt to determine the sector and header sizes. The CUE file may be lying.
* Look for the sync pattern using each of the supported sector sizes.
* Then check for the presence of "CD001", which is gauranteed to be in either the
* boot record or primary volume descriptor, one of which is always at sector 16.
*/
const unsigned char sync_pattern[] = {
0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00
};
unsigned char header[32];
const int64_t toc_sector = 16 + cdrom->track_pregap_sectors;
cdrom->sector_size = 0;
cdrom->sector_header_size = 0;
cdrom->raw_data_size = 2048;
rc_file_seek(cdrom->file_handle, toc_sector * 2352 + cdrom->file_track_offset, SEEK_SET);
if (rc_file_read(cdrom->file_handle, header, sizeof(header)) < sizeof(header))
return;
if (memcmp(header, sync_pattern, 12) == 0)
{
cdrom->sector_size = 2352;
if (memcmp(&header[25], "CD001", 5) == 0)
cdrom->sector_header_size = 24;
else
cdrom->sector_header_size = 16;
cdrom->track_first_sector = cdreader_get_sector(header) - (int)toc_sector;
}
else
{
rc_file_seek(cdrom->file_handle, toc_sector * 2336 + cdrom->file_track_offset, SEEK_SET);
rc_file_read(cdrom->file_handle, header, sizeof(header));
if (memcmp(header, sync_pattern, 12) == 0)
{
cdrom->sector_size = 2336;
if (memcmp(&header[25], "CD001", 5) == 0)
cdrom->sector_header_size = 24;
else
cdrom->sector_header_size = 16;
cdrom->track_first_sector = cdreader_get_sector(header) - (int)toc_sector;
}
else
{
rc_file_seek(cdrom->file_handle, toc_sector * 2048 + cdrom->file_track_offset, SEEK_SET);
rc_file_read(cdrom->file_handle, header, sizeof(header));
if (memcmp(&header[1], "CD001", 5) == 0)
{
cdrom->sector_size = 2048;
cdrom->sector_header_size = 0;
}
}
}
}
static void* cdreader_open_bin_track(const char* path, uint32_t track)
{
void* file_handle;
struct cdrom_t* cdrom;
if (track > 1)
{
if (verbose_message_callback)
verbose_message_callback("Cannot locate secondary tracks without a cue sheet");
return NULL;
}
file_handle = rc_file_open(path);
if (!file_handle)
return NULL;
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
cdrom->file_handle = file_handle;
#ifndef NDEBUG
cdrom->track_id = track;
#endif
cdreader_determine_sector_size(cdrom);
if (cdrom->sector_size == 0)
{
int64_t size;
rc_file_seek(cdrom->file_handle, 0, SEEK_END);
size = rc_file_tell(cdrom->file_handle);
if ((size % 2352) == 0)
{
/* raw tracks use all 2352 bytes and have a 24 byte header */
cdrom->sector_size = 2352;
cdrom->sector_header_size = 24;
}
else if ((size % 2048) == 0)
{
/* cooked tracks eliminate all header/footer data */
cdrom->sector_size = 2048;
cdrom->sector_header_size = 0;
}
else if ((size % 2336) == 0)
{
/* MODE 2 format without 16-byte sync data */
cdrom->sector_size = 2336;
cdrom->sector_header_size = 8;
}
else
{
free(cdrom);
if (verbose_message_callback)
verbose_message_callback("Could not determine sector size");
return NULL;
}
}
return cdrom;
}
static int cdreader_open_bin(struct cdrom_t* cdrom, const char* path, const char* mode)
{
cdrom->file_handle = rc_file_open(path);
if (!cdrom->file_handle)
return 0;
/* determine sector size */
cdreader_determine_sector_size(cdrom);
/* could not determine, which means we'll probably have more issues later
* but use the CUE provided information anyway
*/
if (cdrom->sector_size == 0)
{
/* All of these modes have 2048 byte payloads. In MODE1/2352 and MODE2/2352
* modes, the mode can actually be specified per sector to change the payload
* size, but that reduces the ability to recover from errors when the disc
* is damaged, so it's seldomly used, and when it is, it's mostly for audio
* or video data where a blip or two probably won't be noticed by the user.
* So, while we techincally support all of the following modes, we only do
* so with 2048 byte payloads.
* http://totalsonicmastering.com/cuesheetsyntax.htm
* MODE1/2048 ? CDROM Mode1 Data (cooked) [no header, no footer]
* MODE1/2352 ? CDROM Mode1 Data (raw) [16 byte header, 288 byte footer]
* MODE2/2336 ? CDROM-XA Mode2 Data [8 byte header, 280 byte footer]
* MODE2/2352 ? CDROM-XA Mode2 Data [24 byte header, 280 byte footer]
*/
if (memcmp(mode, "MODE2/2352", 10) == 0)
{
cdrom->sector_size = 2352;
cdrom->sector_header_size = 24;
}
else if (memcmp(mode, "MODE1/2048", 10) == 0)
{
cdrom->sector_size = 2048;
cdrom->sector_header_size = 0;
}
else if (memcmp(mode, "MODE2/2336", 10) == 0)
{
cdrom->sector_size = 2336;
cdrom->sector_header_size = 8;
}
else if (memcmp(mode, "MODE1/2352", 10) == 0)
{
cdrom->sector_size = 2352;
cdrom->sector_header_size = 16;
}
else if (memcmp(mode, "AUDIO", 5) == 0)
{
cdrom->sector_size = 2352;
cdrom->sector_header_size = 0;
cdrom->raw_data_size = 2352; /* no header or footer data on audio tracks */
}
}
return (cdrom->sector_size != 0);
}
static char* cdreader_get_bin_path(const char* cue_path, const char* bin_name)
{
const char* filename = rc_path_get_filename(cue_path);
const size_t bin_name_len = strlen(bin_name);
const size_t cue_path_len = filename - cue_path;
const size_t needed = cue_path_len + bin_name_len + 1;
char* bin_filename = (char*)malloc(needed);
if (!bin_filename)
{
char buffer[64];
snprintf(buffer, sizeof(buffer), "Failed to allocate %u bytes", (unsigned)needed);
rc_hash_error((const char*)buffer);
}
else
{
memcpy(bin_filename, cue_path, cue_path_len);
memcpy(bin_filename + cue_path_len, bin_name, bin_name_len + 1);
}
return bin_filename;
}
static int64_t cdreader_get_bin_size(const char* cue_path, const char* bin_name)
{
int64_t size = 0;
char* bin_filename = cdreader_get_bin_path(cue_path, bin_name);
if (bin_filename)
{
/* disable verbose messaging while getting file size */
rc_hash_message_callback old_verbose_message_callback = verbose_message_callback;
void* file_handle;
verbose_message_callback = NULL;
file_handle = rc_file_open(bin_filename);
if (file_handle)
{
rc_file_seek(file_handle, 0, SEEK_END);
size = rc_file_tell(file_handle);
rc_file_close(file_handle);
}
verbose_message_callback = old_verbose_message_callback;
free(bin_filename);
}
return size;
}
static void* cdreader_open_cue_track(const char* path, uint32_t track)
{
void* cue_handle;
int64_t cue_offset = 0;
char buffer[1024];
char* bin_filename = NULL;
char *ptr, *ptr2, *end;
int done = 0;
int session = 1;
size_t num_read = 0;
struct cdrom_t* cdrom = NULL;
struct track_t
{
uint32_t id;
int sector_size;
int sector_count;
int first_sector;
int pregap_sectors;
int is_data;
int file_track_offset;
int file_first_sector;
char mode[16];
char filename[256];
} current_track, previous_track, largest_track;
cue_handle = rc_file_open(path);
if (!cue_handle)
return NULL;
memset(&current_track, 0, sizeof(current_track));
memset(&previous_track, 0, sizeof(previous_track));
memset(&largest_track, 0, sizeof(largest_track));
do
{
num_read = rc_file_read(cue_handle, buffer, sizeof(buffer) - 1);
if (num_read == 0)
break;
buffer[num_read] = 0;
if (num_read == sizeof(buffer) - 1)
end = buffer + sizeof(buffer) * 3 / 4;
else
end = buffer + num_read;
for (ptr = buffer; ptr < end; ++ptr)
{
while (*ptr == ' ')
++ptr;
if (strncasecmp(ptr, "INDEX ", 6) == 0)
{
int m = 0, s = 0, f = 0;
int index;
int sector_offset;
ptr += 6;
index = atoi(ptr);
while (*ptr != ' ' && *ptr != '\n')
++ptr;
while (*ptr == ' ')
++ptr;
/* convert mm:ss:ff to sector count */
sscanf(ptr, "%d:%d:%d", &m, &s, &f);
sector_offset = ((m * 60) + s) * 75 + f;
if (current_track.first_sector == -1)
{
current_track.first_sector = sector_offset;
if (strcmp(current_track.filename, previous_track.filename) == 0)
{
previous_track.sector_count = current_track.first_sector - previous_track.first_sector;
current_track.file_track_offset += previous_track.sector_count * previous_track.sector_size;
}
/* if looking for the largest data track, determine previous track size */
if (track == RC_HASH_CDTRACK_LARGEST && previous_track.sector_count > largest_track.sector_count &&
previous_track.is_data)
{
memcpy(&largest_track, &previous_track, sizeof(largest_track));
}
}
if (index == 1)
{
current_track.pregap_sectors = (sector_offset - current_track.first_sector);
if (verbose_message_callback)
{
char message[128];
char* scan = current_track.mode;
while (*scan && !isspace((unsigned char)*scan))
++scan;
*scan = '\0';
/* it's undesirable to truncate offset to 32-bits, but %lld isn't defined in c89. */
snprintf(message, sizeof(message), "Found %s track %ld (first sector %d, sector size %d, %d pregap sectors)",
current_track.mode, current_track.id, current_track.first_sector, current_track.sector_size, current_track.pregap_sectors);
verbose_message_callback(message);
}
if (current_track.id == track)
{
done = 1;
break;
}
if (track == RC_HASH_CDTRACK_FIRST_DATA && current_track.is_data)
{
track = current_track.id;
done = 1;
break;
}
if (track == RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION && session == 2)
{
track = current_track.id;
done = 1;
break;
}
}
}
else if (strncasecmp(ptr, "TRACK ", 6) == 0)
{
if (current_track.sector_size)
memcpy(&previous_track, &current_track, sizeof(current_track));
ptr += 6;
current_track.id = atoi(ptr);
current_track.pregap_sectors = -1;
current_track.first_sector = -1;
while (*ptr != ' ')
++ptr;
while (*ptr == ' ')
++ptr;
memcpy(current_track.mode, ptr, sizeof(current_track.mode));
current_track.is_data = (memcmp(current_track.mode, "MODE", 4) == 0);
if (current_track.is_data)
{
current_track.sector_size = atoi(ptr + 6);
}
else
{
/* assume AUDIO */
current_track.sector_size = 2352;
}
}
else if (strncasecmp(ptr, "FILE ", 5) == 0)
{
if (current_track.sector_size)
{
memcpy(&previous_track, &current_track, sizeof(previous_track));
if (previous_track.sector_count == 0)
{
const uint32_t file_sector_count = (uint32_t)cdreader_get_bin_size(path, previous_track.filename) / previous_track.sector_size;
previous_track.sector_count = file_sector_count - previous_track.first_sector;
}
/* if looking for the largest data track, check to see if this one is larger */
if (track == RC_HASH_CDTRACK_LARGEST && previous_track.is_data &&
previous_track.sector_count > largest_track.sector_count)
{
memcpy(&largest_track, &previous_track, sizeof(largest_track));
}
}
memset(&current_track, 0, sizeof(current_track));
current_track.file_first_sector = previous_track.file_first_sector +
previous_track.first_sector + previous_track.sector_count;
ptr += 5;
ptr2 = ptr;
if (*ptr == '"')
{
++ptr;
do
{
++ptr2;
} while (*ptr2 && *ptr2 != '\n' && *ptr2 != '"');
}
else
{
do
{
++ptr2;
} while (*ptr2 && *ptr2 != '\n' && *ptr2 != ' ');
}
if (ptr2 - ptr < (int)sizeof(current_track.filename))
memcpy(current_track.filename, ptr, ptr2 - ptr);
}
else if (strncasecmp(ptr, "REM ", 4) == 0)
{
ptr += 4;
while (*ptr == ' ')
++ptr;
if (strncasecmp(ptr, "SESSION ", 8) == 0)
{
ptr += 8;
while (*ptr == ' ')
++ptr;
session = atoi(ptr);
}
}
while (*ptr && *ptr != '\n')
++ptr;
}
if (done)
break;
cue_offset += (ptr - buffer);
rc_file_seek(cue_handle, cue_offset, SEEK_SET);
} while (1);
rc_file_close(cue_handle);
if (track == RC_HASH_CDTRACK_LARGEST)
{
if (current_track.sector_size && current_track.is_data)
{
const uint32_t file_sector_count = (uint32_t)cdreader_get_bin_size(path, current_track.filename) / current_track.sector_size;
current_track.sector_count = file_sector_count - current_track.first_sector;
if (largest_track.sector_count > current_track.sector_count)
memcpy(&current_track, &largest_track, sizeof(current_track));
}
else
{
memcpy(&current_track, &largest_track, sizeof(current_track));
}
track = current_track.id;
}
else if (track == RC_HASH_CDTRACK_LAST && !done)
{
track = current_track.id;
}
if (current_track.id == track)
{
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
if (!cdrom)
{
snprintf((char*)buffer, sizeof(buffer), "Failed to allocate %u bytes", (unsigned)sizeof(*cdrom));
rc_hash_error((const char*)buffer);
return NULL;
}
cdrom->file_track_offset = current_track.file_track_offset;
cdrom->track_pregap_sectors = current_track.pregap_sectors;
cdrom->track_first_sector = current_track.file_first_sector + current_track.first_sector;
#ifndef NDEBUG
cdrom->track_id = current_track.id;
#endif
/* verify existance of bin file */
bin_filename = cdreader_get_bin_path(path, current_track.filename);
if (bin_filename)
{
if (cdreader_open_bin(cdrom, bin_filename, current_track.mode))
{
if (verbose_message_callback)
{
if (cdrom->track_pregap_sectors)
snprintf((char*)buffer, sizeof(buffer), "Opened track %ld (sector size %d, %d pregap sectors)",
track, cdrom->sector_size, cdrom->track_pregap_sectors);
else
snprintf((char*)buffer, sizeof(buffer), "Opened track %ld (sector size %d)", track, cdrom->sector_size);
verbose_message_callback((const char*)buffer);
}
}
else
{
if (cdrom->file_handle)
{
rc_file_close(cdrom->file_handle);
snprintf((char*)buffer, sizeof(buffer), "Could not determine sector size for %s track", current_track.mode);
}
else
{
snprintf((char*)buffer, sizeof(buffer), "Could not open %s", bin_filename);
}
rc_hash_error((const char*)buffer);
free(cdrom);
cdrom = NULL;
}
free(bin_filename);
}
}
return cdrom;
}
static void* cdreader_open_gdi_track(const char* path, uint32_t track)
{
void* file_handle;
char buffer[1024];
char mode[16] = "MODE1/";
char sector_size[16];
char file[256];
int64_t track_size;
int track_type;
char* bin_path = NULL;
uint32_t current_track = 0;
char* ptr, *ptr2, *end;
int lba = 0;
uint32_t largest_track = 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;
int64_t file_offset = 0;
struct cdrom_t* cdrom = NULL;
file_handle = rc_file_open(path);
if (!file_handle)
return NULL;
file[0] = '\0';
do
{
num_read = rc_file_read(file_handle, buffer, sizeof(buffer) - 1);
if (num_read == 0)
break;
buffer[num_read] = 0;
if (num_read == sizeof(buffer) - 1)
end = buffer + sizeof(buffer) * 3 / 4;
else
end = buffer + num_read;
ptr = buffer;
/* the first line contains the number of tracks, so we can get the last track index from it */
if (track == RC_HASH_CDTRACK_LAST)
track = atoi(ptr);
/* first line contains the number of tracks and will be skipped */
while (ptr < end)
{
/* skip until next newline */
while (*ptr != '\n' && ptr < end)
++ptr;
/* skip newlines */
while ((*ptr == '\n' || *ptr == '\r') && ptr < end)
++ptr;
/* line format: [trackid] [lba] [type] [sectorsize] [file] [?] */
while (isspace((unsigned char)*ptr))
++ptr;
current_track = (uint32_t)atoi(ptr);
if (track && current_track != track && track != RC_HASH_CDTRACK_FIRST_DATA)
continue;
while (isdigit((unsigned char)*ptr))
++ptr;
++ptr;
while (isspace((unsigned char)*ptr))
++ptr;
lba = atoi(ptr);
while (isdigit((unsigned char)*ptr))
++ptr;
++ptr;
while (isspace((unsigned char)*ptr))
++ptr;
track_type = atoi(ptr);
while (isdigit((unsigned char)*ptr))
++ptr;
++ptr;
while (isspace((unsigned char)*ptr))
++ptr;
ptr2 = sector_size;
while (isdigit((unsigned char)*ptr))
*ptr2++ = *ptr++;
*ptr2 = '\0';
++ptr;
while (isspace((unsigned char)*ptr))
++ptr;
ptr2 = file;
if (*ptr == '\"')
{
++ptr;
while (*ptr != '\"')
*ptr2++ = *ptr++;
++ptr;
}
else
{
while (*ptr != ' ')
*ptr2++ = *ptr++;
}
*ptr2 = '\0';
if (track == current_track)
{
found = 1;
break;
}
else if (track == RC_HASH_CDTRACK_FIRST_DATA && track_type == 4)
{
track = current_track;
found = 1;
break;
}
else if (track == RC_HASH_CDTRACK_LARGEST && track_type == 4)
{
track_size = cdreader_get_bin_size(path, file);
if (track_size > largest_track_size)
{
largest_track_size = track_size;
largest_track = current_track;
largest_track_lba = lba;
strcpy(largest_track_file, file);
strcpy(largest_track_sector_size, sector_size);
}
}
}
if (found)
break;
file_offset += (ptr - buffer);
rc_file_seek(file_handle, file_offset, SEEK_SET);
} while (1);
rc_file_close(file_handle);
cdrom = (struct cdrom_t*)calloc(1, sizeof(*cdrom));
if (!cdrom)
{
snprintf((char*)buffer, sizeof(buffer), "Failed to allocate %u bytes", (unsigned)sizeof(*cdrom));
rc_hash_error((const char*)buffer);
return NULL;
}
/* if we were tracking the largest track, make it the current track.
* otherwise, current_track will be the requested track, or last track. */
if (largest_track != 0 && largest_track != current_track)
{
current_track = largest_track;
strcpy(file, largest_track_file);
strcpy(sector_size, largest_track_sector_size);
lba = largest_track_lba;
}
/* open the bin file for the track - construct mode parameter from sector_size */
ptr = &mode[6];
ptr2 = sector_size;
while (*ptr2 && *ptr2 != '\"')
*ptr++ = *ptr2++;
*ptr = '\0';
bin_path = cdreader_get_bin_path(path, file);
if (cdreader_open_bin(cdrom, bin_path, mode))
{
cdrom->track_pregap_sectors = 0;
cdrom->track_first_sector = lba;
#ifndef NDEBUG
cdrom->track_id = current_track;
#endif
if (verbose_message_callback)
{
snprintf((char*)buffer, sizeof(buffer), "Opened track %ld (sector size %d)", current_track, cdrom->sector_size);
verbose_message_callback((const char*)buffer);
}
}
else
{
snprintf((char*)buffer, sizeof(buffer), "Could not open %s", bin_path);
rc_hash_error((const char*)buffer);
free(cdrom);
cdrom = NULL;
}
free(bin_path);
return cdrom;
}
static void* cdreader_open_track(const char* path, uint32_t track)
{
/* backwards compatibility - 0 used to mean largest */
if (track == 0)
track = RC_HASH_CDTRACK_LARGEST;
if (rc_path_compare_extension(path, "cue"))
return cdreader_open_cue_track(path, track);
if (rc_path_compare_extension(path, "gdi"))
return cdreader_open_gdi_track(path, track);
return cdreader_open_bin_track(path, track);
}
static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes)
{
int64_t sector_start;
size_t num_read, total_read = 0;
uint8_t* buffer_ptr = (uint8_t*)buffer;
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
if (!cdrom)
return 0;
if (sector < (uint32_t)cdrom->track_first_sector)
return 0;
sector_start = (int64_t)(sector - cdrom->track_first_sector) * cdrom->sector_size +
cdrom->sector_header_size + cdrom->file_track_offset;
while (requested_bytes > (size_t)cdrom->raw_data_size)
{
rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET);
num_read = rc_file_read(cdrom->file_handle, buffer_ptr, cdrom->raw_data_size);
total_read += num_read;
if (num_read < (size_t)cdrom->raw_data_size)
return total_read;
buffer_ptr += cdrom->raw_data_size;
sector_start += cdrom->sector_size;
requested_bytes -= cdrom->raw_data_size;
}
rc_file_seek(cdrom->file_handle, sector_start, SEEK_SET);
num_read = rc_file_read(cdrom->file_handle, buffer_ptr, (int)requested_bytes);
total_read += num_read;
return total_read;
}
static void cdreader_close_track(void* track_handle)
{
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
if (cdrom)
{
if (cdrom->file_handle)
rc_file_close(cdrom->file_handle);
free(track_handle);
}
}
static uint32_t cdreader_first_track_sector(void* track_handle)
{
struct cdrom_t* cdrom = (struct cdrom_t*)track_handle;
if (cdrom)
return cdrom->track_first_sector + cdrom->track_pregap_sectors;
return 0;
}
void rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader)
{
cdreader->open_track = cdreader_open_track;
cdreader->read_sector = cdreader_read_sector;
cdreader->close_track = cdreader_close_track;
cdreader->first_track_sector = cdreader_first_track_sector;
}
void rc_hash_init_default_cdreader()
{
struct rc_hash_cdreader cdreader;
rc_hash_get_default_cdreader(&cdreader);
rc_hash_init_custom_cdreader(&cdreader);
}

File diff suppressed because it is too large Load Diff

@ -0,0 +1,381 @@
/*
Copyright (C) 1999, 2000, 2002 Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
*/
/* $Id: md5.c,v 1.6 2002/04/13 19:20:28 lpd Exp $ */
/*
Independent implementation of MD5 (RFC 1321).
This code implements the MD5 Algorithm defined in RFC 1321, whose
text is available at
http://www.ietf.org/rfc/rfc1321.txt
The code is derived from the text of the RFC, including the test suite
(section A.5) but excluding the rest of Appendix A. It does not include
any code or documentation that is identified in the RFC as being
copyrighted.
The original and principal author of md5.c is L. Peter Deutsch
<ghost@aladdin.com>. Other authors are noted in the change history
that follows (in reverse chronological order):
2002-04-13 lpd Clarified derivation from RFC 1321; now handles byte order
either statically or dynamically; added missing #include <string.h>
in library.
2002-03-11 lpd Corrected argument list for main(), and added int return
type, in test program and T value program.
2002-02-21 lpd Added missing #include <stdio.h> in test program.
2000-07-03 lpd Patched to eliminate warnings about "constant is
unsigned in ANSI C, signed in traditional"; made test program
self-checking.
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5).
1999-05-03 lpd Original version.
*/
#include "md5.h"
#include <string.h>
#undef BYTE_ORDER /* 1 = big-endian, -1 = little-endian, 0 = unknown */
#ifdef ARCH_IS_BIG_ENDIAN
# define BYTE_ORDER (ARCH_IS_BIG_ENDIAN ? 1 : -1)
#else
# define BYTE_ORDER 0
#endif
#define T_MASK ((md5_word_t)~0)
#define T1 /* 0xd76aa478 */ (T_MASK ^ 0x28955b87)
#define T2 /* 0xe8c7b756 */ (T_MASK ^ 0x173848a9)
#define T3 0x242070db
#define T4 /* 0xc1bdceee */ (T_MASK ^ 0x3e423111)
#define T5 /* 0xf57c0faf */ (T_MASK ^ 0x0a83f050)
#define T6 0x4787c62a
#define T7 /* 0xa8304613 */ (T_MASK ^ 0x57cfb9ec)
#define T8 /* 0xfd469501 */ (T_MASK ^ 0x02b96afe)
#define T9 0x698098d8
#define T10 /* 0x8b44f7af */ (T_MASK ^ 0x74bb0850)
#define T11 /* 0xffff5bb1 */ (T_MASK ^ 0x0000a44e)
#define T12 /* 0x895cd7be */ (T_MASK ^ 0x76a32841)
#define T13 0x6b901122
#define T14 /* 0xfd987193 */ (T_MASK ^ 0x02678e6c)
#define T15 /* 0xa679438e */ (T_MASK ^ 0x5986bc71)
#define T16 0x49b40821
#define T17 /* 0xf61e2562 */ (T_MASK ^ 0x09e1da9d)
#define T18 /* 0xc040b340 */ (T_MASK ^ 0x3fbf4cbf)
#define T19 0x265e5a51
#define T20 /* 0xe9b6c7aa */ (T_MASK ^ 0x16493855)
#define T21 /* 0xd62f105d */ (T_MASK ^ 0x29d0efa2)
#define T22 0x02441453
#define T23 /* 0xd8a1e681 */ (T_MASK ^ 0x275e197e)
#define T24 /* 0xe7d3fbc8 */ (T_MASK ^ 0x182c0437)
#define T25 0x21e1cde6
#define T26 /* 0xc33707d6 */ (T_MASK ^ 0x3cc8f829)
#define T27 /* 0xf4d50d87 */ (T_MASK ^ 0x0b2af278)
#define T28 0x455a14ed
#define T29 /* 0xa9e3e905 */ (T_MASK ^ 0x561c16fa)
#define T30 /* 0xfcefa3f8 */ (T_MASK ^ 0x03105c07)
#define T31 0x676f02d9
#define T32 /* 0x8d2a4c8a */ (T_MASK ^ 0x72d5b375)
#define T33 /* 0xfffa3942 */ (T_MASK ^ 0x0005c6bd)
#define T34 /* 0x8771f681 */ (T_MASK ^ 0x788e097e)
#define T35 0x6d9d6122
#define T36 /* 0xfde5380c */ (T_MASK ^ 0x021ac7f3)
#define T37 /* 0xa4beea44 */ (T_MASK ^ 0x5b4115bb)
#define T38 0x4bdecfa9
#define T39 /* 0xf6bb4b60 */ (T_MASK ^ 0x0944b49f)
#define T40 /* 0xbebfbc70 */ (T_MASK ^ 0x4140438f)
#define T41 0x289b7ec6
#define T42 /* 0xeaa127fa */ (T_MASK ^ 0x155ed805)
#define T43 /* 0xd4ef3085 */ (T_MASK ^ 0x2b10cf7a)
#define T44 0x04881d05
#define T45 /* 0xd9d4d039 */ (T_MASK ^ 0x262b2fc6)
#define T46 /* 0xe6db99e5 */ (T_MASK ^ 0x1924661a)
#define T47 0x1fa27cf8
#define T48 /* 0xc4ac5665 */ (T_MASK ^ 0x3b53a99a)
#define T49 /* 0xf4292244 */ (T_MASK ^ 0x0bd6ddbb)
#define T50 0x432aff97
#define T51 /* 0xab9423a7 */ (T_MASK ^ 0x546bdc58)
#define T52 /* 0xfc93a039 */ (T_MASK ^ 0x036c5fc6)
#define T53 0x655b59c3
#define T54 /* 0x8f0ccc92 */ (T_MASK ^ 0x70f3336d)
#define T55 /* 0xffeff47d */ (T_MASK ^ 0x00100b82)
#define T56 /* 0x85845dd1 */ (T_MASK ^ 0x7a7ba22e)
#define T57 0x6fa87e4f
#define T58 /* 0xfe2ce6e0 */ (T_MASK ^ 0x01d3191f)
#define T59 /* 0xa3014314 */ (T_MASK ^ 0x5cfebceb)
#define T60 0x4e0811a1
#define T61 /* 0xf7537e82 */ (T_MASK ^ 0x08ac817d)
#define T62 /* 0xbd3af235 */ (T_MASK ^ 0x42c50dca)
#define T63 0x2ad7d2bb
#define T64 /* 0xeb86d391 */ (T_MASK ^ 0x14792c6e)
static void
md5_process(md5_state_t *pms, const md5_byte_t *data /*[64]*/)
{
md5_word_t
a = pms->abcd[0], b = pms->abcd[1],
c = pms->abcd[2], d = pms->abcd[3];
md5_word_t t;
#if BYTE_ORDER > 0
/* Define storage only for big-endian CPUs. */
md5_word_t X[16];
#else
/* Define storage for little-endian or both types of CPUs. */
md5_word_t xbuf[16];
const md5_word_t *X;
#endif
{
#if BYTE_ORDER == 0
/*
* Determine dynamically whether this is a big-endian or
* little-endian machine, since we can use a more efficient
* algorithm on the latter.
*/
static const int w = 1;
if (*((const md5_byte_t *)&w)) /* dynamic little-endian */
#endif
#if BYTE_ORDER <= 0 /* little-endian */
{
/*
* On little-endian machines, we can process properly aligned
* data without copying it.
*/
if (!((data - (const md5_byte_t *)0) & 3)) {
/* data are properly aligned */
X = (const md5_word_t *)data;
} else {
/* not aligned */
memcpy(xbuf, data, 64);
X = xbuf;
}
}
#endif
#if BYTE_ORDER == 0
else /* dynamic big-endian */
#endif
#if BYTE_ORDER >= 0 /* big-endian */
{
/*
* On big-endian machines, we must arrange the bytes in the
* right order.
*/
const md5_byte_t *xp = data;
int i;
# if BYTE_ORDER == 0
X = xbuf; /* (dynamic only) */
# else
# define xbuf X /* (static only) */
# endif
for (i = 0; i < 16; ++i, xp += 4)
xbuf[i] = xp[0] + (xp[1] << 8) + (xp[2] << 16) + (xp[3] << 24);
}
#endif
}
#define ROTATE_LEFT(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
/* Round 1. */
/* Let [abcd k s i] denote the operation
a = b + ((a + F(b,c,d) + X[k] + T[i]) <<< s). */
#define F(x, y, z) (((x) & (y)) | (~(x) & (z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + F(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 0, 7, T1);
SET(d, a, b, c, 1, 12, T2);
SET(c, d, a, b, 2, 17, T3);
SET(b, c, d, a, 3, 22, T4);
SET(a, b, c, d, 4, 7, T5);
SET(d, a, b, c, 5, 12, T6);
SET(c, d, a, b, 6, 17, T7);
SET(b, c, d, a, 7, 22, T8);
SET(a, b, c, d, 8, 7, T9);
SET(d, a, b, c, 9, 12, T10);
SET(c, d, a, b, 10, 17, T11);
SET(b, c, d, a, 11, 22, T12);
SET(a, b, c, d, 12, 7, T13);
SET(d, a, b, c, 13, 12, T14);
SET(c, d, a, b, 14, 17, T15);
SET(b, c, d, a, 15, 22, T16);
#undef SET
/* Round 2. */
/* Let [abcd k s i] denote the operation
a = b + ((a + G(b,c,d) + X[k] + T[i]) <<< s). */
#define G(x, y, z) (((x) & (z)) | ((y) & ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + G(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 1, 5, T17);
SET(d, a, b, c, 6, 9, T18);
SET(c, d, a, b, 11, 14, T19);
SET(b, c, d, a, 0, 20, T20);
SET(a, b, c, d, 5, 5, T21);
SET(d, a, b, c, 10, 9, T22);
SET(c, d, a, b, 15, 14, T23);
SET(b, c, d, a, 4, 20, T24);
SET(a, b, c, d, 9, 5, T25);
SET(d, a, b, c, 14, 9, T26);
SET(c, d, a, b, 3, 14, T27);
SET(b, c, d, a, 8, 20, T28);
SET(a, b, c, d, 13, 5, T29);
SET(d, a, b, c, 2, 9, T30);
SET(c, d, a, b, 7, 14, T31);
SET(b, c, d, a, 12, 20, T32);
#undef SET
/* Round 3. */
/* Let [abcd k s t] denote the operation
a = b + ((a + H(b,c,d) + X[k] + T[i]) <<< s). */
#define H(x, y, z) ((x) ^ (y) ^ (z))
#define SET(a, b, c, d, k, s, Ti)\
t = a + H(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 5, 4, T33);
SET(d, a, b, c, 8, 11, T34);
SET(c, d, a, b, 11, 16, T35);
SET(b, c, d, a, 14, 23, T36);
SET(a, b, c, d, 1, 4, T37);
SET(d, a, b, c, 4, 11, T38);
SET(c, d, a, b, 7, 16, T39);
SET(b, c, d, a, 10, 23, T40);
SET(a, b, c, d, 13, 4, T41);
SET(d, a, b, c, 0, 11, T42);
SET(c, d, a, b, 3, 16, T43);
SET(b, c, d, a, 6, 23, T44);
SET(a, b, c, d, 9, 4, T45);
SET(d, a, b, c, 12, 11, T46);
SET(c, d, a, b, 15, 16, T47);
SET(b, c, d, a, 2, 23, T48);
#undef SET
/* Round 4. */
/* Let [abcd k s t] denote the operation
a = b + ((a + I(b,c,d) + X[k] + T[i]) <<< s). */
#define I(x, y, z) ((y) ^ ((x) | ~(z)))
#define SET(a, b, c, d, k, s, Ti)\
t = a + I(b,c,d) + X[k] + Ti;\
a = ROTATE_LEFT(t, s) + b
/* Do the following 16 operations. */
SET(a, b, c, d, 0, 6, T49);
SET(d, a, b, c, 7, 10, T50);
SET(c, d, a, b, 14, 15, T51);
SET(b, c, d, a, 5, 21, T52);
SET(a, b, c, d, 12, 6, T53);
SET(d, a, b, c, 3, 10, T54);
SET(c, d, a, b, 10, 15, T55);
SET(b, c, d, a, 1, 21, T56);
SET(a, b, c, d, 8, 6, T57);
SET(d, a, b, c, 15, 10, T58);
SET(c, d, a, b, 6, 15, T59);
SET(b, c, d, a, 13, 21, T60);
SET(a, b, c, d, 4, 6, T61);
SET(d, a, b, c, 11, 10, T62);
SET(c, d, a, b, 2, 15, T63);
SET(b, c, d, a, 9, 21, T64);
#undef SET
/* Then perform the following additions. (That is increment each
of the four registers by the value it had before this block
was started.) */
pms->abcd[0] += a;
pms->abcd[1] += b;
pms->abcd[2] += c;
pms->abcd[3] += d;
}
void
md5_init(md5_state_t *pms)
{
pms->count[0] = pms->count[1] = 0;
pms->abcd[0] = 0x67452301;
pms->abcd[1] = /*0xefcdab89*/ T_MASK ^ 0x10325476;
pms->abcd[2] = /*0x98badcfe*/ T_MASK ^ 0x67452301;
pms->abcd[3] = 0x10325476;
}
void
md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes)
{
const md5_byte_t *p = data;
int left = nbytes;
int offset = (pms->count[0] >> 3) & 63;
md5_word_t nbits = (md5_word_t)(nbytes << 3);
if (nbytes <= 0)
return;
/* Update the message length. */
pms->count[1] += nbytes >> 29;
pms->count[0] += nbits;
if (pms->count[0] < nbits)
pms->count[1]++;
/* Process an initial partial block. */
if (offset) {
int copy = (offset + nbytes > 64 ? 64 - offset : nbytes);
memcpy(pms->buf + offset, p, copy);
if (offset + copy < 64)
return;
p += copy;
left -= copy;
md5_process(pms, pms->buf);
}
/* Process full blocks. */
for (; left >= 64; p += 64, left -= 64)
md5_process(pms, p);
/* Process a final partial block. */
if (left)
memcpy(pms->buf, p, left);
}
void
md5_finish(md5_state_t *pms, md5_byte_t digest[16])
{
static const md5_byte_t pad[64] = {
0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
md5_byte_t data[8];
int i;
/* Save the length before padding. */
for (i = 0; i < 8; ++i)
data[i] = (md5_byte_t)(pms->count[i >> 2] >> ((i & 3) << 3));
/* Pad to 56 bytes mod 64. */
md5_append(pms, pad, ((55 - (pms->count[0] >> 3)) & 63) + 1);
/* Append the length. */
md5_append(pms, data, 8);
for (i = 0; i < 16; ++i)
digest[i] = (md5_byte_t)(pms->abcd[i >> 2] >> ((i & 3) << 3));
}

@ -0,0 +1,91 @@
/*
Copyright (C) 1999, 2002 Aladdin Enterprises. All rights reserved.
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
L. Peter Deutsch
ghost@aladdin.com
*/
/* $Id: md5.h,v 1.4 2002/04/13 19:20:28 lpd Exp $ */
/*
Independent implementation of MD5 (RFC 1321).
This code implements the MD5 Algorithm defined in RFC 1321, whose
text is available at
http://www.ietf.org/rfc/rfc1321.txt
The code is derived from the text of the RFC, including the test suite
(section A.5) but excluding the rest of Appendix A. It does not include
any code or documentation that is identified in the RFC as being
copyrighted.
The original and principal author of md5.h is L. Peter Deutsch
<ghost@aladdin.com>. Other authors are noted in the change history
that follows (in reverse chronological order):
2002-04-13 lpd Removed support for non-ANSI compilers; removed
references to Ghostscript; clarified derivation from RFC 1321;
now handles byte order either statically or dynamically.
1999-11-04 lpd Edited comments slightly for automatic TOC extraction.
1999-10-18 lpd Fixed typo in header comment (ansi2knr rather than md5);
added conditionalization for C++ compilation from Martin
Purschke <purschke@bnl.gov>.
1999-05-03 lpd Original version.
*/
#ifndef md5_INCLUDED
# define md5_INCLUDED
/*
* This package supports both compile-time and run-time determination of CPU
* byte order. If ARCH_IS_BIG_ENDIAN is defined as 0, the code will be
* compiled to run only on little-endian CPUs; if ARCH_IS_BIG_ENDIAN is
* defined as non-zero, the code will be compiled to run only on big-endian
* CPUs; if ARCH_IS_BIG_ENDIAN is not defined, the code will be compiled to
* run on either big- or little-endian CPUs, but will run slightly less
* efficiently on either one than if ARCH_IS_BIG_ENDIAN is defined.
*/
typedef unsigned char md5_byte_t; /* 8-bit byte */
typedef unsigned int md5_word_t; /* 32-bit word */
/* Define the state of the MD5 Algorithm. */
typedef struct md5_state_s {
md5_word_t count[2]; /* message length in bits, lsw first */
md5_word_t abcd[4]; /* digest buffer */
md5_byte_t buf[64]; /* accumulate block */
} md5_state_t;
#ifdef __cplusplus
extern "C"
{
#endif
/* Initialize the algorithm. */
void md5_init(md5_state_t *pms);
/* Append a string to the message. */
void md5_append(md5_state_t *pms, const md5_byte_t *data, int nbytes);
/* Finish the message and return the digest. */
void md5_finish(md5_state_t *pms, md5_byte_t digest[16]);
#ifdef __cplusplus
} /* end extern "C" */
#endif
#endif /* md5_INCLUDED */
Loading…
Cancel
Save