commit
41c9a75219
@ -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(¤t_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, ¤t_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, ¤t_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(¤t_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(¤t_track, &largest_track, sizeof(current_track));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
memcpy(¤t_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…
Reference in new issue