Compare commits

...
522 Commits
Author SHA1 Message Date
TellowKrinkleandConnor McLaughlin a462c914fc CI:mac: Update to Xcode 15.2 2024-03-23 00:01:43 +10:00
TellowKrinkleandConnor McLaughlin 60cb78577f CI:mac: Set CMAKE_INSTALL_NAME_DIR
CMake defaults to using rpath-based installs, but doesn't set rpath to include the deps dir when building, breaking Qt builds when they try to invoke rcc (which links against zstd) from the build dir
2024-03-23 00:01:43 +10:00
TellowKrinkleandConnor McLaughlin e8ad355a90 CI:mac: Use shared cmake config variable 2024-03-23 00:01:43 +10:00
TellowKrinkleandConnor McLaughlin 0a161f9591 CI: Support relative dirs in install scripts 2024-03-23 00:01:43 +10:00
Stenzek 234acf5ca2 Qt: Fix main window stuck open after update 2024-03-22 22:58:29 +10:00
KamFretoZandrefractionpcsx2 7689729d9b README: Update docs link 2024-03-22 08:11:57 +00:00
refractionpcsx2 6177825939 GameDB: Fix SSX On Tour NTSC-US patch 2024-03-22 08:10:06 +00:00
KamFretoZandConnor McLaughlin a74a236654 Qt: Update docs link on setup wizard
Since the links for the docs has been changed, update the link on the setup wizard.
2024-03-22 12:37:13 +10:00
lightningterror 93a1ae9f2f GameDB: Add some gshw fixes.
Add full mipmap with ps2 trilinear to Global Storm:
Fixes ground textures.

Add missing autoflush fixes to other regions of Terminator 3 The Redemption:
Fixes environment lights visible through player model.
2024-03-22 03:29:41 +01:00
JordanTheToasterandConnor McLaughlin 3f4d9fd23c Qt: Fix closing log window via taskbar 2024-03-21 13:07:48 +10:00
refractionpcsx2 23d98e9352 GS/HW: Optimize RTA correction to reduce copies 2024-03-20 23:51:17 +00:00
lightningterror 8f381a4e16 GS/HW: Improve how we handle texture shuffles with barriers.
If barriers are already present and it's a texture shuffle then prefer full sw blend, it will be more accurate with no cost.

One barrier is enough for texture shuffles, no need to do full, which means less texture barriers and draw calls.
2024-03-19 15:38:20 +01:00
PCSX2 Botandlightningterror 7e43448110 PAD: Update to latest controller database. 2024-03-18 17:33:05 +01:00
JordanTheToasterandlightningterror 3ca9680c88 UI: Rename screenshot size options 2024-03-18 14:43:24 +01:00
JordanTheToasterandlightningterror 6f1048d6fd GameDB: Various fixes 2024-03-18 14:43:24 +01:00
nishiandGitHub 6d92cee5bb GameDB: NTSC-J Fixes. (#10928)
Typo/mistake corrections and improvements to sorting.
2024-03-18 14:19:24 +01:00
Pierre GRASSERandGitHub d1bff18e0c UI: Add the "points" unit in some RA-related strings (#10917) 2024-03-17 22:29:09 +00:00
refractionpcsx2andlightningterror 4d2b2e5803 GS/HW: Decorrect targets for readbacks. 2024-03-17 17:24:39 +01:00
refractionpcsx2andlightningterror bfef8397d6 GS/HW: Allow source is rt to use corrected alpha in most cases. 2024-03-17 17:24:39 +01:00
lightningterror 886a368297 GS/HW: Clean up Correct/Decorrect calls and functions.
No need to check m_rt_alpha_scale and m_type == RenderTarget when we already check them in the Correct/Decorrect functions.
2024-03-17 17:24:39 +01:00
lightningterror 73d617fb72 GS/HW: RTA Decorrect on Target Update. 2024-03-17 17:24:39 +01:00
lightningterror 3a2888a5d5 GS/HW: Adjust RTA shader precision. 2024-03-17 17:24:39 +01:00
lightningterror 97237c963f GS/HW: Check if channel shuffling actually needs decorrecting.
Saves copies.
2024-03-17 17:24:39 +01:00
lightningterror f2b3db9cbc GS/HW: Improve how we handle decorrection on texture shuffles.
Avoid copies if we can.
2024-03-17 17:24:39 +01:00
refractionpcsx2andlightningterror 7a547e64c4 GS/HW: RTA checks for moves and DST matches and ICO CRC. 2024-03-17 17:24:39 +01:00
lightningterror 6a5ac4fe54 GS/HW: Avoid corrections on new targets. 2024-03-17 17:24:39 +01:00
lightningterror 500e86c43c GS/HW: Update TargetClear to work with RTA Correction. 2024-03-17 17:24:39 +01:00
lightningterror 0900c2fd8b GS/HW: RTA Correction, implement on DATE.
Less copies, and makes it work with DATE.
2024-03-17 17:24:39 +01:00
lightningterror 58628b8dd3 GS/HW: Make sure we don't multiply Cs if Ad is corrected.
Another potential to avoid copies.
2024-03-17 17:24:39 +01:00
lightningterror 71376ff4e6 GS/HW: Decorrect Ad on sw blend in tfx shader.
Otherwise if we do decorrection in covert shader we add more copies.

Also make sure to decorrect for fbmask, tex is fb.
2024-03-17 17:24:39 +01:00
lightningterror fff4aea076 GS/HW: Avoid (Ad + 1) cases on RTA correction.
HW blend will be wrong here so no need to do copies.
2024-03-17 17:24:39 +01:00
lightningterror 6c9f132093 GS/HW: Add support for Ad (RTA) correction.
The idea is to adjust the alpha destination for more
accurate hw blending which will work on all renderers.

Old behavior has Ad in range within 0-1 whereas for blending 0-2 is needed.

copy rt -> adjust the alpha -> copy back the adjusted alpha-> restore old alpha after blending is done
2024-03-17 17:24:39 +01:00
refractionpcsx2 3b7ad788bf GS/HW: Improve shuffle width/height detection 2024-03-16 11:19:26 +00:00
lightningterror 867afd7da0 Qt: Disable Texture barriers option on Metal.
Not implemented.
2024-03-15 18:02:54 +01:00
lightningterror c4bfdc4506 GS/Metal: Fix some Wformat warnings. 2024-03-14 18:18:57 +01:00
lightningterror a7c5eebf99 GS/HW: Adjust blend mix for impossible blend.
Since we can't do Cd*(Alpha + 1) - Cs*Alpha in hw blend what we can do is adjust the Cs value that will be subtracted,
this way we can get a better result in hw blend. Result is still wrong but less wrong than before.
2024-03-14 16:16:11 +01:00
Dan McCarthyandlightningterror a2a6a98635 Debugger: Fixes crash selecting a filter search with new search button
Fixes crash issues when trying to use a search comparison that requires prior results with the New Search button instead of Filter Search.
2024-03-14 01:42:11 +01:00
Dan McCarthyandlightningterror d309e24e60 Debugger: Reduces RAM useage for searches (Act 1)
Reduces memory useage during memory searching process.
- Minimizes passing by copy, uses references to avoid causing copies
- Deletes FutureWatcher ptr when no longer needed to avoid leaking memory (thanks Fobes)
- Updates the search results vector in-place so that no extra copies are needed.
- Makes use of std::move to transfer resources instead of keeping two copies.
- No longer keeps a copy of prior search results in addition to the active search, the stored results are transferred to the search worker and then transferred back.

More improvements to be made, but making a first that should make a
decent impact.

Also: Updates storage of prior results a s a vector now.
Prior since the results were stored in a hashmap, the .keys() function needed to be used to index at an arbitrary point when loading results into the UI.

This caused a big spike in memory usage when the results count is particularly large.
Using a vector optimizes this as we don't need to add any memory when indexing in this way.

Also unlike before when we used vector, we're also removing elements in place when doing filter searches so we don't need two vectors.
2024-03-14 01:42:11 +01:00
KamFretoZandrefractionpcsx2 7abbdf89af OSD: Add Memcard Icon in more places 2024-03-13 11:57:36 +00:00
KamFretoZandrefractionpcsx2 4720f69b76 BPM: Implement "Open in File Browser" Function 2024-03-13 11:57:36 +00:00
Stenzek 515cbc7b29 Path: Add CreateFileURL() 2024-03-13 20:13:35 +10:00
lightningterrorandrefractionpcsx2 b4992856f7 GS/HW: Instead of adjusting blend min/max, adjust GetAlphaMinMax.
Might help in blending cases.
2024-03-12 11:25:29 +00:00
lightningterrorandrefractionpcsx2 74df63ff94 GS/HW: Remove blend_ad_improved case.
Didn't work properly and will be useless when RTA correction pr is merged.
2024-03-12 11:25:29 +00:00
lightningterrorandrefractionpcsx2 52ac8f0d7b GS/HW: Calculate blend/rt alpha min/max based on alpha test. 2024-03-12 11:25:29 +00:00
lightningterrorandrefractionpcsx2 760ea91cc1 GS/HW: Adjust blend_alpha_min/max based on DATE. 2024-03-12 11:25:29 +00:00
refractionpcsx2 2324922111 GS/HW: Don't allow conversion to indexed is read is outside the target 2024-03-12 09:12:56 +00:00
PCSX2 Botandlightningterror 1cfc2780d0 PAD: Update to latest controller database. 2024-03-11 17:04:34 +01:00
lightningterrorandrefractionpcsx2 c0e47767b7 GS/HW: Adjust dither on Blend Mix when Cs-Cd * Af blend. 2024-03-10 23:41:18 +00:00
refractionpcsx2 65649b3cbb GS/HW: Adjust dither on Blend Mix when Cs-Cd * As blend. 2024-03-10 23:41:18 +00:00
Stenzek d28ba0e53c GS/HW: Don't include TBW in hash cache key
Since we're hashing at the block level, a different TBW that causes a
different-looking texture should cause different blocks to get hashed,
and thus, a different hash.
2024-03-10 23:22:34 +10:00
Stenzek 4fef86a635 GS/HW: Reduce duplicates in hash cache
- Don't include TCC in the hashed TEX0 bits.
 - Hash the region size, not rectangle.

Significantly reduces hash cache size in Ace Combat 5, over the course
of 30 frames from 1,000+ textures down to 400.

NOTE: This will change texture replacement hashes. Any "old" region
textures will transparently be converted to the new internal name format
upon loading.
2024-03-10 18:26:14 +10:00
Stenzek b16bb14c58 GS/Vulkan: Work around NVIDIA attachment clear bug 2024-03-10 13:16:18 +10:00
Stenzek 36278b6aae GS/HW: Handle redundant FRAME+Z buffer clears 2024-03-10 13:16:03 +10:00
Stenzek 88165ab072 CI: Update to SDL2 2.30.1 and Qt 6.6.2 2024-03-09 14:04:14 +10:00
Stenzek 5337e46f43 Qt: Fix log window disabling itself on close 2024-03-08 23:43:13 +10:00
Stenzek 7ed6801101 VulkanDevice: Add additional semaphore on swap chain
We don't actually need +1 semaphores, or, more than one really.
But, the validation layer gets cranky if we don't fence wait before the next image acquire.
So, add an additional semaphore to ensure that we're never acquiring before fence waiting.
2024-03-08 23:42:58 +10:00
dreamsyntaxandConnor McLaughlin 162354decf Qt: Toggle for DualSense Player LED 2024-03-07 12:37:00 +10:00
Ty Lamontagneandrefractionpcsx2 483c7f41de vmmanager:revert gpu logging 2024-03-07 02:34:53 +00:00
Ty Lamontagneandrefractionpcsx2 46e039dab2 VMManager: Log power profile and GPUs on startup 2024-03-07 01:21:28 +00:00
refractionpcsx2 4d23410b99 GS/HW: Pre check DATE requirement before updating rt alphas 2024-03-07 01:20:25 +00:00
JordanTheToasterandrefractionpcsx2 88192adfd0 GameDB: God of War 2 Fixes 2024-03-05 12:21:14 +00:00
KamFretoZandrefractionpcsx2 e3c745cf9b OSD: Fix Recording Icon 2024-03-05 12:19:51 +00:00
Pierre GRASSERandrefractionpcsx2 e5de4c8f10 UI: Fix broken setup guide links 2024-03-05 00:51:03 +00:00
PCSX2 Botandlightningterror 511cb0c322 PAD: Update to latest controller database. 2024-03-04 17:07:55 +01:00
PCSX2 Botandrefractionpcsx2 688306dd9c Qt: Update Base Translation 2024-03-03 00:10:02 +00:00
refractionpcsx2 d39e655fa2 GS/HW: Require alpha for 8H, 4HH, 4HL on source lookup 2024-03-02 21:18:33 +00:00
refractionpcsx2 812a3c1123 GS/HW: Improvements to rt alpha accuracy 2024-03-02 15:28:55 +00:00
KamFretoZandrefractionpcsx2 244a8775dd Qt: Fix InterfaceSettingsWidget layout 2024-03-02 15:08:11 +00:00
KamFretoZandrefractionpcsx2 5e28c2608a FSUI: Icon treatments
OSD: Add Icon for USB Devices
2024-03-02 15:08:11 +00:00
Dan McCarthyandrefractionpcsx2 bd032bbcb8 Debugger: Memory search expansions + results count
Adds memory search comparisons for Increased, Increased By, Decreased, Decreased By, Changed, Not Changed, Changed By.
For arrays, adds not equals, changed, not changed for filter searches.
Now only shows the comparison types that are currently valid for the given search type and if there's prior search results.

Also refactors to allow holding the prior set of search results rather than just the addresses, needed for these search comparisons to work.

Also adds a ui label to show that the debugger is searching after clicking the search button which then gets replaced with the results count when the search completes.
2024-03-02 14:49:15 +00:00
Stenzek 875fdc40a5 GS/Vulkan: Don't ever fully clear stencil on DATE draws
We manually clear the drawn region when it's needed, in all other cases
it's pre-filled with the setup.

Therefore, the two load actions should be preserve and don't care.
2024-03-02 12:42:42 +10:00
Stenzek 0ae3cbf4d6 GS/Vulkan: Use attachment clear for ONE stencil
66% faster in Persona 3 in DATE-heavy scenes.
2024-03-02 12:42:42 +10:00
JordanTheToasterandrefractionpcsx2 b9a7143dee GameDB: Arc the Lad TOTS Fixes 2024-03-01 18:57:29 +00:00
Stenzek ef9cbf6be8 3rdparty/rcheevos: Bump to 3d01191 and move in-tree
- aes and 3DS functions in hash.c removed, due to potential legal issues.
 - .github/test/validator directories removed, as they are unnecessary.
2024-02-29 15:46:05 +10:00
Dan McCarthyandrefractionpcsx2 a903387182 Debugger: Add automatic refresh on 1 second interval
The debugger now refreshes/updates it's widgets once a second so that the user does not need to interact with the debugger to know when data/state changes.
2024-02-28 17:07:56 +00:00
Ty Lamontagneandrefractionpcsx2 4919f9b18c DebugTools: Add noreturn heuristic 2024-02-28 16:48:54 +00:00
JordanTheToasterandrefractionpcsx2 b07b56fce6 GameDB: FIFA Street 2 Fixes 2024-02-28 14:53:41 +00:00
JordanTheToasterandrefractionpcsx2 3a8845dc6f GameDB: FIFA 14 Fixes 2024-02-28 02:06:04 +00:00
refractionpcsx2 de64f784e8 GS/HW: Calculate alpha on updated areas on RT invalidation 2024-02-27 18:19:32 +00:00
PCSX2 Botandlightningterror f2a614913d PAD: Update to latest controller database. 2024-02-26 18:43:23 +01:00
JordanTheToasterandrefractionpcsx2 6b54094e26 GameDB: Various fixes 2024-02-25 14:56:11 +00:00
Stenzek 4442ff7221 CI/Flatpak: Fix dubious ownership error 2024-02-24 14:54:24 +10:00
Bart PiotrowskiandGitHub 19c3dd8419 ci: Switch Flatpak build to flathub-infra/flatpak-github-actions (#10838)
* ci: Switch Flatpak build to flathub-infra/flatpak-github-actions

Flathub team has recently forked flatpak-github-actions and merged
various PRs submitted to the original repo. However, it's not versioned
(yet?), so pin the latest commit instead.

Additionally, enable validation of the build using flatpak-builder-lint,
and run all steps in the container with the runtime and required tooling
baked in.

* Update mirror-screenshots-url

* Shush git complaining about "dubious" ownership

* Update date format to iso8601

* Fix flatpak-builder-lint invocations
2024-02-24 14:19:35 +10:00
JordanTheToasterandrefractionpcsx2 9c9d2b7c9c 3rdparty: Update xbyak to 7.05.1 2024-02-23 22:04:46 +00:00
Dan McCarthyandrefractionpcsx2 f00f0cc846 Debugger: Fixes crash on debugger open when cpu not alive
The debugger was crashing on open if no game was running due to failing to read from the CPU while the cpu was not alive.

The opcode was read before checking if it should be shown, so I have moved it to only read if the showOpcode boolean is true, and set it to not show opcodes of the cpu is not alive.
2024-02-23 11:36:41 +00:00
Ty Lamontagneandrefractionpcsx2 78b6323272 DisassemblyWidget: Allow showing opcodes & some key bind modification 2024-02-23 01:28:44 +00:00
refractionpcsx2 a272d99335 GS/HW: Preload whole target on match 2024-02-21 20:37:34 +00:00
Dan McCarthyandrefractionpcsx2 ce5e66a2d5 Debugger: Migrates Memory Search tab to it's own widget
Moves the Memory Search fucntionality to it's own widget so as to not pollute the CpuWidget with search related functionality, especially as it is intended to grow in scope. CpuWidget is fairly general and as such everything tends to get tossed together which makes it harder to navigate/understand/maintain.
2024-02-21 00:25:40 +00:00
Matías IsraelsonandGitHub 1f584736f1 Core: Correctly identify TOOL and Namco 246/256 bios (#10836) 2024-02-21 00:16:07 +00:00
JordanTheToasterandrefractionpcsx2 645dc3fa9d GameDB: Jak TLF Fixes 2024-02-20 14:02:18 +00:00
Sestainandrefractionpcsx2 96a72286b7 GameDB: Add PCRTCOverscan to Crash Twinsanity (PAL) 2024-02-20 14:01:21 +00:00
Stenzek d3c97bedb9 ImGuiManager: Defer scale updates
Fixes OSD spinbox closing on click, and popups closing on window resize.
2024-02-20 15:52:44 +09:00
Stenzek e6303cef9e 3rdparty/libchdr: Rebase to upstream 2a1119c
Backport of https://github.com/stenzek/duckstation/commit/0e6a9f637bc1975a75edcfd0ccf32f59bbafd733
2024-02-20 15:52:23 +09:00
PCSX2 Botandlightningterror 5b5e11828f PAD: Update to latest controller database. 2024-02-19 17:07:05 +01:00
DigitalMajesticandrefractionpcsx2 84972d2548 UI: Update SW Renderer Threads Tooltip 2024-02-18 19:46:35 +00:00
TellowKrinkleandTellowKrinkle 76be30f5c8 GitHub: Update macOS versions on issue templates 2024-02-17 16:21:07 -06:00
Connor McLaughlinandrefractionpcsx2 689a512f5d GS/Vulkan: Use fbfetch flag for subpass dependency
llvmpipe apparently supports raster order attachment access now, and if you force-disable fbfetch, it wasn't creating the render passes with the self-dependency declared.
2024-02-17 15:16:05 +00:00
JordanTheToasterandrefractionpcsx2 f81d555f7e GSBlock: Add Zen 4 to comment 2024-02-17 15:15:51 +00:00
JordanTheToasterandrefractionpcsx2 83376d3f3b Misc: Rename Disable Depth Emulation
Renames Disable Depth Emulation to Disable Depth Conversion as it is both more correct to what it does and less likely for a user to think it is free performance.
2024-02-17 15:15:51 +00:00
JordanTheToasterandrefractionpcsx2 ea00a89cff Interpreter: Comment cleanup 2024-02-17 15:15:51 +00:00
JordanTheToasterandrefractionpcsx2 418a97a02a R5900: Comment cleanup 2024-02-17 15:15:51 +00:00
JordanTheToasterandrefractionpcsx2 d2830b77ec GameDB: Various fixes 2024-02-17 15:15:51 +00:00
PCSX2 Botandlightningterror dcb60c785f PAD: Update to latest controller database. 2024-02-17 09:16:15 +01:00
refractionpcsx2 987dd805c2 UI: Remove option to disable per-game settings 2024-02-17 02:41:20 +00:00
refractionpcsx2 45421a9f96 GS: Fix crop scaling for software and bilinear (sharp) 2024-02-15 13:42:02 +00:00
refractionpcsx2 71c8ad605f RAchievements: Fix off by one error in scratchpad reads 2024-02-14 15:47:24 +00:00
nishiandTellowKrinkle e1fc414691 GameDB: NTSC-J Overhaul 2024-02-13 03:53:31 -06:00
KamFretoZandrefractionpcsx2 dc68da4236 OSD: Add icon for Slow-Mo 2024-02-12 15:13:21 +00:00
RedPanda4552andlightningterror 8dc16827ea Memcard: Fix incorrect order of directory/filename path components for _pcsx2_meta_directory files 2024-02-12 14:51:01 +01:00
6e7c6c205c Memory cards: Update error message
Co-Authored-By: Christian Kenny <16314399+MrCK1@users.noreply.github.com>
2024-02-10 20:42:52 +00:00
PCSX2 Botandrefractionpcsx2 734cfb8966 Qt: Update Base Translation 2024-02-10 00:04:34 +00:00
refractionpcsx2 43c85f22c3 GameDB: Add Autoflush to X2: Wolverine 2024-02-09 13:44:06 +00:00
refractionpcsx2 85e899b92c GS/HW: Only allow tex is fb on alpha if draw is recursive 2024-02-09 13:44:06 +00:00
KamFretoZandrefractionpcsx2 941a8832bf Qt: Revert to old icon 2024-02-06 22:14:35 +00:00
KamFretoZandrefractionpcsx2 8ae111add3 Qt: Translation Fixes 2024-02-06 22:14:35 +00:00
TheTechnician27andrefractionpcsx2 88672dc38f Debugger: Create check for successful QString conversion in MemoryViewerWidget.cpp 2024-02-06 22:02:01 +00:00
KamFretoZandrefractionpcsx2 dccba86dde Qt: Default to No for exit Memcard abort msgbox 2024-02-06 21:49:58 +00:00
JordanTheToasterandTellowKrinkle a7bced02da FS: Fix cover text truncation 2024-02-06 02:01:53 -06:00
PCSX2 Botandlightningterror a654e2ac43 PAD: Update to latest controller database. 2024-02-06 01:05:30 +01:00
Stenzek 56b54e0e17 GS: Move input recording shutdown to VMManager 2024-02-05 13:20:57 +10:00
Stenzek 8ffd920700 deps: Fix prefix for libbacktrace on Linux 2024-02-05 13:19:27 +10:00
Stenzek b6f67a7a6e Qt: Increase controller settings window height
Stop it chopping off text when you switch to profile view.
2024-02-05 13:19:27 +10:00
Stenzek 9308410e12 SDLInputSource: Expose IOKit/MFI toggles
Backport of https://github.com/stenzek/duckstation/commit/73bb2e77affa450fa8c446864d69e18201277e57
2024-02-05 13:19:27 +10:00
Stenzek 65882ae14d deps: Bump to SDL2 2.30.0 2024-02-05 13:19:27 +10:00
Buzzardsoulandrefractionpcsx2 0f533ec16c GameDB: Add patch for Samurai Warriors 2 - Xtreme legends 2024-02-04 21:54:24 +00:00
JordanTheToasterandrefractionpcsx2 0c708e8c1e GameDB: Various fixes 2024-02-04 18:37:38 +00:00
Stenzek 2693544faa GS/Vulkan: Fix incorrect memory order arg 2024-02-05 02:34:21 +10:00
refractionpcsx2 2e95e59f40 DEV9: Fix HDD Response when no HDD is connected 2024-02-03 12:29:50 +00:00
JordanTheToasterandrefractionpcsx2 72bc826907 GameDB: Various fixes 2024-02-03 00:15:25 +00:00
RedDevilusandrefractionpcsx2 21440c31f4 Qt: Add mention zso formatted roms
This was an oversight for the Wizard Setup where it didn't list with the supported formats and the GameList when you don't have any games added. It already works when you click open file among supported formats.
2024-02-02 09:36:45 +00:00
PCSX2 Botandrefractionpcsx2 3e225d78fa Qt: Update Base Translation 2024-02-01 22:57:27 +00:00
Tyler WildingandGitHub 5464463ab0 translations: Syncing Crowdin translations (#10759)
* New translations pcsx2-qt_en.ts (Romanian)
[ci skip]

* New translations pcsx2-qt_en.ts (French)
[ci skip]

* New translations pcsx2-qt_en.ts (Spanish)
[ci skip]

* New translations pcsx2-qt_en.ts (Afrikaans)
[ci skip]

* New translations pcsx2-qt_en.ts (Arabic)
[ci skip]

* New translations pcsx2-qt_en.ts (Catalan)
[ci skip]

* New translations pcsx2-qt_en.ts (Czech)
[ci skip]

* New translations pcsx2-qt_en.ts (Danish)
[ci skip]

* New translations pcsx2-qt_en.ts (German)
[ci skip]

* New translations pcsx2-qt_en.ts (Greek)
[ci skip]

* New translations pcsx2-qt_en.ts (Finnish)
[ci skip]

* New translations pcsx2-qt_en.ts (Hebrew)
[ci skip]

* New translations pcsx2-qt_en.ts (Hungarian)
[ci skip]

* New translations pcsx2-qt_en.ts (Italian)
[ci skip]

* New translations pcsx2-qt_en.ts (Japanese)
[ci skip]

* New translations pcsx2-qt_en.ts (Korean)
[ci skip]

* New translations pcsx2-qt_en.ts (Lithuanian)
[ci skip]

* New translations pcsx2-qt_en.ts (Dutch)
[ci skip]

* New translations pcsx2-qt_en.ts (Norwegian)
[ci skip]

* New translations pcsx2-qt_en.ts (Polish)
[ci skip]

* New translations pcsx2-qt_en.ts (Portuguese)
[ci skip]

* New translations pcsx2-qt_en.ts (Russian)
[ci skip]

* New translations pcsx2-qt_en.ts (Serbian (Cyrillic))
[ci skip]

* New translations pcsx2-qt_en.ts (Swedish)
[ci skip]

* New translations pcsx2-qt_en.ts (Turkish)
[ci skip]

* New translations pcsx2-qt_en.ts (Ukrainian)
[ci skip]

* New translations pcsx2-qt_en.ts (Chinese Simplified)
[ci skip]

* New translations pcsx2-qt_en.ts (Chinese Traditional)
[ci skip]

* New translations pcsx2-qt_en.ts (Vietnamese)
[ci skip]

* New translations pcsx2-qt_en.ts (Portuguese, Brazilian)
[ci skip]

* New translations pcsx2-qt_en.ts (Indonesian)
[ci skip]

* New translations pcsx2-qt_en.ts (Persian)
[ci skip]

* New translations pcsx2-qt_en.ts (Croatian)
[ci skip]

* New translations pcsx2-qt_en.ts (Latvian)
[ci skip]

* New translations pcsx2-qt_en.ts (Hindi)
[ci skip]

* New translations pcsx2-qt_en.ts (Spanish, Latin America)
[ci skip]
2024-02-01 16:43:56 +00:00
refractionpcsx2 9c9bae7443 GS/HW: Clear draw list when replacing with single sprite 2024-02-01 00:13:39 +00:00
refractionpcsx2 9d69a0ad54 GameDB: Add MipMap to Arc the Lad games for Depth of Field 2024-02-01 00:13:39 +00:00
refractionpcsx2 4b88256df2 GS/HW: Allow palette lookups from depth and deswizzle manual deswizzles 2024-02-01 00:13:39 +00:00
Dan McCarthyandrefractionpcsx2 bc7b0e53f0 Debugger: Adds editing register values via double click
Double clicking a register segment will open the segment edit dialogue that normally has to be accessed by the right click menu.
2024-01-31 14:46:28 +00:00
KamFretoZandrefractionpcsx2 3695862368 Qt/BPM: Minor icon tweaks 2024-01-31 14:45:03 +00:00
KamFretoZandrefractionpcsx2 18ae23b99d InputRec: Stop input recording on VM shutdown 2024-01-31 14:45:03 +00:00
Stenzekandrefractionpcsx2 bb60058fa0 Achievements: Handle fake memory map 2024-01-30 15:26:40 +00:00
Stenzekandrefractionpcsx2 1ad25605d1 GS: Check for D3D Mapping Layers and disable Vulkan if present 2024-01-30 15:26:21 +00:00
Stenzek 9809265be4 GS/Vulkan: Fix potential race between submit and main thread
Backport of https://github.com/stenzek/duckstation/commit/bcf7f55b93d6c61a66c1aabec5b8568d10a5b26c
2024-01-29 12:40:06 +10:00
Stenzek 63872e6b28 GS/Capture: Stop capture on VM shutdown 2024-01-28 21:23:05 +10:00
Stenzek fb3a9eae9b ImGuiOverlays: Use white font for recording time 2024-01-28 21:23:05 +10:00
Stenzek f53c3050df GS/Capture: Show video timestamp instead of wall time 2024-01-28 21:23:05 +10:00
PCSX2 BotandConnor McLaughlin 67bd0dfcdb Qt: Update Base Translation 2024-01-28 21:12:06 +10:00
Stenzek 12196359f7 Qt: Deprecate per-game WS/NI toggles in favor of Patches 2024-01-28 21:09:53 +10:00
Stenzek 609165e412 USB: Fix buffer copies in EyeToy 2024-01-28 17:04:24 +10:00
lightningterror 41f14a8cf8 GameDB: Add mipmap full with ps2 trilinear to SWAT Global Strike Team.
Improves building textures to match sw renderer.
2024-01-28 02:36:46 +01:00
lightningterror a8bdd3fbfc GameDB: Add gshwfixes to Band Hero.
Fixes character shadow misalignment.
Fixes icons.
2024-01-28 00:27:25 +01:00
refractionpcsx2 993ec82a9e GSDumpReplayer: Fix UI not responded to messages when replaying dumps 2024-01-27 16:38:54 +00:00
Stenzek c5a2844367 USB: Fix possible buffer overflow in webcam 2024-01-27 21:26:04 +10:00
Stenzek e5bb405b47 USB: Don't reset configuration when saving state 2024-01-27 21:26:04 +10:00
Stenzek 8c9a65d094 USB: Audio devices don't need port numbers 2024-01-27 21:26:04 +10:00
Stenzek 047b8593ac USB: Fix incorrect config keys for usb-headset 2024-01-27 21:26:04 +10:00
Stenzek 4fc4eb8a66 USB: Rewrite RingBuffer class
It was overflowing and corrupting the heap...
2024-01-27 21:26:04 +10:00
PCSX2 BotandConnor McLaughlin 1f95a86f0a Qt: Update Base Translation 2024-01-27 13:32:21 +10:00
lightningterrorandConnor McLaughlin 2b90451c4a Qt: Fix more compiler warnings.
-Wsign-compare, -Wunused-variable.
2024-01-27 13:30:45 +10:00
Stenzek 77a6525556 Counters: Move input poll to after throttle 2024-01-27 13:29:55 +10:00
refractionpcsx2 5eacab387d GameDB: Add Merge Targets to Shadow Hearts 2024-01-26 12:17:52 +00:00
refractionpcsx2 ddaf57d5b0 GS/HW: Kill old source using target if rect is outside target surface 2024-01-26 12:17:52 +00:00
Stenzek 1144c46109 ImGuiOverlays: Fix OSD margin 2024-01-26 19:47:50 +10:00
Stenzek 850b839fc3 Qt: Make "Ignore Inversion" a global mapping setting
Instead of being specific to DInput.
2024-01-26 18:14:08 +10:00
PCSX2 BotandConnor McLaughlin 22037e75ba Qt: Update Base Translation 2024-01-26 13:04:42 +10:00
TheLastRarandConnor McLaughlin 303ab163ab DEV9: Remove some casts in ATA Info 2024-01-26 13:03:06 +10:00
TheLastRarandConnor McLaughlin 2833537167 DEV9: Various ATA fixes 2024-01-26 13:03:06 +10:00
TheLastRarandConnor McLaughlin 1aa7b591b6 DEV9: LBA48 Support 2024-01-26 13:02:53 +10:00
KamFretoZandConnor McLaughlin 2f0463d936 Qt: Icon Refresh 2024-01-26 13:02:36 +10:00
TellowKrinkleandConnor McLaughlin 82a1ec0c3a Qt: Fix use of tr outside of Qt class 2024-01-26 12:48:37 +10:00
TellowKrinkleandConnor McLaughlin d65133451d Common: Add helper for creating an NSString from a string_view 2024-01-26 12:48:37 +10:00
TellowKrinkleandConnor McLaughlin 36e81949e0 Qt: Use proper Show in Finder on macOS 2024-01-26 12:48:37 +10:00
TellowKrinkleandConnor McLaughlin 6b62945632 CMake: Set qm files as non-source data in Xcode
Prevents their contents from coming up in searches
2024-01-26 12:48:37 +10:00
Stenzek fa2f578900 GS/HW: Handle more double-half clear edge cases
Spiderman: Web of Shadows clears its depth buffer with 32-bit FRAME
and 24-bit Z. So the upper 8 bits of half the depth buffer are not
cleared, yay. We can't turn this into a 32-bit clear, because then
it'll zero out those bits, which other games need (e.g. Jak 2). We
can't do a 24-bit clear, because something might rely on half those
bits actually getting zeroed. So, instead, we toss the depth buffer,
and let the mem clear path write out FRAME and Z separately, with
their associated masks. Limit it to black to avoid false positives.

Also fixes FBW getting blown out to 20 for a horizontal double-half
clear in Wakeboarding Unleashed.
2024-01-26 12:08:29 +10:00
JordanTheToasterandrefractionpcsx2 e1c9987059 GameDB: Hot Shots Golf Fore Fixes 2024-01-26 01:51:00 +00:00
GovanifYandConnor McLaughlin 325e219bb1 PINE: fix save state regression introduced during the switch to Qt
Ensure thread safety when loading/saving state
2024-01-25 20:05:44 +10:00
Dan McCarthyandConnor McLaughlin be208df11b Debugger: Allow copying address of memory search results
Adds the ability to copy the address directly of a memory search result through the right click context menu.
2024-01-25 16:55:43 +10:00
KamFretoZandConnor McLaughlin 49df804d1b FSUI: Add Recording Indicator and Time 2024-01-25 00:05:34 +10:00
JordanTheToasterandConnor McLaughlin 122fde79bd GameDB: Thrillville fixes 2024-01-24 20:07:12 +10:00
Pierre GRASSERandConnor McLaughlin 2099e68c41 GUI: Memory Card "slot" instead of "port"
"Slot" is the official term, we should use it instead of "port".
2024-01-24 20:06:54 +10:00
Dan McCarthyandConnor McLaughlin 52ccc609cd Debugger: Adds loading breakpoints/saved addresses from settings
Adds `/inis/debuggersettings/` settings folder to contain settings specifically for the debugger. Adds functionality to manually save (to settings) Breakpoints/Saved addresses and automatically load them upon launching the debugger.
2024-01-24 20:06:20 +10:00
Stenzek b402f6a404 Qt: Fix unclickable links in Setup Wizard 2024-01-24 17:49:18 +10:00
KamFretoZandConnor McLaughlin 3576a1824d Qt: Remove the duplicate memcard button 2024-01-24 13:47:20 +10:00
Stenzek c3afaae169 Qt: Remove NTFS compression checkbox on Linux/Mac 2024-01-24 13:46:47 +10:00
Stenzek c5a9ce6b56 MemoryCardFile: Log size/formatted state on load 2024-01-24 13:46:47 +10:00
Stenzek 4f0d944bb9 MemoryCardFile: Don't compress file on opening 2024-01-24 13:46:47 +10:00
Stenzek 5684bcb6f4 Qt: Explicitly set memcard compression regardless of choice
Fixes compressed cards being created when the directory has the
compressed flag set (probably from wx..).
2024-01-24 13:46:47 +10:00
JordanTheToasterandrefractionpcsx2 5087fcea88 GameDB: Various fixes 2024-01-23 18:52:09 +00:00
refractionpcsx2 28f984e87f GS/HW: Don't preload target from other targets after hw clear 2024-01-22 20:38:49 +00:00
PCSX2 Botandlightningterror e0233daeb0 PAD: Update to latest controller database. 2024-01-22 19:30:53 +01:00
refractionpcsx2 49922ebe32 GS/HW: Fix up Tekken 5 CRC hack to not remove post + break other scenes 2024-01-21 14:56:54 +00:00
MrlinkwiiandConnor McLaughlin 8fe0c3441c config: remove unused variables 2024-01-21 21:39:07 +10:00
PCSX2 BotandConnor McLaughlin ba55caa404 Qt: Update Base Translation 2024-01-21 19:28:44 +10:00
Stenzek e50e651078 Qt: Warning fix 2024-01-21 19:04:16 +10:00
Stenzek 39b4fd47c7 GS/HW: Fix incorrect page mask in ClearGSLocalMemory() 2024-01-21 19:04:16 +10:00
Stenzek 56b83e5cfa GS/HW: Fix double-half clears misfiring on CT32+Z24 when clearing depth 2024-01-21 19:04:16 +10:00
Stenzek ed57355948 GS/HW: Prevent creating texture with invalid mipmap levels
Fixes crash during loading in Jak with Metal renderer, and UB with
Vulkan.
2024-01-21 19:04:16 +10:00
Stenzek b4b2f3a0b6 GS/Metal: Don't discard alpha on RGB depth copy 2024-01-21 19:03:16 +10:00
TheTechnician27andrefractionpcsx2 1946882952 GameDB: A few trilinear+mipmap games 2024-01-21 09:00:09 +00:00
refractionpcsx2 dbe4930c17 GS: Correct flush reasons 2024-01-20 18:41:06 +00:00
Stenzek 3b25fa3065 GS/HW: Better avoid target uploads on HW clear 2024-01-21 00:49:35 +10:00
ZiemasandConnor McLaughlin 01849565aa SPU: VolumeSlide: disregard phase when exp + decr 2024-01-21 00:05:50 +10:00
Stenzek e3ca5833d0 GS/HW: Fix false positive on shared bits with double-half clears 2024-01-20 23:03:01 +10:00
Stenzek dfac1f034b Qt: Make Shift+Wheel scroll settings info panel 2024-01-20 22:58:22 +10:00
Stenzek a8d07df81d GunCon2: Fix cursor assertions in devbuilds 2024-01-20 22:58:22 +10:00
refractionpcsx2andlightningterror 609cb44b01 GS/HW: Optimize out some situations of texture loading 2024-01-20 06:05:49 +01:00
JordanTheToasterandrefractionpcsx2 feb4d605d0 GameDB: Various fixes 2: Electric Boogaloo
Revert VP2 changes as they cause issues and fix a shadow issue in Star Wars Force Unleashed.
2024-01-19 11:38:54 +00:00
JordanTheToasterandrefractionpcsx2 276d887917 GameDB: Various fixes 2024-01-19 08:27:43 +00:00
PCSX2 Botandlightningterror 61aef9c96f Qt: Update Base Translation 2024-01-18 16:56:06 +01:00
lightningterror b4005682f2 GS/HW: Fix compiler warnings. 2024-01-18 16:41:05 +01:00
Pierre GRASSERandrefractionpcsx2 7d98461fc6 Qt: Remove non-printable character from some strings
This non-printable character (U+FE0F, VARIATION SELECTOR 16) has been added for seemingly no reason to multiple strings and have polluted Translation Memory on Crowdin for some languages (👀).
Let's get rid of it.
2024-01-18 15:02:20 +00:00
Stenzek 862d03b78e FullscreenUI: Link create memory card to Qt dialog 2024-01-18 21:48:22 +10:00
Stenzek 0ecf46240f Qt: Fix OSD/Big Picture display for missing languages 2024-01-18 21:10:52 +10:00
Tyler WildingandGitHub a2755e212b translations: Syncing Crowdin translations (#10638)
* New translations pcsx2-qt_en.ts (Romanian)
[ci skip]

* New translations pcsx2-qt_en.ts (French)
[ci skip]

* New translations pcsx2-qt_en.ts (Spanish)
[ci skip]

* New translations pcsx2-qt_en.ts (Afrikaans)
[ci skip]

* New translations pcsx2-qt_en.ts (Arabic)
[ci skip]

* New translations pcsx2-qt_en.ts (Catalan)
[ci skip]

* New translations pcsx2-qt_en.ts (Czech)
[ci skip]

* New translations pcsx2-qt_en.ts (Danish)
[ci skip]

* New translations pcsx2-qt_en.ts (German)
[ci skip]

* New translations pcsx2-qt_en.ts (Greek)
[ci skip]

* New translations pcsx2-qt_en.ts (Finnish)
[ci skip]

* New translations pcsx2-qt_en.ts (Hebrew)
[ci skip]

* New translations pcsx2-qt_en.ts (Hungarian)
[ci skip]

* New translations pcsx2-qt_en.ts (Italian)
[ci skip]

* New translations pcsx2-qt_en.ts (Japanese)
[ci skip]

* New translations pcsx2-qt_en.ts (Korean)
[ci skip]

* New translations pcsx2-qt_en.ts (Lithuanian)
[ci skip]

* New translations pcsx2-qt_en.ts (Dutch)
[ci skip]

* New translations pcsx2-qt_en.ts (Norwegian)
[ci skip]

* New translations pcsx2-qt_en.ts (Polish)
[ci skip]

* New translations pcsx2-qt_en.ts (Portuguese)
[ci skip]

* New translations pcsx2-qt_en.ts (Russian)
[ci skip]

* New translations pcsx2-qt_en.ts (Serbian (Cyrillic))
[ci skip]

* New translations pcsx2-qt_en.ts (Swedish)
[ci skip]

* New translations pcsx2-qt_en.ts (Turkish)
[ci skip]

* New translations pcsx2-qt_en.ts (Ukrainian)
[ci skip]

* New translations pcsx2-qt_en.ts (Chinese Simplified)
[ci skip]

* New translations pcsx2-qt_en.ts (Chinese Traditional)
[ci skip]

* New translations pcsx2-qt_en.ts (Vietnamese)
[ci skip]

* New translations pcsx2-qt_en.ts (Portuguese, Brazilian)
[ci skip]

* New translations pcsx2-qt_en.ts (Indonesian)
[ci skip]

* New translations pcsx2-qt_en.ts (Persian)
[ci skip]

* New translations pcsx2-qt_en.ts (Croatian)
[ci skip]

* New translations pcsx2-qt_en.ts (Latvian)
[ci skip]

* New translations pcsx2-qt_en.ts (Hindi)
[ci skip]

* New translations pcsx2-qt_en.ts (Spanish, Latin America)
[ci skip]
2024-01-18 20:21:15 +10:00
forrvalhallaandrefractionpcsx2 2ec5559dd6 GameDB: Add HPO Native to SLAI 2024-01-18 09:28:34 +00:00
RedDevilusandrefractionpcsx2 6f904759ee GameDB: Gun Align to Native + typo fix
Looks better with the bloom and character shadows
2024-01-18 09:27:14 +00:00
RedPanda4552andConnor McLaughlin 507fafd601 Pad: Force multitapped slots to Not Connected if their multitap is missing 2024-01-18 14:05:21 +10:00
refractionpcsx2andConnor McLaughlin 9c463f1338 GS/HW: Add DS and Alpha Test checks 2024-01-18 13:33:34 +10:00
Stenzek 8818cd0285 GS/HW: Further improve no_rt heuristics
Reduces copies by almost 500 in Crash and Burn, few hundred drawcall
reductions in other games.
2024-01-18 13:33:34 +10:00
Stenzek 23b72d08d2 ImGuiOverlays: Fix analog input display 2024-01-17 19:02:33 +10:00
Stenzek 227049b6f2 FileSystem: Handle infinite symlink loops in FindFiles() 2024-01-17 13:57:19 +10:00
Stenzek 97abd3e1f9 ImGuiOverlays: Display inputs as integer, ignoring deadzone 2024-01-17 13:56:54 +10:00
Stenzek 6f34b7ba99 Pad: Add GetEffectiveInput()
Returns the value, incorporating any deadzone.
2024-01-17 13:56:54 +10:00
Alessa BakerandConnor McLaughlin c0d86c3852 Adding Jetbrains Rider to Gitignore. 2024-01-17 12:24:15 +10:00
Stenzek bf743ff467 gitignore: Add portable.ini/fix font paths 2024-01-16 22:13:56 +10:00
Connor McLaughlin 306f994464 CI/Linux: Use wildcard for AppImage libc downgrade 2024-01-16 21:43:25 +10:00
TheLastRarandrefractionpcsx2 f56676942b DEV9: Remove SDMA code 2024-01-16 10:45:14 +00:00
TheLastRarandrefractionpcsx2 bfd0a05289 DEV9: Fix ATA MDMA 2024-01-16 10:45:14 +00:00
JordanTheToasterandrefractionpcsx2 3999c08f00 GameDB: Various fixes 2024-01-16 10:43:38 +00:00
TheTechnician27andGitHub febb8ec1a6 GameDB: Fix water in Just Cause (#10645) 2024-01-16 10:29:31 +00:00
RedPanda4552andConnor McLaughlin cdd38ef7aa Game List: Ignore exclusion paths if they are empty string
Works around a bug of unknown origin which causes empty string to be added to the exclusions list, clearing the games list completely.
2024-01-16 18:38:18 +10:00
Stenzek 54c620a5df CI: Default to non-portable builds 2024-01-16 13:13:02 +10:00
Martino FontanaandConnor McLaughlin 089b7fa73e Build: Remove unused option DISABLE_BUILD_DATE 2024-01-16 13:12:20 +10:00
PCSX2 Botandlightningterror d9d0e84ca1 PAD: Update to latest controller database. 2024-01-15 17:17:01 +01:00
refractionpcsx2 badede2e2d GS/HW: ignore lower 3 bits of 16bit color for AEM check 2024-01-15 14:45:00 +00:00
Stenzek e2ae28741d Qt: Look for a file named DefaultUpdaterChannel.h
Make manual releases create this file, and make it contain

  #define DEFAULT_UPDATER_CHANNEL "stable"

So that the release defaults to the stable update channel.
2024-01-15 15:35:21 +10:00
Stenzek ff34150b15 Build: Simplify Git version extraction
Get rid of SysForwardDefs.h
Use last known git tag to determine version info, if it is not a tagged commit.
2024-01-15 15:35:21 +10:00
Tyler Wilding 4a93ceac58 Update Crowdin configuration file 2024-01-14 14:18:03 -05:00
JordanTheToasterandrefractionpcsx2 5a724bf49e GameDB: Flower sun and rain fixes 2024-01-14 17:40:58 +00:00
Connor McLaughlinandGitHub c23bcf6322 Qt: Remove debug popup after update cleanup 2024-01-14 18:36:01 +10:00
PCSX2 BotandConnor McLaughlin c863149ad9 Qt: Update Base Translation 2024-01-14 18:07:44 +10:00
Connor McLaughlin 5fb6e22bed Qt: Invoke Updater as Administrator for Program Files installs 2024-01-14 18:03:10 +10:00
refractionpcsx2 d634088282 GS/HW: Don't mark 24bit alpha as valid on upgrade 2024-01-14 06:56:57 +00:00
Stenzek c99a935831 CDVD: Fix block dump path generation 2024-01-14 15:23:25 +10:00
Stenzek 165da677ed Qt: Use native path separators for block dump 2024-01-14 15:23:25 +10:00
Stenzek 0bbde2ca52 IPU: Fix regression from #10617 2024-01-14 15:22:43 +10:00
Stenzek 3d13c5d13c CI: Allow user to override deps build path 2024-01-14 14:05:16 +10:00
Stenzek 9d978c67ec GS/DX11: Remove unused field 2024-01-14 12:51:51 +10:00
Stenzek 8766e1fa57 GS: Fix enum type return warnings 2024-01-14 12:51:51 +10:00
Stenzek c0e4883987 GS: Make GSVertex POD
Same code is generated with optimization (256-bit store in AVX2).
2024-01-14 12:51:51 +10:00
Stenzek 57f2cd5f9e GS: Make GSDrawingContext and GSDrawingEnvironment POD 2024-01-14 12:51:51 +10:00
Stenzek 50d8d06a94 GS: Make vector types POD
Generates the same code in Release, but slightly difference code in Debug.
In Debug, with the default copy assignment operator, it turns into two
8-byte moves. With the overloaded operator, it turns into a function call
with a vector move.
2024-01-14 12:51:51 +10:00
Stenzek 2e07328878 GS: Skip vertex append for GS_INVALID PRIM 2024-01-14 12:51:51 +10:00
Stenzek ad8294e521 GS: Remove unused vertex types 2024-01-14 12:51:51 +10:00
Stenzek 14426a7b45 GSCapture: Fix invalid substring comparison 2024-01-14 12:51:51 +10:00
Stenzek 6697e76be1 x86/iR5900: Fix signed/unsigned comparison in timeout loop 2024-01-14 12:51:51 +10:00
Stenzek 73cd876f6d SPU2: Make StereoOut16/32 POD 2024-01-14 12:51:51 +10:00
Stenzek e626754000 IPU: Remove last parameter from IDCT_Add
It was always being used uninitialized.
2024-01-14 12:51:51 +10:00
Stenzek 1fedd31229 HW: Make various DMAC/IPU/VIF register types POD 2024-01-14 12:51:51 +10:00
Stenzek bfe1746ddb Cache: Make CacheTag/CacheData POD 2024-01-14 12:51:51 +10:00
PCSX2 BotandConnor McLaughlin 4800c32db4 Qt: Update Base Translation 2024-01-13 12:27:29 +10:00
KamFretoZandConnor McLaughlin 140fc28b3e FSUI: Allow toggling fullscreen when VM is paused 2024-01-13 12:27:05 +10:00
Stenzek f9833bb8af Qt: Tidy up Tools menu
Make System Console and Verbose contingent on Advanced Settings being enabled.
Make Debug Console contingent on actually running under a debugger.
2024-01-13 12:26:34 +10:00
TheTechnician27andConnor McLaughlin 4b6ddaf09e Update VMManager.cpp to not count paused time toward time played
Hopefully resolves #10497. Between each update to played time, we calculate the sum of the deltas between VM resume and pause in order to subtract that off from the total session (paused and unpaused) session time.

Thus, if someone pauses the emulator, that time spent paused will no longer be reflected in the GameList.
2024-01-12 22:52:34 +10:00
Stenzek d427db4a15 GS/HW: Fix drawing with mismatched target sizes 2024-01-12 19:40:30 +10:00
Stenzek deb39cc20f CMake: Set LINUX in current and parent scope 2024-01-12 19:21:31 +10:00
Stenzek 3a4c25e916 VMManager: Force TimeStretch in Achievements HC Mode 2024-01-12 19:20:38 +10:00
f416eb3d4f Translation\UI: Updates Bitrate "Kbps" to "kpbs"
Co-Authored-By: KamFretoZ <14798312+kamfretoz@users.noreply.github.com>
2024-01-12 15:59:15 +10:00
Dan McCarthyandConnor McLaughlin 87cbb1f90c Settings: Adds description to 'All CRCs' on hover for cheat/patch settings
Add an on hover description to explain the purpose of the 'All CRCs' button in the Cheat and Patch Settings window.
2024-01-12 15:59:15 +10:00
Stenzek 68f43121fb MTGS: Explicitly set FP control register on startup
Linux appears? to inherit the value of MXCSR from the creating thread,
and that breaks parts of the software renderer.
2024-01-12 15:23:31 +10:00
Stenzek 51ceab1f3c PINE: Convert from class to namespace
Fewer global objects, indirect includes via headers.
2024-01-12 12:57:28 +10:00
Stenzek 2257992a3f PINE: Tidy up and fix shutdown hang on Linux 2024-01-12 12:57:28 +10:00
TellowKrinkleandConnor McLaughlin 40ead584d7 GS:MTL: Properly handle hdr rendering to cleared textures 2024-01-12 12:51:50 +10:00
PCSX2 BotandConnor McLaughlin 6f2be7bd17 Qt: Update Base Translation 2024-01-12 12:51:24 +10:00
KamFretoZandConnor McLaughlin af9ca4cf7d 3rdparty: Add source for modified PromptFont that we use for OSD 2024-01-12 12:44:43 +10:00
Stenzek ea98203ac5 Qt: Add log window 2024-01-12 12:40:46 +10:00
Stenzek e908bbfae6 Qt: Add accessors for UI thread view of current game 2024-01-12 12:40:46 +10:00
Stenzek fa00069068 Console: Remove WX rubbish and replace 2024-01-12 12:40:46 +10:00
refractionpcsx2 efae58de52 GS/HW: Don't bother trying to draw empty draws 2024-01-11 14:43:04 +00:00
refractionpcsx2 39c7f703cb GameDB: Remove GS fixes from Ty The Tasmanian Tiger 2024-01-11 13:52:53 +00:00
refractionpcsx2 96220e509c GSDumpRunner: Fix dump runner 2024-01-11 13:52:53 +00:00
refractionpcsx2 4de495ad59 GS/HW: Upgrade target to C32 if alpha requested on src is rt 2024-01-11 13:52:53 +00:00
refractionpcsx2 56a950a074 GS/HW: Update cached source alpha if linked to target 2024-01-11 12:46:23 +00:00
forrvalhallaandGitHub 2b47254bdd GameDB: Set HPO Native to Fast and Furious (#10595) 2024-01-11 08:20:56 +00:00
Tyler WildingandGitHub da28e2cc37 CI: fix flatpak cron and add a way to manually kick off a release (#10567)
* CI: fix mistake stopping flatpak cron from running

* CI: add a way to manually kick off releases with a custom tag

* cmake: handle edge-case where a commit can have multiple tags

* CI: set release channel appropriately when it's a stable release
2024-01-10 19:04:42 -05:00
refractionpcsx2 3d64f4a548 GameDB: Merge sprite to remove blur on Sakura Taisen - Atsuki Chishioni 2024-01-10 15:50:33 +00:00
refractionpcsx2 abf03b0c79 GS/HW: Detected striped moves in HW 2024-01-10 15:50:33 +00:00
refractionpcsx2 8381742422 GS: Flush draw when address matches FRAME/Z 2024-01-10 15:43:06 +00:00
Stenzek 724bb09aec GS/HW: Don't rely on old targets for double-half clear 2024-01-10 19:36:49 +10:00
Stenzek ad5de248b4 Qt: Fix incorrect renderer shown in status bar 2024-01-10 19:35:26 +10:00
AKuHAKandrefractionpcsx2 1a1fdf6a5a DEV9: various fixes for ATA Identify
improved 48-bit addressing
UDMA mode now set correctly
MDMA0 mode now set correctly
fixed user addressable sectors limit
2024-01-09 21:39:27 +00:00
Stenzekandrefractionpcsx2 c469b17350 GS/HW: Fix incorrect resizing of Ico move target 2024-01-09 19:50:51 +00:00
refractionpcsx2 01842a3c6b GS/HW: Allow blending on normal shuffles 2024-01-09 13:17:52 +00:00
Berylskidandrefractionpcsx2 4cd385dbff GameDB: Fix broken shadows in Armored Core Last Raven 2024-01-09 11:56:31 +00:00
GovanifYandrefractionpcsx2 72787d103f PINE: fix regressions introduced in #10448 2024-01-08 22:01:24 +00:00
PCSX2 Botandlightningterror 59072272a9 PAD: Update to latest controller database. 2024-01-08 17:06:21 +01:00
Stenzek ef76666e5a Misc: Warning fixes 2024-01-08 23:33:43 +10:00
Stenzek 0a0994b19c Qt: Fix docs file not getting included on Linux/Mac 2024-01-08 23:33:43 +10:00
psykoseandConnor McLaughlin ab68c570a9 Debugger: initialise SavedAddress.address with 0 instead of NULL 2024-01-08 20:44:26 +10:00
JordanTheToasterandrefractionpcsx2 dcdf3b39a3 GameDB: Various fixes 2024-01-08 10:38:53 +00:00
Stenzek 476cb2db34 Qt: Add third-party license statements 2024-01-07 22:20:43 +10:00
Stenzek 34a7e00413 GameDB: Use chop rounding for division in GT4 NTSC-J 2024-01-07 21:42:18 +10:00
Stenzek 413ba09ee0 Config: Replace FpuNegDivHack with eeDivRoundMode 2024-01-07 21:42:18 +10:00
KamFretoZandrefractionpcsx2 33eefefffb Qt: Update Base Translation 2024-01-07 10:44:12 +00:00
KamFretoZandrefractionpcsx2 b3c822e31b Qt: Minor Translation Fixes 2024-01-07 10:44:12 +00:00
Connor McLaughlinandGitHub 14a458a275 Qt: Fix HTTP download error format string 2024-01-07 20:14:01 +10:00
JordanTheToasterandConnor McLaughlin 6c49f9cf52 GameDB: DMC 1 Fixes 2024-01-07 18:40:47 +10:00
Stenzek 803ea4dda5 Qt: Update base translation 2024-01-07 18:35:50 +10:00
Stenzek c9b23e6e4e Qt: Fix opening game properties causing patch reload 2024-01-07 18:35:50 +10:00
Stenzek 509c9318af Qt: Fix missing font message not being translatable 2024-01-07 18:35:50 +10:00
Stenzek 46e1e3d904 Qt: Add runtime downloading of CJK OSD fonts 2024-01-07 17:23:57 +10:00
Stenzek 7a045d837b Qt: Add runtime resource downloading 2024-01-07 17:23:57 +10:00
Stenzek 574ea820f7 Config: Add UserResources to EmuFolders
Allowing some resources, such as fonts/sounds to be overridden by the
user.
2024-01-07 17:23:57 +10:00
Stenzek 7061d48dde SDLInputSource: Replace hint strings with macros 2024-01-07 16:19:29 +10:00
Stenzek 53633b7279 Qt: Fix columns in cheat tree list not auto-sizing 2024-01-07 16:03:37 +10:00
Stenzek 05bb126af9 Patch: Fix ungrouped patches from >1 file not merging 2024-01-07 16:03:37 +10:00
Stenzek 436fba77ac SDLInputSource: Log axes/buttons when controller is opened 2024-01-07 14:07:53 +10:00
Ty LamontagneandConnor McLaughlin 91c0e64159 Misc: Enable some disabled warnings
smite you windows for keeping long 32 bits
2024-01-07 13:19:14 +10:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Tyler Wilding
7207681485 CI: migrate to upload-artifact@v4, download-artifact@v4 and labeler@v5 (#10431)
* Bump actions/upload-artifact from 3 to 4

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* Bump actions/download-artifact from 3 to 4

Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 3 to 4.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v3...v4)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>

* github: group github actions updates going forward

* ci: update to labeler@v5

* ci: properly differentiate between windows build artifacts on PRs

* ci: workaround flatpak-builder temporarily as well as test fix

* ci: simplify things, just explicitly specify the artifact names in the workflows

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Tyler Wilding <xtvaser@gmail.com>
2024-01-06 15:44:44 -05:00
KamFretoZandrefractionpcsx2 eef53f1b54 Qt: Minor Translation Fixes 2024-01-06 16:03:19 +00:00
KamFretoZandrefractionpcsx2 150d8d3214 Qt: Add Popn icon to Input OSD 2024-01-06 16:03:19 +00:00
JordanTheToasterandrefractionpcsx2 a14376abb6 GameDB: Various fixes 2024-01-06 15:58:00 +00:00
KamFretoZandrefractionpcsx2 dfb45a8ebc GameDB: Fixes for Bully 2024-01-06 15:56:51 +00:00
Dan McCarthyandConnor McLaughlin 00137c3031 Misc: Uses Qt::UserRole for hardcoded literals & const changes
Adds const to variables that could use them but currently don't and replaces 256 literal uses with Qt::UserRole to be clear what it is for and how this number is used as currently it's a bit confusing.
2024-01-06 13:17:39 +10:00
Dan McCarthyandConnor McLaughlin b9c7dacbd1 Debugger: Double clicking Memory Search result switches to memory view tab
Previously when double clicking a memory search result it would go to the address but not switch to the memory view tab if the user wasn't already on it.
Now it ensures the user moves to the memory view widget, as this is almost always going to be the user's intention.
2024-01-06 13:17:39 +10:00
Dan McCarthyandConnor McLaughlin 3a03b579d2 Debugger: Add Saved Addresses tab widget for bookmarking mem addresses
Adds a tab widget to the debugger that allows saving/bookmarking memory addresses and giving them labels/descriptions for convenience. Includes the ability to jump back to memory addresses from the Saved Addresses tab, and adding Saved Addresses from memory search search results context menu and the memory view context menu.

Also supports importing and exporting the saved addresses as CSV format.
2024-01-06 13:17:39 +10:00
lightningterror e2bbe5cd8b SIO: Fix Wunused-variable compiler warnings. 2024-01-05 14:30:40 +01:00
PCSX2 Botandlightningterror c7347a3a16 Qt: Update Base Translation 2024-01-05 14:30:18 +01:00
Stenzek 8749b3c782 Patch: Fix last duplicate patch group not being ignored 2024-01-05 22:32:11 +10:00
RedPanda4552andConnor McLaughlin 10389dc3f1 Pad: Add Pop'n Music controller type 2024-01-05 16:44:36 +10:00
Stenzek 5e191a6cd8 GameDB: Add fullblend/autoflush/tex-in-RT for Catwoman 2024-01-05 13:55:18 +10:00
Stenzek a247625095 GS/HW: Handle offset RTs when offset texture is detected
Catwoman draws to the target at 2E60 via 2D00, one page back
horizontally, and one vertically.

The Destroy All Humans fix also works here, but it only applied to the
texture, not the RT. So do a very conservative form of RT-in-RT to
handle Catwoman.

Unfortunately nothing else benefits from it.
2024-01-05 13:55:18 +10:00
refractionpcsx2 0fb234f3e4 Linux: Stop OpenGL overriding Vulkan if both VK and OpenGL are enabled 2024-01-05 00:03:09 +00:00
Dan McCarthyandrefractionpcsx2 e3e35d3bc6 Misc: Add patches folder to gitignore
Adds Patches folder to git ignore so that changing branches/stashing/unstashing does not impact patches files in repository folder.
This matches how the cheats folder is handled.
2024-01-04 23:29:19 +00:00
Dan McCarthyandrefractionpcsx2 6b12ca5b39 CheatsSettings/PatchSettings: Add toggle for showing All CRCs
This used to be off by default, but was changed to show found patches/cheats for all CRCs for a game serial. However, this also clutters and shows cheats for CRCs that may not be applicable without a way to hide them.

This puts this setting behind a toggle to choose if this should be enabled/disabled so the user has choice. Still defaults to enabled to keep current behavior.
2024-01-04 23:29:19 +00:00
PCSX2 Botandlightningterror 44e2837efb Qt: Update Base Translation 2024-01-04 23:50:13 +01:00
Ty Lamontagneandrefractionpcsx2 27dcd1bba2 Core: Remove unused variable
Caused from my previous commit. oops
2024-01-04 21:31:47 +00:00
Ty Lamontagneandrefractionpcsx2 ebf8cba6a4 Core: Remove syscon log and merge with EE logs 2024-01-04 21:10:37 +00:00
Christian Kennyandrefractionpcsx2 e06cf23a03 Readme: Update and Remove Redundant Info
This PR updates the readme to be more concise. Much of the old information has been moved over to the new documentation site and no longer needs to be duplicated.
2024-01-04 20:31:09 +00:00
Dan McCarthyandrefractionpcsx2 dd55887f4b Debugger: Add support for Not Equals Array filter searches
Adds the ability to do Not Equals filter searches for arrays and strings. Refactors array search function for easily adding new search types.

Adds error message that informs the user that Not Equals can not be used for initial searches and can only be used for filter searches.
2024-01-04 20:24:38 +00:00
Dan McCarthyandrefractionpcsx2 39b7b29af8 Debugger: Combine Start & End addresses to 1 line & add comparison label
- Combines the start and end address inputs for memory search into a single line to not take up unnecessary space and allow room for other UI elements.

- Adds 'Comparison' label to memory search UI to describe the comparison input dropdown.
2024-01-04 20:24:38 +00:00
Dan McCarthyandrefractionpcsx2 6f0aef3344 Settings: Clearing settings disables Cheats/Patches
The 'Clear Settings' button will now disable all cheats and patches from the configuration. This will allow quickly resetting to having no game altering pnach patches/cheats.
2024-01-04 20:23:18 +00:00
Mrlinkwiiandrefractionpcsx2 85c3a413fa GameDB: remove Knights Of The Temple 2 CRC 2024-01-04 20:19:52 +00:00
Mrlinkwiiandrefractionpcsx2 b6c365b89d HW/CRC: remove Knights Of The Temple 2 CRC hacks 2024-01-04 20:19:52 +00:00
Stenzek 9eedf3901a GS: Fix autoflush init after renderer switch 2024-01-04 15:27:59 +10:00
Mrlinkwiiandrefractionpcsx2 2a6ecaeb09 GameDB: fixes for Mary-Kate & Ashley - Sweet Sixteen - Licensed to Drive 2024-01-02 22:03:10 +00:00
JordanTheToasterandrefractionpcsx2 027db2480a GameDB: Rock Band Track Pack Volume 2 Fixes
Fixes for complete lack of visuals in menus and game.
2024-01-02 10:02:25 +00:00
KamFretoZandrefractionpcsx2 f4cabca62d Qt: More Translation Fixes 2024-01-02 07:48:23 +00:00
ElTioRataandrefractionpcsx2 0b3edc5fd4 GameDB: 007 - Agent Under Fire
Recommends 'Full' blending setting to fix lighting issues
2024-01-02 07:40:39 +00:00
PCSX2 Botandlightningterror b05152da0f PAD: Update to latest controller database. 2024-01-01 17:22:34 +01:00
Ty LamontagneandConnor McLaughlin 2e0988ff03 IOP HLE: Fix broken dopen implementation 2024-01-01 04:07:21 +10:00
Silentandrefractionpcsx2 ec12d9b657 PCAP: Fix a regression from #10448 2023-12-31 12:19:50 +00:00
Stenzek f388de26ab GS: Refactor renderer switching
- Fix automatic renderer causing delay when changing settings.
 - Make the Debug -> Switch Renderer menu actually save.
2023-12-31 19:17:43 +10:00
Stenzek 68df8bf8ea GS/DX11: Ignore two irrelevant warnings 2023-12-31 16:19:03 +10:00
Stenzek 4a960bba5e GS/DX11: Fix crash with debug device 2023-12-31 16:19:03 +10:00
Stenzek 3a72b3fe3f GS/HW: Fix crash with custom textures 2023-12-31 16:19:03 +10:00
Stenzek 47f372f215 Achievements: Wrap long achievement descriptions 2023-12-31 13:40:32 +10:00
Stenzek 7dc93bee7e ImGuiFullscreen: Add LayoutUnscale() 2023-12-31 13:40:32 +10:00
Stenzek 69ff64149a Achievements: Display missable/progression/win icons 2023-12-31 13:40:32 +10:00
Stenzek 4795f0a7dd 3rdparty/rcheevos: Bump to 3cadf84 2023-12-31 13:40:32 +10:00
Stenzek e9411a3db5 Resources: Add achievement icons to PromptFont
By @kamfretoz.
2023-12-31 13:40:32 +10:00
Stenzek 6a064a4fa5 GS/HW: Add descriptive labels to RTs/textures 2023-12-31 13:09:54 +10:00
Stenzek f036cdaaad GS: Move texture object labels to base class 2023-12-31 13:09:54 +10:00
Stenzek 46a68e2118 GS: Purge GSTextureSW
Everything uses hardware device textures now.
2023-12-31 13:09:54 +10:00
Stenzek d020ea8f63 GS/SW: Remove use of GSTextureSW for dumping 2023-12-31 13:09:54 +10:00
Stenzek a4b40ab9e1 GS: Purge GSTexture::Swap()
Was only used for texture replacements, and that can just be a pointer
swap instead.
2023-12-31 13:09:54 +10:00
Stenzek 7c62b86ed9 CMake: Move unsupported compiler warning to end 2023-12-31 13:08:03 +10:00
Stenzek 96fb49e35c 3rdparty/libzip: Hardcode most checks
We are only targetting a limited number of platforms.

Significantly speeds up generation time with Xcode.
2023-12-31 13:08:03 +10:00
UserandConnor McLaughlin 8e5fc236ca CMake: Fix building x86 on Apple Silicon 2023-12-31 13:08:03 +10:00
Stenzek 0784b5930b CMake: Use shared libwebp, liblz4, and libzstd on Linux/Mac 2023-12-31 13:08:03 +10:00
Stenzek 6de792c465 CI: Add xz/zstd/lz4/webp to Mac dependencies
Also fix building on Apple Silicon, and allow the destination path to be
specified, instead of being hardcoded to $HOME/deps.
2023-12-31 13:08:03 +10:00
SilentandConnor McLaughlin e9a2c89930 GamePatchSettingsWidget: Enable word wrap for long patch names/descriptions 2023-12-31 13:05:02 +10:00
PCSX2 BotandConnor McLaughlin 9d9f3acbe6 Qt: Update Base Translation 2023-12-31 13:03:27 +10:00
Stenzek b67126e917 GS/HW: Use DECAL/TCC for GT4 render fix
That way the channel gets actually written to alpha.
2023-12-31 12:06:08 +10:00
Stenzek bcbf390334 GS/HW: Don't discard alpha in C32 targets being used as C24 2023-12-31 12:06:08 +10:00
KamFretoZandConnor McLaughlin 714e355c87 Qt/Translations: Hopefully filled the remainder of still missing description 2023-12-30 22:55:49 +10:00
KamFretoZandConnor McLaughlin f258ea2906 Tools: Fix glyph range generator script 2023-12-30 22:55:49 +10:00
KamFretoZandConnor McLaughlin 9445018586 Qt: Use more fitting icon for the cover download FSUI 2023-12-30 22:55:49 +10:00
TellowKrinkleandConnor McLaughlin 97310b8912 GS:MTL: Give labels to textures 2023-12-30 19:11:30 +10:00
JordanTheToasterandrefractionpcsx2 47073040d4 GameDB: Various fixes 2023-12-30 05:50:58 +00:00
Stenzek a93663af0b VMManager: Fix disc change path message 2023-12-30 15:24:28 +10:00
Dan McCarthyandGitHub ecd3b87cc0 Debugger: Fix Importing Breakpoints CSV functionality (#10486)
* Debugger: Fix Importing Breakpoints CSV functionality

This resolves a few issues with the Breakpoint CSV importing functionality.
Makes the following changes/fixes:
-  Assembly instructions with commas caused part of the instruction text to be considered the start of a new column (comma is the CSV delimiter), resulting in "too many columns errors" and failing to import. Now puts values in quotes and detects the true start and end of the values, so that no extra columns are created.
- The import function was using the incorrect columns to load since the Enabled column was moved to be the first one but the import function had not been updated. Fixed this by using the column enum values instead of hardcoded numbers, so if ordering in the model changes it will still import fine.
- Updates the beginRemoveRows function to not remove an extra row. With this function you would want a "count" of 0 to remove 1, so "row, row" deletes one, "row, row+1" deletes 2. Updates it to subtract one so that the range is not including one more than it is intended to.
-

* Misc: Cleanup regex string & add explicit imports

Uses raw string literal to make the regular expression easier to read since regex is ugly enough as it is, we really don't need escaping characters in a string on top.

Also explicitly includes used Qt classes.
2023-12-30 14:01:30 +10:00
PCSX2 BotandConnor McLaughlin 5c34f208bd Qt: Update Base Translation 2023-12-30 14:00:42 +10:00
Stenzek 47f8d8c71c GS: Use templates for shift immediates
Also removes the __m128 overloads - it's too easy to mistake for a
variable shift (which doesn't exist in SSE4), instead it takes the shift
amount from the lowest 32-bits.
2023-12-30 14:00:03 +10:00
Stenzek b2a0dba3bb GS/SW: Replace non-constant shift immediate 2023-12-30 14:00:03 +10:00
Stenzek fc1304dff5 GS/SW: Work around not being able to use non-constant offsetof 2023-12-30 14:00:03 +10:00
Stenzek 0e15de7103 CMake: Tidy up platform/architecture detection 2023-12-30 14:00:03 +10:00
psykoseandConnor McLaughlin 182375314e DEV9: fix some missing includes and nonportable u_long type use 2023-12-30 13:05:39 +10:00
星云nebulasandGitHub 69ac4ddedc GameDB: Add some NTSC-C game's official Simplified Chinese names 2023-12-30 00:03:19 +00:00
JordanTheToasterandrefractionpcsx2 68b31347f4 GameDB: Ace Combat fixes
Hopefully this will be the last time I have to do this but knowing my luck.
2023-12-29 17:04:39 +00:00
refractionpcsx2 6be2d1cbc0 GS/HW: Fix some target resize/clear handling 2023-12-29 05:33:19 +00:00
refractionpcsx2 5fac15c449 GS/HW: Include expansion of new targets in preload
Also potentially avoids a copy
2023-12-29 05:33:09 +00:00
refractionpcsx2 ecb86b01c8 GS/TC: Fix invalidation when start page is negative 2023-12-29 05:33:09 +00:00
refractionpcsx2 1744a6ffeb GS/TC: Fix invalidation bug when translating P8->C32 2023-12-29 05:33:09 +00:00
refractionpcsx2 fb393c22b6 GS/HW: Improve target lookup handling + fix alpha requirements 2023-12-29 05:33:09 +00:00
Stenzek fe2ed74020 GS: Use DX11/GL for Auto when no compatible Vulkan devices exist 2023-12-29 15:29:54 +10:00
Stenzek 5f51050b2f GS/Vulkan: Ignore GPUs that don't meet the required extensions 2023-12-29 15:29:54 +10:00
refractionpcsx2 913253d861 GS/TC: Remove old targets when changing width and outside a page 2023-12-28 14:57:22 +00:00
AKuHAKandConnor McLaughlin 8172b2e5ed DEV9: Add support for external HDD ID.
This commit introduces the capability to read the content of a *.hddid file located in the same directory as the HDD image (same name, different extension). The retrieved content is then used to fill the SCE_IDENTIFY_DRIVE response.
2023-12-28 22:19:19 +10:00
AKuHAKandConnor McLaughlin 0a7fc06510 R5900: fix ParseArgumentString for strings with spaces in the beginning 2023-12-28 22:18:20 +10:00
AKuHAKandConnor McLaughlin a81b20d9d5 QT: Add ability to pass launch arguments with CLI option -gameargs 2023-12-28 22:18:20 +10:00
Stenzek 90a09eb520 GameDB: Remove mention of full clamp for GT4 2023-12-28 22:15:29 +10:00
Stenzek 4ffbe0bbf9 x86/FPU: Always preserve sign in neg.s 2023-12-28 22:15:29 +10:00
Stenzek 8f991c311f GameList: Sanitize all cover paths 2023-12-28 18:35:28 +10:00
Stenzek 367f839934 Qt: Fix game properties dialog not activating 2023-12-28 18:35:11 +10:00
PCSX2 BotandConnor McLaughlin 6c15359137 Qt: Update Base Translation 2023-12-28 14:02:38 +10:00
Stenzek fa05d656ad VMManager: Simplify CPU heuristics for MTVU default 2023-12-28 14:02:14 +10:00
KamFretoZandConnor McLaughlin 76e32f55de Qt: Hide advanced graphics settings by default.
To show it, enable "Show Advanced Settings" option first.
2023-12-28 13:34:06 +10:00
KamFretoZandConnor McLaughlin 30ca79a23a Qt/Translation: Make the input setting value for Analog Sensitivity Consistent with Description 2023-12-28 13:34:06 +10:00
KamFretoZandConnor McLaughlin 0ce6961972 gitignore: Don't include KDevelop 4 workspace configs 2023-12-28 13:34:06 +10:00
KamFretoZandConnor McLaughlin b0ae2caef8 Qt/Translation: Attempt to fill in missing descriptions in settings 2023-12-28 13:34:06 +10:00
JordanTheToasterandrefractionpcsx2 3eb4a64517 GameDB: Various fixes
Fixes for HC size in Ace Combat and FMVs now working fine without SoftwareFMV in COD 2
2023-12-28 01:23:23 +00:00
PCSX2 Botandlightningterror 61a80b59d1 Qt: Update Base Translation 2023-12-27 23:28:50 +01:00
refractionpcsx2 71eaef35e3 GSDumpRunner: Fix dump runner not starting 2023-12-27 19:10:47 +00:00
Connor McLaughlinandGitHub b7ae453d84 Common: Fix duplicate fastjmp on Windows/CMake 2023-12-27 16:03:49 +10:00
Stenzek 33958fed96 Qt: Fix AutoUpdaterDialog building 2023-12-27 14:34:48 +10:00
Stenzek 59d29b3648 Common: Rename General to HostSys
Actually fits what it's doing.
2023-12-27 13:55:35 +10:00
Stenzek 911d7f6533 Common: Move janky macros to Config.h 2023-12-27 13:55:35 +10:00
Stenzek 9d49015c0c Common: Purge pxEnum macros 2023-12-27 13:55:35 +10:00
Stenzek d585712b40 GIF: Comment out packet register assertion
Triggers on "normal" register writes.
2023-12-27 13:55:35 +10:00
Stenzek fb15893521 VMManager: Remove and merge System.cpp 2023-12-27 13:55:35 +10:00
Stenzek 308f8c5112 x86emitter: Abstract MXCSR into a general FPControlRegister type 2023-12-27 13:55:35 +10:00
Stenzek 86edc0dbe2 x86emitter: Make SSE_MXCSR not dependent on emitter 2023-12-27 13:55:35 +10:00
Stenzek f461bc9176 x86emitter: Purge x86caps
We can use cpuinfo for querying AVX/AVX2.
2023-12-27 13:55:35 +10:00
Stenzek b121e5af25 x86emitter: Purge MXCSR mask
We require SSE4, no need to mask away things that are unsupported by
SSE1.
2023-12-27 13:55:35 +10:00
Riley011andrefractionpcsx2 d292835941 GameDB: Add a couple more dothack title adjustments 2023-12-26 14:58:02 +00:00
Stenzek 88aee00a02 GS/Vulkan: Disable threaded presentation when spin is enabled 2023-12-26 14:14:15 +10:00
Jakeyandrefractionpcsx2 2fa806bbee GameDB: Recommended blending for Silent Hill: Origins.
Recommend full blending to fix grey text.
2023-12-26 03:54:09 +00:00
refractionpcsx2 ae3faa1540 GS/HW: Only copy valid channels for copied channels in hw move 2023-12-26 03:51:33 +00:00
RileyandGitHub e1e9c428f1 GameDB: Fix dothack titles (#10477) 2023-12-26 01:52:37 +00:00
PCSX2 Botandlightningterror 443ca9da56 Qt: Update Base Translation 2023-12-25 19:50:45 +01:00
JordanTheToasterandrefractionpcsx2 ef05da1860 GameDB: Various fixes
Gamer
2023-12-25 12:54:06 +00:00
refractionpcsx2 e5a87c7463 GS/HW: further Z copy improvements and reverse shuffle coords 2023-12-25 11:49:40 +00:00
refractionpcsx2 da2f419b86 GS/HW: Fix some behaviour when target is copied from Z 2023-12-25 11:49:40 +00:00
refractionpcsx2 4586a12859 GS/HW: Correct some channel shuffle behaviour for alpha shuffles 2023-12-25 11:49:40 +00:00
Stenzek 9c416bdbc1 Qt: Patch macdeployqt to always deploy Qt SVG imageformat plugin
This seems silly, but since we don't have a direct reference to
QtSvg, it doesn't deployed directly from the main binary
(only indirectly from iconengines), and the libqsvg.dylib imageformat
plugin does not get deployed.

Patch macdeploy to remove the Svg check, ensuring the dylib is always
deployed.

Fixes controller images not being displayed in Mac builds.
2023-12-25 18:05:41 +10:00
Stenzek f43255a1fb MSBuild: Don't supply PrecompiledHeader.h to moc
Redundant, since it's force included anyway.
2023-12-25 12:39:08 +10:00
Stenzek 59949c50d3 CMake: Don't compile PrecompiledHeader.cpp 2023-12-25 12:39:08 +10:00
Stenzek 4608579c31 GIF: Get rid of assertion with side effects 2023-12-24 23:12:12 +10:00
Stenzek 8b7c69c4b0 GS/HW: Further improve GT4 render fix
Fixes transitions in Prologue version, and the coloured strip when
starting normal races.
2023-12-24 22:01:32 +10:00
Stenzek 0d519a5f28 x86/FPU: Use SSE4 pminsd+pminud for sign clamping 2023-12-24 22:01:23 +10:00
Stenzek 47ae3ff8d3 SDLInputSource: Optionally load game_controller_db.txt from data dir 2023-12-24 15:52:56 +10:00
refractionpcsx2 97b7d7c7b9 GameDB: Add merge targets for Wave Rally 2023-12-24 04:19:39 +00:00
refractionpcsx2 c552d717ff GS/HW: Only update dirty if intersects + fix tex is rt regions 2023-12-24 04:19:39 +00:00
Stenzek 3230287a5f UnitTests: Hopefully fix random failures based on load address 2023-12-24 14:03:14 +10:00
Stenzek 0bc9c7ffa1 Common: Replace x86_intrin.h with generic Intrin.h
For later Apple Silicon support.
2023-12-24 14:03:14 +10:00
Stenzek d9abe10308 Misc: Remove explicit PCH include, switch to SPDX 2023-12-24 14:03:14 +10:00
Stenzek 3f62e7d36f Qt: Force include PrecompiledHeader.h for MSBuild 2023-12-24 14:03:14 +10:00
Stenzek 47a65ce01e Misc: Slim down PCH 2023-12-24 14:03:14 +10:00
Stenzek cd6590e15c Misc: Fix clang warnings 2023-12-24 14:03:14 +10:00
Stenzek 87d392adb5 Common: Don't include x86_intrin.h globally 2023-12-24 14:03:14 +10:00
Stenzek 1dac754ca3 Misc: Purge OFFSETOF 2023-12-24 14:03:14 +10:00
Stenzek b844bb1268 Misc: Simplify platform/compiler macros 2023-12-24 14:03:14 +10:00
Stenzek dc859ca0a6 Misc: Simplify assertion macros 2023-12-24 14:03:14 +10:00
Stenzek 20e75b4057 GS: Replace ASSERT() with pxAssert() 2023-12-24 14:03:14 +10:00
Stenzek a69977c62b Misc: Always use C++20 constinit 2023-12-24 14:03:14 +10:00
Stenzek 3b0513a57c Misc: Purge _M_AMD64 define 2023-12-24 14:03:14 +10:00
Stenzek a5c2ea679d TextureDecompress: Remove non-SSE code paths 2023-12-24 14:03:14 +10:00
Stenzek 0dd5e7c081 GS: Move privileged register dump to GSRenderer.cpp 2023-12-24 14:03:14 +10:00
Stenzek 31e9206fc0 Common: Purge unused macros 2023-12-24 14:03:14 +10:00
Stenzek 4bf7d2b2fe Common: Remove macro checks for unsupported platforms 2023-12-24 14:03:14 +10:00
Stenzek c8b051628f Misc: Replace likely/unlikely with C++20 equivalents 2023-12-24 14:03:14 +10:00
Stenzek 76f36b780c StringUtil: Replace StartsWith/EndsWith with C++20 equivalents 2023-12-24 14:03:14 +10:00
Stenzek d73d698fd5 FileSystem: Purge GetDisplayNameFromPath()
Redundant function.
2023-12-24 14:03:14 +10:00
Stenzek 9967d5ca9e CDVD: Simplify compressed ISO detection 2023-12-24 14:03:14 +10:00
refractionpcsx2 fdc0370cdf Perfmon: Fix statistics for GS dump runner to ignore idle frames 2023-12-23 19:17:01 +00:00
refractionpcsx2 a51a46f61a GS/HW: Fix texture shuffles with reversed co-ords 2023-12-23 19:17:01 +00:00
refractionpcsx2 3edd508fc8 PerfMon: Fix statistics for games with idle frames 2023-12-23 19:17:01 +00:00
refractionpcsx2 18fea6440e DumpRunner: Fix script handling of paths containing square brackets 2023-12-23 18:40:38 +00:00
refractionpcsx2 3a242b513c GS/PCRTC/HW: Correct height of framebuffer reads 2023-12-23 18:40:38 +00:00
Dan McCarthyandGitHub ade6a6c3ab Debugger: Add memory search types: GreaterThan(OrEqual), LesserThan(OrEqual), and Not Equal (#10441)
* Make memory search search type handling more clear with enum

Adds an enum class to represent the Search type used in a memory search. Prior, this was just handled with an integer to represent each type, but it was very unclear what corresponded to which type at first glance.

Made this easier to follow by using an enum to represent the type.

* Debugger : Add support for greater than/less than/not equal search types

Adds support for basic greater than/greater than or equal/less than/less than or equal/not equal search types for the debugger's Memory Scan.

This adds a new input to allow selecting the search comparison type, which defaults to Equals, and allows switching to the above mentioned comparisons.
It's set up to allow for adding more easily. Restructures some of the functions to make having multiple comparisons quite manageable.
Adds an enum for search comparison types for easy logic handling.

* Debugger: Update Array/String search type error to mention not handling Not Equals

Currently array/string searches don't support Not Equals searches, so this needs to be removed.

* Debugger: Code cleanup + feedback changes

Sets up if expressions to use constexpr for compile time evaluation and makes the is greater/less than logic simpler to read for int. Also removes an unneeded QPushButton cast and simply compares the pointers directly.
2023-12-23 19:02:48 +10:00
JordanTheToasterandConnor McLaughlin 9740ebe2a4 GameDB: Various fixes 2023-12-23 17:55:16 +10:00
SilentandConnor McLaughlin 0a2e137613 DiscordRPC: Show session time in Discord Rich Presence 2023-12-23 17:54:58 +10:00
refractionpcsx2 56056c4846 GS/HW: Fix source partial preload alpha tracking behaviour 2023-12-23 03:29:06 +00:00
Stenzek b32d96636d GS/HW: Always mark fetched/created depth targets as used
Same as colour targets. Fixes half screen after cutscenes in Merceneries
- Playground of Destruction.
2023-12-23 13:01:27 +10:00
Stenzek 9a4094e997 GS/Vulkan: Fix incorrect layout in depth feedback loops
Was setting the incorrect texture slot as dirty.
2023-12-23 13:01:27 +10:00
refractionpcsx2 6a0bbea9c5 GS/HW: Fix 16bit depth conversion in shuffles + fix NFSU CRC 2023-12-22 19:33:41 +00:00
PCSX2 BotandConnor McLaughlin fea213ee2c Qt: Update Base Translation 2023-12-21 13:40:51 +10:00
AKuHAKandrefractionpcsx2 669cadf1ee DEV9: fix incorrect max hdd size
PS2 support 28-bit addressing not the the 24-bit addressing. 24-bit addressing is limited by 8Gb storage, while 28-bit is limited by 128Gb
2023-12-20 22:22:55 +00:00
RedPanda4552andrefractionpcsx2 c3bafa2a40 Memcard: Fix terminator not properly flagging ejections 2023-12-20 09:58:35 +00:00
refractionpcsx2 2611a93af2 MCD: Correctly wait 60 frames for eject timeout 2023-12-19 19:02:11 +00:00
KamFretoZandrefractionpcsx2 651e7e5963 OSD/FSUI: Adjust PromptFont for more readability 2023-12-19 15:38:48 +00:00
refractionpcsx2 7bf18a4464 DEV9: Implement a slightly less hacky (incomplete) DVE reg set 2023-12-19 15:14:35 +00:00
Stenzekandrefractionpcsx2 14fd42ad91 Core/Memory: Properly reset memory on reset
[SAVEVERSION+]
2023-12-19 15:14:35 +00:00
refractionpcsx2 33a61558e1 Core: Improve determinism on reset + default fast boot interlaced 2023-12-19 15:14:35 +00:00
RedPanda4552andrefractionpcsx2 05ed785af1 Memcard: Remove option to disable auto eject 2023-12-18 21:06:58 +00:00
PCSX2 Botandlightningterror 158193a6c4 PAD: Update to latest controller database. 2023-12-18 19:23:29 +01:00
Stenzek 2671da5c2f GS/HW: Don't crash on texture allocation failure 2023-12-18 18:19:13 +10:00
Stenzek a99a819e75 Qt: Fix game properties dialogs keeping app open 2023-12-18 14:12:57 +10:00
KamFretoZandrefractionpcsx2 94664c24d8 GameDB: Fixes for Boku no Natsuyasumi 2 2023-12-17 16:41:55 +00:00
JordanTheToasterandrefractionpcsx2 466e96e79a GameDB: Sims 2 Castaway fixes
Fixes dastardly lines appearing on the ground at certain viewing angles.
2023-12-17 16:41:40 +00:00
Stenzek fd2cd0b904 ImGui: Fix save state selector crash in DX11 2023-12-17 22:06:55 +10:00
Stenzek 4afe14dff7 Qt: Fix settings window focusing 2023-12-17 19:59:05 +10:00
Stenzek 9436a823ba Qt: Fix incorrect translation context preventing settings switch 2023-12-17 19:59:05 +10:00
Stenzek 1346c98a58 GS: Autodetect Vulkan for Intel Xe GPUs 2023-12-17 19:35:18 +10:00
lightningterror 93992ee1c1 ImGui: Fix Wsign-compare compiler warning. 2023-12-16 14:35:36 +01:00
lightningterror e29bc933fe FullscreenUI: Fix Wunused-function compiler warning. 2023-12-16 14:35:36 +01:00
lightningterror 6df5cd8222 GS: Fix compiler warnings.
GSDeviceVK:
Fixes Wlogical-op-parentheses warning.

GSTextureCAche:
Fixes Wunused-lambda-capture warning.

GSRendererHW:
Fixes Wlogical-op-parentheses warning.
2023-12-16 14:35:36 +01:00
Stenzek 2dfc75c341 GS/DX12: Fix a warning during PrimID DATE pipeline creation 2023-12-16 21:04:52 +10:00
refractionpcsx2 e75a1edea8 GameDB: Add VU1 clamping to Dino Stalker/Gun Survivor 3 2023-12-16 03:02:03 +00:00
PCSX2 Botandlightningterror 8c94efd61a Qt: Update Base Translation 2023-12-16 01:33:46 +01:00
Ty Lamontagneandrefractionpcsx2 20c3178dfe Debugger: Don't modify disassembly focus on CPU state change 2023-12-15 20:35:06 +00:00
refractionpcsx2 a7271697a2 CDVD: Adjust abort behaviour on action. 2023-12-15 16:12:26 +00:00
Stenzekandrefractionpcsx2 c2bd21922f GS: Fix more lambda capture warnings 2023-12-15 15:42:08 +00:00
lightningterror 96831b0970 GS/D3D: Fix X3206 CAS shader warning.
Warning X3206: implicit truncation of vector type warning fix.
2023-12-15 15:40:31 +01:00
refractionpcsx2 0fd5db9f78 GS: Add local references to Move lambda function 2023-12-15 12:57:14 +00:00
refractionpcsx2 918328e5b0 GS/TC: Copy only up to old RT size on preload overlap 2023-12-15 12:57:14 +00:00
lightningterror 6bb6322bde GameDB: Remove Merge sprite from God Hand.
It breaks the lighting in the game making it dark, changing the Shadeboost is not a good solution and just masks the issue.
2023-12-15 11:59:42 +01:00
00e255ee3c CDVD: Add ZSO support (#10396)
* add zso support

* format and fixed typo

* fix typo in extension (duplicated .cso)

* format

* proper casting

* use regular casting; added lz4 to gitmodules

* use C++ style casting

* fix casts

* add lz4 submodule

* added windows build configuration

* add lz4 to cmake

* undo

* undo

* add lz4 to SearchForStuff

* undo

* add own lz4 source code

* cleanup

* fix

* add nwe sources to windows build

* cleanup

* don't use precompile headers on lz4

* stupid compiler

* add const. better logging.

* cast to std::string

* 3rdparty: Add lz4

* use 3rdparty lz4

* cleanup references to lz4.cpp

* format code

* add missing header

* use fmt::format

* don't call inflateReset on ZSO

* use LZ4_decompress_safe

* fix syntax

* fix call to LZ4_decompress_safe

* use LZ4_decompress_safe_partial to ignore padded data

* cleanup

* refactor

---------

Co-authored-by: Stenzek <stenzek@gmail.com>
2023-12-15 13:05:04 +10:00
Stenzek c662dd8b04 ImGuiFullscreen: Add hover animation 2023-12-15 13:04:03 +10:00
Stenzek a85a246ab8 ImGui: Add save state selector UI 2023-12-15 13:04:03 +10:00
Mrlinkwiiandrefractionpcsx2 9132a7c8d4 GameDB: Add monster hunter memcard filters 2023-12-14 23:40:45 +00:00
Connor McLaughlinandGitHub 44367f24f9 Pad: Fix incorrect RDown icon for DualShock2 2023-12-14 17:54:28 +10:00
Mrlinkwiiandrefractionpcsx2 5d6a9a68fe GameDB: memcardFilters for Ratchet&Clank series 2023-12-13 19:51:01 +00:00
JordanTheToasterandConnor McLaughlin 476d15f0fc GameDB: Various fixes
You like HPO?
2023-12-13 16:53:01 +10:00
Stenzek cce7f465f2 CI/AppImage: Bump libc6 downgrade version 2023-12-13 14:51:06 +10:00
ElTioRataandConnor McLaughlin 0e289d2d3b GameDB: Darkwatch Upscaling Fix
Sets HPO to Special setting to fix bloom misalignment seen on blood vision
2023-12-13 14:50:39 +10:00
ElTioRataandrefractionpcsx2 efc9321cec GameDB: RPM Tuning bloom alignment fix
Enables Full Round Sprite + Wild Arms Hack + Autoflush
2023-12-12 21:42:55 +00:00
PCSX2 BotandConnor McLaughlin 4154784f09 Qt: Update Base Translation 2023-12-12 12:55:30 +10:00
RedPanda4552andConnor McLaughlin feb9d7b2a9 Memcard/Qt/Big Picture: Make shutdowns, resets, disc swaps, and savestates aware of memcard busy status 2023-12-12 12:47:39 +10:00
RedPanda4552andConnor McLaughlin da22df5f5d Memcard: Move auto eject countdown out of CDVD up into counters vsync loop 2023-12-12 12:47:39 +10:00
Stenzek 78f751959d GS/HW: Don't try to double-buffer preload depth targets
Spec violations if we do.
2023-12-12 12:46:39 +10:00
Stenzek b0c3a1e23f GS/HW: Handle GT4 alpha channel shuffle 2023-12-12 12:46:39 +10:00
Mrlinkwiiandrefractionpcsx2 cb703a6ce5 GameDB : Upscaling fixes for X-Men 2 - Wolverine's Revenge 2023-12-11 20:22:28 +00:00
1231 changed files with 252673 additions and 157583 deletions
+1 -2
View File
@@ -67,11 +67,10 @@ body:
- Windows 11
- Windows 10 (64bit)
- Linux (64bit) - Specify distro below
- macOS 14 (Sonoma)
- macOS 13 (Ventura)
- macOS 12 (Monterey)
- macOS 11 (Big Sur)
- macOS 10.15 (Catalina)
- macOS 10.14 (Mojave)
validations:
required: true
- type: input
+1 -2
View File
@@ -82,11 +82,10 @@ body:
- Windows 11
- Windows 10 (64bit)
- Linux (64bit) - Specify distro below
- macOS 14 (Sonoma)
- macOS 13 (Ventura)
- macOS 12 (Monterey)
- macOS 11 (Big Sur)
- macOS 10.15 (Catalina)
- macOS 10.14 (Mojave)
validations:
required: true
- type: input
+4
View File
@@ -6,3 +6,7 @@ updates:
schedule:
# Check for updates to GitHub Actions every week
interval: "weekly"
groups:
ci-deps:
patterns:
- "*"
+147 -87
View File
@@ -2,127 +2,187 @@
# General Labels
'Build | Project System':
- '.github/*'
- '.github/**/*'
- '*.sln'
- '**/*.sln'
- '*.vcxproj*'
- '**/*.vcxproj*'
- 'cmake/*'
- 'cmake/**/*'
- 'CMakeLists.txt'
- '**/CMakeLists.txt'
- 'build.sh'
- changed-files:
- any-glob-to-any-file:
- '.github/*'
- '.github/**/*'
- '*.sln'
- '**/*.sln'
- '*.vcxproj*'
- '**/*.vcxproj*'
- 'cmake/*'
- 'cmake/**/*'
- 'CMakeLists.txt'
- '**/CMakeLists.txt'
- 'build.sh'
'Dependencies':
- '3rdparty/*'
- '3rdparty/**/*'
- '**/3rdpartyDeps.props'
- '.gitmodules'
- changed-files:
- any-glob-to-any-file:
- '3rdparty/*'
- '3rdparty/**/*'
- '**/3rdpartyDeps.props'
- '.gitmodules'
'Documentation':
- '*.md'
- '**/*.md'
- '*.pdf'
- '**/*.pdf'
- changed-files:
- any-glob-to-any-file:
- '*.md'
- '**/*.md'
- '*.pdf'
- '**/*.pdf'
'GUI/Qt':
- 'pcsx2-qt/*'
- 'pcsx2-qt/**/*'
- '3rdparty/Qt/*'
- '3rdparty/Qt/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2-qt/*'
- 'pcsx2-qt/**/*'
- '3rdparty/Qt/*'
- '3rdparty/Qt/**/*'
'GameDB':
- '**/GameIndex.*'
- changed-files:
- any-glob-to-any-file:
- '**/GameIndex.*'
'Installer | Package':
- 'build.sh'
- changed-files:
- any-glob-to-any-file:
- 'build.sh'
'Translations':
- 'pcsx2-qt/Translations/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2-qt/Translations/*'
# Tools / Features
'Debugger':
- 'pcsx2/DebugTools/*'
- 'pcsx2/DebugTools/**/*'
- 'pcsx2-qt/Debugger/*'
- 'pcsx2-qt/Debugger/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/DebugTools/*'
- 'pcsx2/DebugTools/**/*'
- 'pcsx2-qt/Debugger/*'
- 'pcsx2-qt/Debugger/**/*'
'IPC':
- 'pcsx2/IPC*'
- 'pcsx2/**/IPC*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/IPC*'
- 'pcsx2/**/IPC*'
'TAS Functionality':
- 'pcsx2/Recording/*'
- 'pcsx2/Recording/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/Recording/*'
- 'pcsx2/Recording/**/*'
'RetroAchievements':
- 'pcsx2/Frontend/Achievements.*'
- 'pcsx2/Achievements.*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/Frontend/Achievements.*'
- 'pcsx2/Achievements.*'
# Emulation Components
'Counters':
- 'pcsx2/Counters.*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/Counters.*'
'Vector Units':
- 'pcsx2/VU*'
- 'pcsx2/**/VU*'
- 'pcsx2/*VU*'
- 'pcsx2/**/*VU*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/VU*'
- 'pcsx2/**/VU*'
- 'pcsx2/*VU*'
- 'pcsx2/**/*VU*'
'VIF':
- 'pcsx2/Vif*'
- 'pcsx2/**/Vif*'
- 'pcsx2/VIF*'
- 'pcsx2/**/VIF*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/Vif*'
- 'pcsx2/**/Vif*'
- 'pcsx2/VIF*'
- 'pcsx2/**/VIF*'
# GS Related Labels
'GS':
- 'pcsx2/GS/*'
- 'pcsx2/GS/**/*'
- 'bin/resources/shaders/*'
- 'bin/resources/shaders/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/*'
- 'pcsx2/GS/**/*'
- 'bin/resources/shaders/*'
- 'bin/resources/shaders/**/*'
'GS: Direct3D 11':
- 'pcsx2/GS/Renderers/DX11/*'
- 'pcsx2/GS/Renderers/DX11/**/*'
- 'bin/resources/shaders/dx11/*'
- 'bin/resources/shaders/dx11/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/DX11/*'
- 'pcsx2/GS/Renderers/DX11/**/*'
- 'bin/resources/shaders/dx11/*'
- 'bin/resources/shaders/dx11/**/*'
'GS: Direct3D 12':
- 'pcsx2/GS/Renderers/DX12/*'
- 'pcsx2/GS/Renderers/DX12/**/*'
- 'bin/resources/shaders/dx11/*'
- 'bin/resources/shaders/dx11/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/DX12/*'
- 'pcsx2/GS/Renderers/DX12/**/*'
- 'bin/resources/shaders/dx11/*'
- 'bin/resources/shaders/dx11/**/*'
'GS: Hardware':
- 'pcsx2/GS/Renderers/HW/*'
- 'pcsx2/GS/Renderers/HW/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/HW/*'
- 'pcsx2/GS/Renderers/HW/**/*'
'GS: OpenGL':
- 'pcsx2/GS/Renderers/OpenGL/*'
- 'pcsx2/GS/Renderers/OpenGL/**/*'
- 'bin/resources/shaders/opengl/*'
- 'bin/resources/shaders/opengl/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/OpenGL/*'
- 'pcsx2/GS/Renderers/OpenGL/**/*'
- 'bin/resources/shaders/opengl/*'
- 'bin/resources/shaders/opengl/**/*'
'GS: Vulkan':
- 'pcsx2/GS/Renderers/Vulkan/*'
- 'pcsx2/GS/Renderers/Vulkan/**/*'
- 'bin/resources/shaders/vulkan/*'
- 'bin/resources/shaders/vulkan/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/Vulkan/*'
- 'pcsx2/GS/Renderers/Vulkan/**/*'
- 'bin/resources/shaders/vulkan/*'
- 'bin/resources/shaders/vulkan/**/*'
'GS: Metal':
- 'pcsx2/GS/Renderers/Metal/*'
- 'pcsx2/GS/Renderers/Metal/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/Metal/*'
- 'pcsx2/GS/Renderers/Metal/**/*'
'GS: Texture Cache':
- 'pcsx2/GS/Renderers/*TextureCache*.*'
- 'pcsx2/GS/Renderers/**/*TextureCache*.*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/*TextureCache*.*'
- 'pcsx2/GS/Renderers/**/*TextureCache*.*'
'GS: Software':
- 'pcsx2/GS/Renderers/SW/*'
- 'pcsx2/GS/Renderers/SW/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/GS/Renderers/SW/*'
- 'pcsx2/GS/Renderers/SW/**/*'
# Other Core Components
'CDVD':
- 'pcsx2/CDVD/*'
- 'pcsx2/CDVD/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/CDVD/*'
- 'pcsx2/CDVD/**/*'
'DEV9':
- 'pcsx2/DEV9/*'
- 'pcsx2/DEV9/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/DEV9/*'
- 'pcsx2/DEV9/**/*'
'IPU':
- 'pcsx2/IPU/*'
- 'pcsx2/IPU/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/IPU/*'
- 'pcsx2/IPU/**/*'
'Memory Card':
- 'pcsx2/SIO/Memcard/*'
- 'pcsx2/SIO/Memcard/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/SIO/Memcard/*'
- 'pcsx2/SIO/Memcard/**/*'
'PAD':
- 'pcsx2/SIO/Pad/*'
- 'pcsx2/SIO/Pad/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/SIO/Pad/*'
- 'pcsx2/SIO/Pad/**/*'
'SPU2':
- 'pcsx2/SPU2/*'
- 'pcsx2/SPU2/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/SPU2/*'
- 'pcsx2/SPU2/**/*'
'USB':
- 'pcsx2/USB/*'
- 'pcsx2/USB/**/*'
- changed-files:
- any-glob-to-any-file:
- 'pcsx2/USB/*'
- 'pcsx2/USB/**/*'
@@ -33,6 +33,7 @@ jobs:
uses: ./.github/workflows/linux_build_flatpak.yml
with:
jobName: "Qt"
artifactPrefixName: "PCSX2-linux-Qt-x64-flatpak"
compiler: clang
cmakeflags: ""
publish: true
+47 -25
View File
@@ -6,6 +6,9 @@ on:
jobName:
required: true
type: string
artifactPrefixName:
required: true
type: string
os:
required: false
type: string
@@ -36,11 +39,18 @@ on:
required: false
type: boolean
default: false
stableBuild:
required: false
type: boolean
default: false
jobs:
build_linux:
name: ${{ inputs.jobName }}
runs-on: ${{ inputs.os }}
container:
image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.6
options: --privileged
timeout-minutes: 60
steps:
@@ -48,6 +58,12 @@ jobs:
uses: actions/checkout@v4
with:
submodules: recursive
set-safe-directory: ${{ env.GITHUB_WORKSPACE }}
# Work around container ownership issue
- name: Set Safe Directory
shell: bash
run: git config --global --add safe.directory "*"
# Hackity hack. When running the workflow on a schedule, we don't have the tag,
# it doesn't fetch tags, therefore we don't get a version. So grab them manually.
@@ -56,68 +72,61 @@ jobs:
if: ${{ inputs.fetchTags }}
run: git fetch --tags --no-recurse-submodules
- name: Add stable release identifier file
if: ${{ inputs.stableBuild == true || inputs.stableBuild == 'true' }}
shell: bash
run: |
echo "#define DEFAULT_UPDATER_CHANNEL \"stable\"" > ./pcsx2-qt/DefaultUpdaterChannel.h
cat ./pcsx2-qt/DefaultUpdaterChannel.h
- name: Prepare Artifact Metadata
id: artifact-metadata
shell: bash
env:
OS: linux
BUILD_SYSTEM: flatpak
ARCH: ${{ inputs.platform }}
PREFIX: ${{ inputs.artifactPrefixName }}
EVENT_NAME: ${{ github.event_name }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_NUM: ${{ github.event.pull_request.number }}
PR_SHA: ${{ github.event.pull_request.head.sha }}
run: ./.github/workflows/scripts/common/name-artifacts.sh
- name: Install Packages
env:
COMPILER: ${{ inputs.compiler }}
run: .github/workflows/scripts/linux/install-packages-flatpak.sh
- name: Download patches
run: |
cd bin/resources
aria2c -Z "${{ inputs.patchesUrl }}/patches.zip"
wget "${{ inputs.patchesUrl }}/patches.zip"
- name: Generate AppStream XML
run: |
./.github/workflows/scripts/linux/generate-metainfo.sh .github/workflows/scripts/linux/flatpak/net.pcsx2.PCSX2.metainfo.xml
cat .github/workflows/scripts/linux/flatpak/net.pcsx2.PCSX2.metainfo.xml
flatpak run org.freedesktop.appstream-glib validate .github/workflows/scripts/linux/flatpak/net.pcsx2.PCSX2.metainfo.xml
flatpak-builder-lint appstream .github/workflows/scripts/linux/flatpak/net.pcsx2.PCSX2.metainfo.xml
- name: Validate manifest
run: |
flatpak run --command=flatpak-builder-lint org.flatpak.Builder manifest .github/workflows/scripts/linux/flatpak/net.pcsx2.PCSX2.json
flatpak-builder-lint manifest .github/workflows/scripts/linux/flatpak/net.pcsx2.PCSX2.json
- name: Build Flatpak
uses: flatpak/flatpak-github-actions/flatpak-builder@v6.3
uses: flathub-infra/flatpak-github-actions/flatpak-builder@23796715b3dfa4c86ddf50cf29c3cc8b3c82dca8
with:
bundle: ${{ steps.artifact-metadata.outputs.artifact-name }}.flatpak
upload-artifact: false
manifest-path: .github/workflows/scripts/linux/flatpak/net.pcsx2.PCSX2.json
arch: x86_64
build-bundle: true
verbose: true
mirror-screenshots-url: https://dl.flathub.org/repo/screenshots
mirror-screenshots-url: https://dl.flathub.org/media
branch: ${{ inputs.branch }}
cache: true
restore-cache: true
cache-key: ${{ inputs.os }} ${{ inputs.platform }} ${{ inputs.compiler }} flatpak ${{ hashFiles('.github/workflows/scripts/linux/flatpak/**/*.json') }}
- name: Commit screenshots to OSTree
- name: Validate build
run: |
ostree commit --repo=repo --canonical-permissions --branch=screenshots/x86_64 flatpak_app/screenshots
#- name: Validate build directory
# run: |
# flatpak run --command=flatpak-builder-lint org.flatpak.Builder builddir flatpak_app
#
#- name: Validate repo
# run: |
# flatpak run --command=flatpak-builder-lint org.flatpak.Builder repo repo
flatpak-builder-lint repo repo
- name: Push to Flathub beta
if: inputs.publish == true && inputs.branch == 'beta'
uses: flatpak/flatpak-github-actions/flat-manager@v6.3
uses: flathub-infra/flatpak-github-actions/flat-manager@23796715b3dfa4c86ddf50cf29c3cc8b3c82dca8
with:
flat-manager-url: https://hub.flathub.org/
repository: beta
@@ -125,9 +134,22 @@ jobs:
- name: Push to Flathub stable
if: inputs.publish == true && inputs.branch == 'stable'
uses: flatpak/flatpak-github-actions/flat-manager@v6.3
uses: flathub-infra/flatpak-github-actions/flat-manager@23796715b3dfa4c86ddf50cf29c3cc8b3c82dca8
with:
flat-manager-url: https://hub.flathub.org/
repository: stable
token: ${{ secrets.FLATHUB_TOKEN }}
# NOTE - this is done after on purpose so the flatpak file is wherever it needs to be for the previous pushes
- name: Prepare artifacts folder
# NOTE - 'flatpak-builder' dumps the artifact out into the current directory
run: |
mkdir -p "$GITHUB_WORKSPACE"/ci-artifacts/
mv "./${{ steps.artifact-metadata.outputs.artifact-name }}.flatpak" "$GITHUB_WORKSPACE"/ci-artifacts/
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: ci-artifacts
+2
View File
@@ -15,6 +15,7 @@ jobs:
uses: ./.github/workflows/linux_build_qt.yml
with:
jobName: "AppImage Build"
artifactPrefixName: "PCSX2-linux-Qt-x64-appimage-sse4"
compiler: clang
cmakeflags: ""
buildAppImage: true
@@ -25,6 +26,7 @@ jobs:
uses: ./.github/workflows/linux_build_flatpak.yml
with:
jobName: "Flatpak Build"
artifactPrefixName: "PCSX2-linux-Qt-x64-flatpak-sse4"
compiler: clang
cmakeflags: ""
publish: false
+18 -5
View File
@@ -6,6 +6,9 @@ on:
jobName:
required: true
type: string
artifactPrefixName:
required: true
type: string
os:
required: false
type: string
@@ -36,6 +39,10 @@ on:
required: false
type: boolean
default: false
stableBuild:
required: false
type: boolean
default: false
jobs:
build_linux:
@@ -57,7 +64,7 @@ jobs:
run: |
sudo rm -f /etc/apt/sources.list.d/ubuntu-toolchain-r-ubuntu-test-jammy.list
sudo apt-get update
sudo apt-get install -y --allow-downgrades libc6=2.35-0ubuntu3.4 libc6-dev=2.35-0ubuntu3.4 libstdc++6=12.3.0-1ubuntu1~22.04 libgcc-s1=12.3.0-1ubuntu1~22.04
sudo apt-get install -y --allow-downgrades 'libc6=2.35-0ubuntu*' 'libc6-dev=2.35-0ubuntu*' libstdc++6=12.3.0-1ubuntu1~22.04 libgcc-s1=12.3.0-1ubuntu1~22.04
- name: Checkout Repository
uses: actions/checkout@v4
@@ -69,12 +76,18 @@ jobs:
if: ${{ inputs.fetchTags }}
run: git fetch --tags --no-recurse-submodules
- name: Add stable release identifier file
if: ${{ inputs.stableBuild == true || inputs.stableBuild == 'true' }}
shell: bash
run: |
echo "#define DEFAULT_UPDATER_CHANNEL \"stable\"" > ./pcsx2-qt/DefaultUpdaterChannel.h
cat ./pcsx2-qt/DefaultUpdaterChannel.h
- name: Prepare Artifact Metadata
id: artifact-metadata
shell: bash
env:
OS: linux
ARCH: ${{ inputs.platform }}
PREFIX: ${{ inputs.artifactPrefixName }}
EVENT_NAME: ${{ github.event_name }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_NUM: ${{ github.event.pull_request.number }}
@@ -107,7 +120,7 @@ jobs:
- name: Build Dependencies
if: steps.cache-deps.outputs.cache-hit != 'true'
run: .github/workflows/scripts/linux/build-dependencies-qt.sh
run: .github/workflows/scripts/linux/build-dependencies-qt.sh "$HOME/deps"
- name: Download patches
run: |
@@ -142,7 +155,7 @@ jobs:
- name: Upload artifact
if: inputs.buildAppImage == true
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: ci-artifacts
+20 -5
View File
@@ -6,6 +6,9 @@ on:
jobName:
required: true
type: string
artifactPrefixName:
required: true
type: string
os:
required: false
type: string
@@ -18,6 +21,10 @@ on:
required: false
type: boolean
default: false
stableBuild:
required: false
type: boolean
default: false
jobs:
build_macos:
@@ -44,14 +51,21 @@ jobs:
if: ${{ inputs.fetchTags }}
run: git fetch --tags --no-recurse-submodules
- name: Use Xcode 14.3.1
run: sudo xcode-select -s /Applications/Xcode_14.3.1.app
- name: Add stable release identifier file
if: ${{ inputs.stableBuild == true || inputs.stableBuild == 'true' }}
shell: bash
run: |
echo "#define DEFAULT_UPDATER_CHANNEL \"stable\"" > ./pcsx2-qt/DefaultUpdaterChannel.h
cat ./pcsx2-qt/DefaultUpdaterChannel.h
- name: Use Xcode 15.2
run: sudo xcode-select -s /Applications/Xcode_15.2.app
- name: Prepare Artifact Metadata
id: artifact-metadata
shell: bash
env:
OS: macos
PREFIX: ${{ inputs.artifactPrefixName }}
EVENT_NAME: ${{ github.event_name }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_NUM: ${{ github.event.pull_request.number }}
@@ -79,7 +93,8 @@ jobs:
- name: Build Dependencies
if: steps.cache-deps.outputs.cache-hit != 'true'
run: .github/workflows/scripts/macos/build-dependencies.sh
run: |
.github/workflows/scripts/macos/build-dependencies.sh "$HOME/deps"
- name: Download patches
run: |
@@ -148,7 +163,7 @@ jobs:
cp "${{ steps.artifact-metadata.outputs.artifact-name }}.tar.xz" ci-artifacts/macOS.tar.xz
- name: Upload Artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: "*.tar.xz"
+1
View File
@@ -15,4 +15,5 @@ jobs:
uses: ./.github/workflows/macos_build.yml
with:
jobName: "MacOS Build"
artifactPrefixName: "PCSX2-macos-Qt"
secrets: inherit
+35 -16
View File
@@ -10,16 +10,19 @@ on:
push:
branches:
- master
# TODO - future work
# workflow_dispatch:
# inputs:
# isStable:
# description: 'Should it be a stable release?'
# required: true
# default: 'false'
# versionTag:
# description: 'The version to tag with'
# required: true
workflow_dispatch:
inputs:
is_prelease:
description: 'Should be a pre-release?'
required: true
default: 'true'
type: choice
options:
- 'true'
- 'false'
tag_value:
description: 'Create a new release from latest master with the given tag, if this is left blank it will bump the patch version. You dont need to include the "v" prefix'
required: false
permissions:
contents: write
@@ -42,6 +45,8 @@ jobs:
github_token: ${{ github.token }}
tag_prefix: v
default_bump: patch
# if set, it will overwrite the bump settings
custom_tag: ${{ github.event.inputs.tag_value == '' && null || github.event.inputs.tag_value }}
# TODO - we could do this and remove the node.js script, but auto-generated notes only work
# with PRs -- not commits (determine how much we care).
@@ -62,9 +67,18 @@ jobs:
node index.js
mv ./release-notes.md ${GITHUB_WORKSPACE}/release-notes.md
- name: Create a GitHub Release
- name: Create a GitHub Release (Manual)
uses: softprops/action-gh-release@v1
if: steps.tag_version.outputs.new_tag
if: steps.tag_version.outputs.new_tag && github.event_name == 'workflow_dispatch'
with:
body_path: ./release-notes.md
draft: true
prerelease: ${{ github.event_name != 'workflow_dispatch' || inputs.is_prelease == 'true' }}
tag_name: ${{ steps.tag_version.outputs.new_tag }}
- name: Create a GitHub Release (Push)
uses: softprops/action-gh-release@v1
if: steps.tag_version.outputs.new_tag && github.event_name != 'workflow_dispatch'
with:
body_path: ./release-notes.md
draft: true
@@ -81,10 +95,12 @@ jobs:
uses: ./.github/workflows/linux_build_qt.yml
with:
jobName: "AppImage Build"
artifactPrefixName: "PCSX2-linux-Qt-x64-appimage"
compiler: clang
cmakeflags: ""
buildAppImage: true
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
secrets: inherit
build_linux_flatpak:
@@ -95,11 +111,13 @@ jobs:
uses: ./.github/workflows/linux_build_flatpak.yml
with:
jobName: "Flatpak Build"
artifactPrefixName: "PCSX2-linux-Qt-x64-flatpak"
compiler: clang
cmakeflags: ""
branch: "stable"
publish: false
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
secrets: inherit
# Windows
@@ -111,10 +129,12 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "Windows Build"
artifactPrefixName: "PCSX2-windows-Qt-x64"
configuration: CMake
buildSystem: cmake
cmakeFlags: -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
secrets: inherit
# MacOS
@@ -126,7 +146,9 @@ jobs:
uses: ./.github/workflows/macos_build.yml
with:
jobName: "MacOS Build"
artifactPrefixName: "PCSX2-macos-Qt"
fetchTags: true
stableBuild: ${{ github.event_name == 'workflow_dispatch' && inputs.is_prelease == 'false' }}
secrets: inherit
# Upload the Artifacts
@@ -146,7 +168,7 @@ jobs:
- name: Prepare Artifact Folder
run: mkdir ./ci-artifacts/
- uses: actions/download-artifact@v3
- uses: actions/download-artifact@v4
name: Download all Artifacts
with:
path: ./ci-artifacts/
@@ -159,9 +181,6 @@ jobs:
working-directory: ./ci-artifacts/
run: for d in *windows*/; do 7z a "${d}asset.7z" ./$d/*; done
# Artifact Naming:
# MacOS: PCSX2-<tag>-macOS-[additional hyphen seperated tags]
# Windows|Linux: PCSX2-<tag>-<windows|linux>-<32bit|64bit>--[additional hyphen seperated tags]
- name: Name and Upload the Release Assets
env:
GITHUB_TOKEN: ${{ github.token }}
@@ -1,39 +1,26 @@
#!/bin/bash
# Artifact Naming Scheme:
# PCSX2-<OS>-Qt-[ARCH]-[SIMD]-[pr\[PR_NUM\]]-[title|sha\[SHA|PR_TITLE\]
# PCSX2-<OS>-Qt-[BUILD_SYSTEM]-[ARCH]-[SIMD]-[pr\[PR_NUM\]]-[title|sha\[SHA|PR_TITLE\]
# -- limited to 200 chars
# Outputs:
# - artifact-name
# Example - PCSX2-linux-Qt-x64-flatpak-sse4-sha[e880a2749]
# Inputs as env-vars
# OS
# BUILD_SYSTEM
# ARCH
# SIMD
# PREFIX
# EVENT_NAME
# PR_TITLE
# PR_NUM
# PR_SHA
NAME=""
if [ "${OS}" == "macos" ]; then
# MacOS has combined binaries for x64 and ARM64.
NAME="PCSX2-${OS}-Qt"
elif [[ ("${OS}" == "windows" && "$BUILD_SYSTEM" != "cmake") ]]; then
NAME="PCSX2-${OS}-Qt-${ARCH}-${SIMD}"
else
NAME="PCSX2-${OS}-Qt-${ARCH}"
if [[ -z "${PREFIX}" ]]; then
echo "PREFIX is not set, can't name artifact without it!"
exit 1
fi
# Add cmake if used to differentate it from msbuild builds
# Else the two artifacts will have the same name and the files will be merged
if [[ ! -z "${BUILD_SYSTEM}" ]]; then
if [[ "${BUILD_SYSTEM}" == "cmake" ]] || [[ "${BUILD_SYSTEM}" == "flatpak" ]]; then
NAME="${NAME}-${BUILD_SYSTEM}"
fi
fi
NAME="${PREFIX}"
# Add PR / Commit Metadata
if [ "$EVENT_NAME" == "pull_request" ]; then
@@ -2,24 +2,33 @@
set -e
INSTALLDIR="$HOME/deps"
if [ "$#" -ne 1 ]; then
echo "Syntax: $0 <output directory>"
exit 1
fi
INSTALLDIR="$1"
NPROCS="$(getconf _NPROCESSORS_ONLN)"
SDL=SDL2-2.28.5
QT=6.6.1
SDL=SDL2-2.30.1
QT=6.6.2
LIBBACKTRACE=ad106d5fdd5d960bd33fae1c48a351af567fd075
if [ "${INSTALLDIR:0:1}" != "/" ]; then
INSTALLDIR="$PWD/$INSTALLDIR"
fi
mkdir -p deps-build
cd deps-build
cat > SHASUMS <<EOF
332cb37d0be20cb9541739c61f79bae5a477427d79ae85e352089afdaf6666e4 $SDL.tar.gz
01215ffbc8cfc4ad165ba7573750f15ddda1f971d5a66e9dcaffd37c587f473a $SDL.tar.gz
fd6f417fe9e3a071cf1424a5152d926a34c4a3c5070745470be6cf12a404ed79 $LIBBACKTRACE.zip
450c5b4677b2fe40ed07954d7f0f40690068e80a94c9df86c2c905ccd59d02f7 qtbase-everywhere-src-$QT.tar.xz
ac4ed08950072e375be662cfa64fdb447dd6e935cf29c56a4128d1500492188f qtimageformats-everywhere-src-$QT.tar.xz
248deb56d26a463cf3162f530358ccf90cfb654bbf518bb35ddf81b205e09228 qtsvg-everywhere-src-$QT.tar.xz
4939105a7345ab4e19e7caee8654a836e65bd41910359623e0f233f3aff0914a qttools-everywhere-src-$QT.tar.xz
668702e822ad7150b27e7caa2158595fd9b3b77ffbc8262e6509872a3920ee88 qttranslations-everywhere-src-$QT.tar.xz
66cc2d632dc07fc6cc4e35247f48b7c1753276ccbf86e86d7b24d799725568b1 qtwayland-everywhere-src-$QT.tar.xz
b89b426b9852a17d3e96230ab0871346574d635c7914480a2a27f98ff942677b qtbase-everywhere-src-$QT.tar.xz
71584c9136d4983ad19fa2d017abbae57b055eb90c62a36bf3f45d6d21a87cb3 qtimageformats-everywhere-src-$QT.tar.xz
5a231d59ef1b42bfbaa5174d4ff39f8e1b4ba070ef984a70b069b4b2576d8181 qtsvg-everywhere-src-$QT.tar.xz
e6d49e9f52111287f77878ecb8b708cce682f10b03ba2476d9247603bc6c4746 qttools-everywhere-src-$QT.tar.xz
ca3ac090ef3aa12566c26b482c106f1f986c5a3444e7003f379726a550530c77 qttranslations-everywhere-src-$QT.tar.xz
9bcdd5cef7ae304e3e0435dac495367ccfb010d09f664b596ba330361941dd78 qtwayland-everywhere-src-$QT.tar.xz
EOF
curl -L \
@@ -45,7 +54,7 @@ cd ..
echo "Building libbacktrace..."
unzip "$LIBBACKTRACE.zip"
cd "libbacktrace-$LIBBACKTRACE"
./configure --prefix="$HOME/deps"
./configure --prefix="$INSTALLDIR"
make
make install
cd ..
@@ -28,8 +28,8 @@
"sources": [
{
"type": "archive",
"url": "https://libsdl.org/release/SDL2-2.28.5.tar.gz",
"sha256": "332cb37d0be20cb9541739c61f79bae5a477427d79ae85e352089afdaf6666e4"
"url": "https://libsdl.org/release/SDL2-2.30.1.tar.gz",
"sha256": "01215ffbc8cfc4ad165ba7573750f15ddda1f971d5a66e9dcaffd37c587f473a"
}
],
"cleanup": [
@@ -38,13 +38,9 @@ cmake \
$ADDITIONAL_CMAKE_ARGS \
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache \
-DCMAKE_BUILD_TYPE=Release \
-DQT_BUILD=ON \
-DCUBEB_API=ON \
-DX11_API=ON \
-DWAYLAND_API=ON \
-DENABLE_SETCAP=OFF \
-DCMAKE_PREFIX_PATH="${DEPS_PREFIX}" \
-DUSE_SYSTEM_SDL2=ON \
-DUSE_SYSTEM_ZSTD=OFF \
-DDISABLE_ADVANCE_SIMD=TRUE
@@ -8,7 +8,7 @@ if [[ $# -lt 1 ]]; then
fi
OUTFILE=$1
GIT_DATE=$(git log -1 --pretty=%cd --date=short)
GIT_DATE=$(git log -1 --pretty=%cd --date=iso8601)
GIT_VERSION=$(git tag --points-at HEAD)
GIT_HASH=$(git rev-parse HEAD)
@@ -1,58 +0,0 @@
#!/bin/bash
SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}")
source "$SCRIPTDIR/functions.sh"
set -e
ARCH=x86_64
KDE_BRANCH=6.6
BRANCH=23.08
FLAT_MANAGER_CLIENT_DIR="$HOME/.local/bin"
# Build packages. Mostly needed for flat-manager-client.
declare -a BUILD_PACKAGES=(
"flatpak"
"flatpak-builder"
"appstream-util"
"python3-aiohttp"
"python3-tenacity"
"python3-gi"
"gobject-introspection"
"libappstream-glib8"
"libappstream-glib-dev"
"libappstream-dev"
"gir1.2-ostree-1.0"
)
# Flatpak runtimes and SDKs.
declare -a FLATPAK_PACKAGES=(
"org.kde.Platform/${ARCH}/${KDE_BRANCH}"
"org.kde.Sdk/${ARCH}/${KDE_BRANCH}"
"org.freedesktop.Platform.ffmpeg-full/${ARCH}/${BRANCH}"
"org.freedesktop.Sdk.Extension.llvm17/${ARCH}/${BRANCH}"
"org.freedesktop.appstream-glib/${ARCH}/stable"
)
retry_command sudo apt-get -qq update
# Install packages needed for building
echo "Will install the following packages for building - ${BUILD_PACKAGES[*]}"
retry_command sudo apt-get -y install "${BUILD_PACKAGES[@]}"
sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
# Install packages needed for building
echo "Will install the following packages for building - ${FLATPAK_PACKAGES[*]}"
retry_command sudo flatpak -y install "${FLATPAK_PACKAGES[@]}"
echo "Installing Flatpak Builder"
retry_command sudo flatpak -y install flathub org.flatpak.builder
echo "Downloading flat-manager-client"
mkdir -p "$FLAT_MANAGER_CLIENT_DIR"
pushd "$FLAT_MANAGER_CLIENT_DIR"
aria2c -Z "https://raw.githubusercontent.com/flatpak/flat-manager/9401efbdc0d6bd489507d8401c567ba219d735d5/flat-manager-client"
chmod +x flat-manager-client
echo "$FLAT_MANAGER_CLIENT_DIR" >> $GITHUB_PATH
popd
@@ -19,9 +19,11 @@ declare -a BUILD_PACKAGES=(
"libfontconfig1-dev"
"libharfbuzz-dev"
"libjpeg-dev"
"liblz4-dev"
"libpng-dev"
"libfreetype-dev"
"libinput-dev"
"libwebp-dev"
"libxcb-*-dev"
"libxkbcommon-dev"
"libxkbcommon-x11-dev"
@@ -2,38 +2,65 @@
set -e
if [ "$#" -ne 1 ]; then
echo "Syntax: $0 <output directory>"
exit 1
fi
export MACOSX_DEPLOYMENT_TARGET=11.0
INSTALLDIR="$HOME/deps"
INSTALLDIR="$1"
NPROCS="$(getconf _NPROCESSORS_ONLN)"
SDL=SDL2-2.28.5
SDL=SDL2-2.30.1
XZ=5.4.5
ZSTD=1.5.5
LZ4=b8fd2d15309dd4e605070bd4486e26b6ef814e29
PNG=1.6.37
JPG=9e
WEBP=1.3.2
FFMPEG=6.0
QT=6.6.0
QT=6.6.2
mkdir deps-build
if [ "${INSTALLDIR:0:1}" != "/" ]; then
INSTALLDIR="$PWD/$INSTALLDIR"
fi
mkdir -p deps-build
cd deps-build
export PKG_CONFIG_PATH="$INSTALLDIR/lib/pkgconfig:$PKG_CONFIG_PATH"
export LDFLAGS="-L$INSTALLDIR/lib -dead_strip $LDFLAGS"
export CFLAGS="-I$INSTALLDIR/include -Os $CFLAGS"
export CXXFLAGS="-I$INSTALLDIR/include -Os $CXXFLAGS"
export LDFLAGS="-L$INSTALLDIR/lib $LDFLAGS"
export CFLAGS="-I$INSTALLDIR/include $CFLAGS"
export CXXFLAGS="-I$INSTALLDIR/include $CXXFLAGS"
CMAKE_COMMON=(
-DCMAKE_BUILD_TYPE=Release
-DCMAKE_PREFIX_PATH="$INSTALLDIR"
-DCMAKE_INSTALL_PREFIX="$INSTALLDIR"
-DCMAKE_OSX_ARCHITECTURES="x86_64"
-DCMAKE_INSTALL_NAME_DIR='$<INSTALL_PREFIX>/lib'
)
cat > SHASUMS <<EOF
332cb37d0be20cb9541739c61f79bae5a477427d79ae85e352089afdaf6666e4 $SDL.tar.gz
01215ffbc8cfc4ad165ba7573750f15ddda1f971d5a66e9dcaffd37c587f473a $SDL.tar.gz
135c90b934aee8fbc0d467de87a05cb70d627da36abe518c357a873709e5b7d6 xz-$XZ.tar.gz
9c4396cc829cfae319a6e2615202e82aad41372073482fce286fac78646d3ee4 zstd-$ZSTD.tar.gz
0728800155f3ed0a0c87e03addbd30ecbe374f7b080678bbca1506051d50dec3 $LZ4.tar.gz
505e70834d35383537b6491e7ae8641f1a4bed1876dbfe361201fc80868d88ca libpng-$PNG.tar.xz
2a499607df669e40258e53d0ade8035ba4ec0175244869d1025d460562aa09b4 libwebp-$WEBP.tar.gz
57be87c22d9b49c112b6d24bc67d42508660e6b718b3db89c44e47e289137082 ffmpeg-$FFMPEG.tar.xz
039d53312acb5897a9054bd38c9ccbdab72500b71fdccdb3f4f0844b0dd39e0e qtbase-everywhere-src-$QT.tar.xz
e1542cb50176e237809895c6549598c08587c63703d100be54ac2d806834e384 qtimageformats-everywhere-src-$QT.tar.xz
33da25fef51102f564624a7ea3e57cb4a0a31b7b44783d1af5749ac36d3c72de qtsvg-everywhere-src-$QT.tar.xz
4e9feebc142bbb6e453e1dc3277e09ec45c8ef081b5ee2a029e6684b5905ba99 qttools-everywhere-src-$QT.tar.xz
a0d89a236f64b810eb0fe4ae1e90db22b0e86263521b35f89e69f1392815078c qttranslations-everywhere-src-$QT.tar.xz
b89b426b9852a17d3e96230ab0871346574d635c7914480a2a27f98ff942677b qtbase-everywhere-src-$QT.tar.xz
71584c9136d4983ad19fa2d017abbae57b055eb90c62a36bf3f45d6d21a87cb3 qtimageformats-everywhere-src-$QT.tar.xz
5a231d59ef1b42bfbaa5174d4ff39f8e1b4ba070ef984a70b069b4b2576d8181 qtsvg-everywhere-src-$QT.tar.xz
e6d49e9f52111287f77878ecb8b708cce682f10b03ba2476d9247603bc6c4746 qttools-everywhere-src-$QT.tar.xz
ca3ac090ef3aa12566c26b482c106f1f986c5a3444e7003f379726a550530c77 qttranslations-everywhere-src-$QT.tar.xz
EOF
curl -L \
-O "https://libsdl.org/release/$SDL.tar.gz" \
-O "https://github.com/tukaani-project/xz/releases/download/v$XZ/xz-$XZ.tar.gz" \
-O "https://github.com/facebook/zstd/releases/download/v$ZSTD/zstd-$ZSTD.tar.gz" \
-O "https://github.com/lz4/lz4/archive/$LZ4.tar.gz" \
-O "https://downloads.sourceforge.net/project/libpng/libpng16/$PNG/libpng-$PNG.tar.xz" \
-O "https://storage.googleapis.com/downloads.webmproject.org/releases/webp/libwebp-$WEBP.tar.gz" \
-O "https://ffmpeg.org/releases/ffmpeg-$FFMPEG.tar.xz" \
-O "https://download.qt.io/official_releases/qt/${QT%.*}/$QT/submodules/qtbase-everywhere-src-$QT.tar.xz" \
-O "https://download.qt.io/official_releases/qt/${QT%.*}/$QT/submodules/qtimageformats-everywhere-src-$QT.tar.xz" \
@@ -46,41 +73,18 @@ shasum -a 256 --check SHASUMS
echo "Installing SDL..."
tar xf "$SDL.tar.gz"
cd "$SDL"
# MFI causes multiple joystick connection events, I'm guessing because both the HIDAPI and MFI interfaces
# race each other, and sometimes both end up getting through. So, just force MFI off.
patch -u CMakeLists.txt <<EOF
--- CMakeLists.txt 2023-08-03 01:33:11
+++ CMakeLists.txt 2023-08-26 12:58:53
@@ -2105,7 +2105,7 @@
#import <Foundation/Foundation.h>
#import <CoreHaptics/CoreHaptics.h>
int main() { return 0; }" HAVE_FRAMEWORK_COREHAPTICS)
- if(HAVE_FRAMEWORK_GAMECONTROLLER AND HAVE_FRAMEWORK_COREHAPTICS)
+ if(HAVE_FRAMEWORK_GAMECONTROLLER AND HAVE_FRAMEWORK_COREHAPTICS AND FALSE)
# Only enable MFI if we also have CoreHaptics to ensure rumble works
set(SDL_JOYSTICK_MFI 1)
set(SDL_FRAMEWORK_GAMECONTROLLER 1)
EOF
cmake -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DSDL_X11=OFF
cmake -B build "${CMAKE_COMMON[@]}" -DSDL_X11=OFF -DBUILD_SHARED_LIBS=ON
make -C build "-j$NPROCS"
make -C build install
cd ..
echo "Installing libpng..."
tar xf "libpng-$PNG.tar.xz"
cd "libpng-$PNG"
./configure --prefix "$INSTALLDIR" --disable-dependency-tracking
make "-j$NPROCS"
make install
cd ..
echo "Installing FFmpeg..."
tar xf "ffmpeg-$FFMPEG.tar.xz"
cd "ffmpeg-$FFMPEG"
./configure --prefix="$INSTALLDIR" --disable-all --disable-autodetect --disable-static --enable-shared \
LDFLAGS="-dead_strip $LDFLAGS" CFLAGS="-Os $CFLAGS" CXXFLAGS="-Os $CXXFLAGS" \
./configure --prefix="$INSTALLDIR" \
--enable-cross-compile --arch=x86_64 --cc='clang -arch x86_64' --cxx='clang++ -arch x86_64' --disable-x86asm \
--disable-all --disable-autodetect --disable-static --enable-shared \
--enable-avcodec --enable-avformat --enable-avutil --enable-swresample --enable-swscale \
--enable-audiotoolbox --enable-videotoolbox \
--enable-encoder=ffv1,qtrle,pcm_s16be,pcm_s16le,*_at,*_videotoolbox \
@@ -90,27 +94,101 @@ make "-j$NPROCS"
make install
cd ..
echo "Installing XZ..."
tar xf "xz-$XZ.tar.gz"
cd "xz-$XZ"
cmake "${CMAKE_COMMON[@]}" -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF -B build
make -C build "-j$NPROCS"
make -C build install
make install
cd ..
echo "Installing Zstd..."
tar xf "zstd-$ZSTD.tar.gz"
cd "zstd-$ZSTD"
cmake "${CMAKE_COMMON[@]}" -DBUILD_SHARED_LIBS=ON -DZSTD_BUILD_PROGRAMS=OFF -B build-dir build/cmake
make -C build-dir "-j$NPROCS"
make -C build-dir install
cd ..
echo "Installing LZ4..."
tar xf "$LZ4.tar.gz"
cd "lz4-$LZ4"
cmake "${CMAKE_COMMON[@]}" -DBUILD_SHARED_LIBS=ON -DLZ4_BUILD_CLI=OFF -DLZ4_BUILD_LEGACY_LZ4C=OFF -B build-dir build/cmake
make -C build-dir "-j$NPROCS"
make -C build-dir install
cd ..
echo "Installing libpng..."
tar xf "libpng-$PNG.tar.xz"
cd "libpng-$PNG"
cmake "${CMAKE_COMMON[@]}" -DBUILD_SHARED_LIBS=ON -DPNG_TESTS=OFF -B build
make -C build "-j$NPROCS"
make -C build install
cd ..
echo "Installing WebP..."
tar xf "libwebp-$WEBP.tar.gz"
cd "libwebp-$WEBP"
cmake "${CMAKE_COMMON[@]}" -B build \
-DWEBP_BUILD_ANIM_UTILS=OFF -DWEBP_BUILD_CWEBP=OFF -DWEBP_BUILD_DWEBP=OFF -DWEBP_BUILD_GIF2WEBP=OFF -DWEBP_BUILD_IMG2WEBP=OFF \
-DWEBP_BUILD_VWEBP=OFF -DWEBP_BUILD_WEBPINFO=OFF -DWEBP_BUILD_WEBPMUX=OFF -DWEBP_BUILD_EXTRAS=OFF -DBUILD_SHARED_LIBS=ON
make -C build "-j$NPROCS"
make -C build install
cd ..
echo "Installing Qt Base..."
tar xf "qtbase-everywhere-src-$QT.tar.xz"
cd "qtbase-everywhere-src-$QT"
cmake -B build -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DCMAKE_BUILD_TYPE=Release -DFEATURE_optimize_size=ON -DFEATURE_dbus=OFF -DFEATURE_framework=OFF -DFEATURE_icu=OFF -DFEATURE_opengl=OFF -DFEATURE_printsupport=OFF -DFEATURE_sql=OFF -DFEATURE_gssapi=OFF
# since we don't have a direct reference to QtSvg, it doesn't deployed directly from the main binary
# (only indirectly from iconengines), and the libqsvg.dylib imageformat plugin does not get deployed.
# We could run macdeployqt twice, but that's even more janky than patching it.
patch -u src/tools/macdeployqt/shared/shared.cpp <<EOF
--- shared.cpp
+++ shared.cpp
@@ -1119,14 +1119,8 @@
addPlugins(QStringLiteral("networkinformation"));
}
- // All image formats (svg if QtSvg is used)
- const bool usesSvg = deploymentInfo.containsModule("Svg", libInfix);
- addPlugins(QStringLiteral("imageformats"), [usesSvg](const QString &lib) {
- if (lib.contains(QStringLiteral("qsvg")) && !usesSvg)
- return false;
- return true;
- });
-
+ // All image formats
+ addPlugins(QStringLiteral("imageformats"));
addPlugins(QStringLiteral("iconengines"));
// Platforminputcontext plugins if QtGui is in use
EOF
cmake -B build "${CMAKE_COMMON[@]}" -DFEATURE_dbus=OFF -DFEATURE_framework=OFF -DFEATURE_icu=OFF -DFEATURE_opengl=OFF -DFEATURE_printsupport=OFF -DFEATURE_sql=OFF -DFEATURE_gssapi=OFF
make -C build "-j$NPROCS"
make -C build install
cd ..
echo "Installing Qt SVG..."
tar xf "qtsvg-everywhere-src-$QT.tar.xz"
cd "qtsvg-everywhere-src-$QT"
cmake -B build -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DCMAKE_BUILD_TYPE=MinSizeRel
make -C build "-j$NPROCS"
make -C build install
cd ..
mkdir build
cd build
"$INSTALLDIR/bin/qt-configure-module" .. -- "${CMAKE_COMMON[@]}"
make "-j$NPROCS"
make install
cd ../..
echo "Installing Qt Image Formats..."
tar xf "qtimageformats-everywhere-src-$QT.tar.xz"
cd "qtimageformats-everywhere-src-$QT"
cmake -B build -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DCMAKE_BUILD_TYPE=MinSizeRel
make -C build "-j$NPROCS"
make -C build install
cd ..
mkdir build
cd build
"$INSTALLDIR/bin/qt-configure-module" .. -- "${CMAKE_COMMON[@]}"
make "-j$NPROCS"
make install
cd ../..
echo "Installing Qt Tools..."
tar xf "qttools-everywhere-src-$QT.tar.xz"
cd "qttools-everywhere-src-$QT"
@@ -128,18 +206,23 @@ patch -u src/linguist/CMakeLists.txt <<EOF
add_subdirectory(linguist)
endif()
EOF
cmake -B build -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DCMAKE_BUILD_TYPE=Release -DFEATURE_assistant=OFF -DFEATURE_clang=OFF -DFEATURE_designer=OFF -DFEATURE_kmap2qmap=OFF -DFEATURE_pixeltool=OFF -DFEATURE_pkg_config=OFF -DFEATURE_qev=OFF -DFEATURE_qtattributionsscanner=OFF -DFEATURE_qtdiag=OFF -DFEATURE_qtplugininfo=OFF
make -C build "-j$NPROCS"
make -C build install
cd ..
mkdir build
cd build
"$INSTALLDIR/bin/qt-configure-module" .. -- "${CMAKE_COMMON[@]}" -DFEATURE_assistant=OFF -DFEATURE_clang=OFF -DFEATURE_designer=OFF -DFEATURE_kmap2qmap=OFF -DFEATURE_pixeltool=OFF -DFEATURE_pkg_config=OFF -DFEATURE_qev=OFF -DFEATURE_qtattributionsscanner=OFF -DFEATURE_qtdiag=OFF -DFEATURE_qtplugininfo=OFF
make "-j$NPROCS"
make install
cd ../..
echo "Installing Qt Translations..."
tar xf "qttranslations-everywhere-src-$QT.tar.xz"
cd "qttranslations-everywhere-src-$QT"
cmake -B build -DCMAKE_PREFIX_PATH="$INSTALLDIR" -DCMAKE_INSTALL_PREFIX="$INSTALLDIR" -DCMAKE_BUILD_TYPE=Release
make -C build "-j$NPROCS"
make -C build install
cd ..
mkdir build
cd build
"$INSTALLDIR/bin/qt-configure-module" .. -- "${CMAKE_COMMON[@]}"
make "-j$NPROCS"
make install
cd ../..
echo "Cleaning up..."
cd ..
rm -r deps-build
rm -rf deps-build
@@ -37,16 +37,16 @@ echo INSTALLDIR=%INSTALLDIR%
cd "%BUILDDIR%"
set QT=6.6.1
set QT=6.6.2
set QTMINOR=6.6
set SDL=SDL2-2.28.5
set SDL=SDL2-2.30.1
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" 97bd14ee0ec67494d2b93f1a4f7da2bf891103c57090d96fdcc2b019d885c76a || goto error
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" 818f92518d1a89ee98ae818891a7d2f0e41aa45b933d55215da2df6d5459428e || goto error
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 03f01042f86b4dbf7329a179f20835817c660a183178c11570cc0535b3c3ba58 || goto error
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" d44d5ead8d4682f54c91687b5e32f2735f086419e3889e05609feae1a7f02da9 || goto error
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" a17eba4e1c00dbd62c13d708c2bc918c2954b2b25a94d3c05e891d62e8f187c8 || goto error
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" e5ccf0eefd6b1ef9604fdf57f6d16ad8484d07fb141ca3a2d9c3f1771296ae25 || goto error
call :downloadfile "%SDL%.zip" "https://libsdl.org/release/%SDL%.zip" c15ded54e9f32f8a1f9ed3e3dc072837a320ed23c5d0e95b7c18ecbe05c1187b || goto error
call :downloadfile "qtbase-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtbase-everywhere-src-%QT%.zip" 3582dbc46df280365fc5d5e6cc8fcfc72ddbddbd330a03a98eec24b8b44fa4d0 || goto error
call :downloadfile "qtimageformats-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtimageformats-everywhere-src-%QT%.zip" 2176b623c9141b1136d57ff9ca1ed12e3636146b53c6feed2083e1cbecff6454 || goto error
call :downloadfile "qtsvg-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qtsvg-everywhere-src-%QT%.zip" 84ba758ef06b93532f2d098f0d08d7bbddf6f3e6273c9e0d3a58498338f85b18 || goto error
call :downloadfile "qttools-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttools-everywhere-src-%QT%.zip" c760fbd229de8a02e793fa41ddd6843cd2cb9c93a4e99bc5384c646599aee996 || goto error
call :downloadfile "qttranslations-everywhere-src-%QT%.zip" "https://download.qt.io/official_releases/qt/%QTMINOR%/%QT%/submodules/qttranslations-everywhere-src-%QT%.zip" a061e8d61eb7c03823ead92fe46b5722ed7b8119aeb84157d80f10a53c77d262 || goto error
if %DEBUG%==1 (
echo Building debug and release libraries...
+1 -1
View File
@@ -8,7 +8,7 @@ jobs:
if: github.repository == 'PCSX2/pcsx2'
runs-on: ubuntu-latest
steps:
- uses: actions/labeler@v4.3.0
- uses: actions/labeler@v5
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
@@ -27,6 +27,7 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "MSVC SSE4"
artifactPrefixName: "PCSX2-windows-Qt-x64-sse4-msvc"
configuration: Release
simd: "SSE4"
secrets: inherit
@@ -38,6 +39,7 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "MSVC AVX2"
artifactPrefixName: "PCSX2-windows-Qt-x64-avx2-msvc"
configuration: Release AVX2
secrets: inherit
@@ -47,6 +49,7 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "CMake MSVC"
artifactPrefixName: "PCSX2-windows-Qt-x64-cmake-msvc"
configuration: CMake
buildSystem: cmake
secrets: inherit
@@ -58,6 +61,7 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "Clang SSE4"
artifactPrefixName: "PCSX2-windows-Qt-x64-sse4-clang"
configuration: Release Clang
simd: "SSE4"
secrets: inherit
@@ -69,6 +73,7 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "Clang AVX2"
artifactPrefixName: "PCSX2-windows-Qt-x64-avx2-clang"
configuration: Release Clang AVX2
secrets: inherit
@@ -78,6 +83,7 @@ jobs:
uses: ./.github/workflows/windows_build_qt.yml
with:
jobName: "CMake Clang"
artifactPrefixName: "PCSX2-windows-Qt-x64-cmake-clang"
configuration: CMake
buildSystem: cmake
cmakeFlags: -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DPCSX2_EXE_NAME=pcsx2-qt-clang
+17 -6
View File
@@ -6,6 +6,9 @@ on:
jobName:
required: true
type: string
artifactPrefixName:
required: true
type: string
os:
required: false
type: string
@@ -37,6 +40,10 @@ on:
required: false
type: boolean
default: false
stableBuild:
required: false
type: boolean
default: false
jobs:
build_windows_qt:
@@ -58,14 +65,18 @@ jobs:
if: ${{ inputs.fetchTags }}
run: git fetch --tags --no-recurse-submodules
- name: Add stable release identifier file
if: ${{ inputs.stableBuild == true || inputs.stableBuild == 'true' }}
shell: bash
run: |
echo "#define DEFAULT_UPDATER_CHANNEL \"stable\"" > ./pcsx2-qt/DefaultUpdaterChannel.h
cat ./pcsx2-qt/DefaultUpdaterChannel.h
- name: Prepare Artifact Metadata
id: artifact-metadata
shell: bash
env:
OS: windows
BUILD_SYSTEM: ${{ inputs.buildSystem }}
ARCH: ${{ inputs.platform }}
SIMD: ${{ inputs.simd }}
PREFIX: ${{ inputs.artifactPrefixName }}
EVENT_NAME: ${{ github.event_name }}
PR_TITLE: ${{ github.event.pull_request.title }}
PR_NUM: ${{ github.event.pull_request.number }}
@@ -122,7 +133,7 @@ jobs:
cmake --build build --config Release --target unittests
- name: Upload artifact
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}
path: |
@@ -136,7 +147,7 @@ jobs:
!./bin/**/*.lib
- name: Upload artifact - with symbols
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: ${{ steps.artifact-metadata.outputs.artifact-name }}-symbols
path: ./bin/**/*.pdb
+21
View File
@@ -56,6 +56,13 @@ oprofile_data/
/UpgradeLog*.htm
/.vscode*
# Jetbrains Rider
/.idea*
# KDevelop 4 Workspace Configuration Files
*.kdev4
/.kdev4*
/bin/**/*.dll
/bin/**/*.dmp
/bin/**/*.exp
@@ -67,11 +74,13 @@ oprofile_data/
/bin/bios
/bin/cache
/bin/cheats
/bin/patches
/bin/covers
/bin/dumps
/bin/gamesettings
/bin/help
/bin/inis
/bin/inis/debuggersettings
/bin/logs
/bin/memcards
/bin/plugins
@@ -80,6 +89,18 @@ oprofile_data/
/bin/textures
/bin/translations
/bin/inputprofiles
/bin/videos
/bin/portable.ini
# Manually added by user.
/bin/resources/patches.zip
# Resources that are runtime downloaded.
/bin/resources/fonts/NotoSansJP-Regular.ttf
/bin/resources/fonts/NotoSansKR-Regular.ttf
/bin/resources/fonts/NotoSansSC-Regular.ttf
/bin/resources/fonts/NotoSansTC-Regular.ttf
/deps
/ipch
+3 -3
View File
@@ -21,12 +21,12 @@
[submodule "3rdparty/zstd/zstd"]
path = 3rdparty/zstd/zstd
url = https://github.com/facebook/zstd.git
[submodule "3rdparty/rcheevos/rcheevos"]
path = 3rdparty/rcheevos/rcheevos
url = https://github.com/RetroAchievements/rcheevos.git
[submodule "3rdparty/libwebp/libwebp"]
path = 3rdparty/libwebp/libwebp
url = https://github.com/webmproject/libwebp
[submodule "3rdparty/xz/xz"]
path = 3rdparty/xz/xz
url = https://github.com/tukaani-project/xz.git
[submodule "3rdparty/lz4/lz4"]
path = 3rdparty/lz4/lz4
url = https://github.com/lz4/lz4
+39
View File
@@ -185,6 +185,7 @@
#define ICON_PF_KEYBOARD "\xE2\x90\xBD"
#define ICON_PF_MOUSE "\xE2\x90\xBE"
#define ICON_PF_MOUSE_AND_KEYBOARD "\xE2\x90\xBF"
#define ICON_PF_GUITAR "\xE2\x91\x83"
#define ICON_PF_F1 "\xE2\x91\xA0"
#define ICON_PF_F2 "\xE2\x91\xA1"
#define ICON_PF_F3 "\xE2\x91\xA2"
@@ -326,3 +327,41 @@
#define ICON_PF_EXIT_QUIT_LEAVE "\xF0\x9F\x9A\xAA"
#define ICON_PF_INFORMATION "\xF0\x9F\x9B\x88"
#define ICON_PF_SHOPPING_CART "\xF0\x9F\x9B\x92"
#define ICON_PF_ACHIEVEMENTS_PROGRESSION "\xE2\x8E\xB2"
#define ICON_PF_ACHIEVEMENTS_MISSABLE "\xE2\x8E\xB3"
#define ICON_PF_ACHIEVEMENTS_WIN "\xE2\x8E\xB4"
#define ICON_PF_DUMBELL "\xE2\x8E\xB5"
#define ICON_PF_POPN_CONTROLLER "\xE2\x8A\xBF"
#define ICON_PF_POPN_BLUE_LEFT "\xE2\x8B\x80"
#define ICON_PF_POPN_BLUE_RIGHT "\xE2\x8B\x81"
#define ICON_PF_POPN_GREEN_LEFT "\xE2\x8B\x82"
#define ICON_PF_POPN_GREEN_RIGHT "\xE2\x8B\x83"
#define ICON_PF_POPN_YELLOW_LEFT "\xE2\x8B\x84"
#define ICON_PF_POPN_YELLOW_RIGHT "\xE2\x8B\x85"
#define ICON_PF_POPN_WHITE_LEFT "\xE2\x8B\x86"
#define ICON_PF_POPN_WHITE_RIGHT "\xE2\x8B\x87"
#define ICON_PF_POPN_RED "\xE2\x8B\x88"
#define ICON_PF_SLOW_MOTION "\xE2\x8D\xBA"
#define ICON_PF_CONTROLLER_VIBRATION "\xE2\x88\x9A"
#define ICON_PF_CIRCLE "\xE2\x8D\x89"
#define ICON_PF_USB "\xE2\x8D\x9A"
#define ICON_PF_MICROCHIP_ALT "\xE2\x8D\x9D"
#define ICON_PF_MICROCHIP "\xE2\x8D\x9E"
#define ICON_PF_INFINITY "\xE2\x8D\x9B"
#define ICON_PF_GAMEPAD_ALT "\xE2\x8D\xA0"
#define ICON_PF_KEYBOARD_ALT "\xE2\x8D\xA1"
#define ICON_PF_DOWNLOAD "\xE2\x8D\xA2"
#define ICON_PF_UPLOAD "\xE2\x8D\xA3"
#define ICON_PF_MEMORY_CARD "\xE2\x8D\xA4"
#define ICON_PF_SOUND "\xE2\x8D\xA5"
#define ICON_PF_PICTURE "\xE2\x8D\xA6"
#define ICON_PF_ISOMETRIC_CUBE "\xE2\xAC\x9B"
#define ICON_PF_SNOOZE "\xE2\x8D\xBB"
#define ICON_PF_HEARTBEAT_CIRCLE "\xE2\x8D\xBC"
#define ICON_PF_HEARTBEAT_ALT "\xE2\x8D\xBD"
#define ICON_PF_HEARTBEAT_MAG "\xE2\x8D\xBE"
#define ICON_PF_MONITOR_CODE "\xE2\x8D\xBF"
#define ICON_PF_SIXTY_CIRCLE "\xE2\x8E\x80"
#define ICON_PF_SPEAKER_ALT "\xE2\x8D\xA7"
#define ICON_PF_THUNDERBOLT "\xE2\x8D\x9C"
#define ICON_PF_BACKWARD "\xE2\x8F\x8C"
+1 -1
View File
@@ -15,5 +15,5 @@ add_library(libchdr
)
target_include_directories(libchdr PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_link_libraries(libchdr PRIVATE ZLIB::ZLIB LZMA::LZMA)
target_link_libraries(libchdr PRIVATE ZLIB::ZLIB LZMA::LZMA Zstd::Zstd)
+55 -26
View File
@@ -1,6 +1,6 @@
/*
FLAC audio decoder. Choice of public domain or MIT-0. See license statements at the end of this file.
dr_flac - v0.12.39 - 2022-09-17
dr_flac - v0.12.42 - 2023-11-02
David Reid - mackron@gmail.com
@@ -235,12 +235,12 @@ extern "C" {
#define DRFLAC_VERSION_MAJOR 0
#define DRFLAC_VERSION_MINOR 12
#define DRFLAC_VERSION_REVISION 39
#define DRFLAC_VERSION_REVISION 42
#define DRFLAC_VERSION_STRING DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MAJOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_MINOR) "." DRFLAC_XSTRINGIFY(DRFLAC_VERSION_REVISION)
#include <stddef.h> /* For size_t. */
/* Sized types. */
/* Sized Types */
typedef signed char drflac_int8;
typedef unsigned char drflac_uint8;
typedef signed short drflac_int16;
@@ -273,7 +273,9 @@ typedef drflac_uint8 drflac_bool8;
typedef drflac_uint32 drflac_bool32;
#define DRFLAC_TRUE 1
#define DRFLAC_FALSE 0
/* End Sized Types */
/* Decorations */
#if !defined(DRFLAC_API)
#if defined(DRFLAC_DLL)
#if defined(_WIN32)
@@ -303,6 +305,7 @@ typedef drflac_uint32 drflac_bool32;
#define DRFLAC_PRIVATE static
#endif
#endif
/* End Decorations */
#if defined(_MSC_VER) && _MSC_VER >= 1700 /* Visual Studio 2012 */
#define DRFLAC_DEPRECATED __declspec(deprecated)
@@ -321,6 +324,16 @@ typedef drflac_uint32 drflac_bool32;
DRFLAC_API void drflac_version(drflac_uint32* pMajor, drflac_uint32* pMinor, drflac_uint32* pRevision);
DRFLAC_API const char* drflac_version_string(void);
/* Allocation Callbacks */
typedef struct
{
void* pUserData;
void* (* onMalloc)(size_t sz, void* pUserData);
void* (* onRealloc)(void* p, size_t sz, void* pUserData);
void (* onFree)(void* p, void* pUserData);
} drflac_allocation_callbacks;
/* End Allocation Callbacks */
/*
As data is read from the client it is placed into an internal buffer for fast access. This controls the size of that buffer. Larger values means more speed,
but also more memory. In my testing there is diminishing returns after about 4KB, but you can fiddle with this to suit your own needs. Must be a multiple of 8.
@@ -329,11 +342,22 @@ but also more memory. In my testing there is diminishing returns after about 4KB
#define DR_FLAC_BUFFER_SIZE 4096
#endif
/* Check if we can enable 64-bit optimizations. */
/* Architecture Detection */
#if defined(_WIN64) || defined(_LP64) || defined(__LP64__)
#define DRFLAC_64BIT
#endif
#if defined(__x86_64__) || defined(_M_X64)
#define DRFLAC_X64
#elif defined(__i386) || defined(_M_IX86)
#define DRFLAC_X86
#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64)
#define DRFLAC_ARM
#endif
/* End Architecture Detection */
#ifdef DRFLAC_64BIT
typedef drflac_uint64 drflac_cache_t;
#else
@@ -562,14 +586,6 @@ will be set to one of the DRFLAC_METADATA_BLOCK_TYPE_* tokens.
typedef void (* drflac_meta_proc)(void* pUserData, drflac_metadata* pMetadata);
typedef struct
{
void* pUserData;
void* (* onMalloc)(size_t sz, void* pUserData);
void* (* onRealloc)(void* p, size_t sz, void* pUserData);
void (* onFree)(void* p, void* pUserData);
} drflac_allocation_callbacks;
/* Structure for internal use. Only used for decoders opened with drflac_open_memory. */
typedef struct
{
@@ -1351,6 +1367,7 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat
#include <stdlib.h>
#include <string.h>
/* Inline */
#ifdef _MSC_VER
#define DRFLAC_INLINE __forceinline
#elif defined(__GNUC__)
@@ -1377,15 +1394,7 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat
#else
#define DRFLAC_INLINE
#endif
/* CPU architecture. */
#if defined(__x86_64__) || defined(_M_X64)
#define DRFLAC_X64
#elif defined(__i386) || defined(_M_IX86)
#define DRFLAC_X86
#elif defined(__arm__) || defined(_M_ARM) || defined(__arm64) || defined(__arm64__) || defined(__aarch64__) || defined(_M_ARM64)
#define DRFLAC_ARM
#endif
/* End Inline */
/*
Intrinsics Support
@@ -1623,6 +1632,7 @@ static DRFLAC_INLINE drflac_bool32 drflac_has_sse41(void)
#define DRFLAC_MAX_SIMD_VECTOR_SIZE 64 /* 64 for AVX-512 in the future. */
/* Result Codes */
typedef drflac_int32 drflac_result;
#define DRFLAC_SUCCESS 0
#define DRFLAC_ERROR -1 /* A generic error. */
@@ -1678,7 +1688,10 @@ typedef drflac_int32 drflac_result;
#define DRFLAC_CANCELLED -51
#define DRFLAC_MEMORY_ALREADY_MAPPED -52
#define DRFLAC_AT_END -53
#define DRFLAC_CRC_MISMATCH -128
#define DRFLAC_CRC_MISMATCH -100
/* End Result Codes */
#define DRFLAC_SUBFRAME_CONSTANT 0
#define DRFLAC_SUBFRAME_VERBATIM 1
@@ -1838,7 +1851,7 @@ static DRFLAC_INLINE drflac_uint32 drflac__swap_endian_uint32(drflac_uint32 n)
#if defined(_MSC_VER) && !defined(__clang__)
return _byteswap_ulong(n);
#elif defined(__GNUC__) || defined(__clang__)
#if defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(DRFLAC_64BIT) /* <-- 64-bit inline assembly has not been tested, so disabling for now. */
#if defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 6) && !defined(__ARM_ARCH_6M__) && !defined(DRFLAC_64BIT) /* <-- 64-bit inline assembly has not been tested, so disabling for now. */
/* Inline assembly optimized implementation for ARM. In my testing, GCC does not generate optimized code with __builtin_bswap32(). */
drflac_uint32 r;
__asm__ __volatile__ (
@@ -2802,7 +2815,7 @@ static DRFLAC_INLINE drflac_uint32 drflac__clz_lzcnt(drflac_cache_t x)
return r;
}
#elif defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(DRFLAC_64BIT) /* <-- I haven't tested 64-bit inline assembly, so only enabling this for the 32-bit build for now. */
#elif defined(DRFLAC_ARM) && (defined(__ARM_ARCH) && __ARM_ARCH >= 5) && !defined(__ARM_ARCH_6M__) && !defined(DRFLAC_64BIT) /* <-- I haven't tested 64-bit inline assembly, so only enabling this for the 32-bit build for now. */
{
unsigned int r;
__asm__ __volatile__ (
@@ -6479,7 +6492,7 @@ static drflac_bool32 drflac__read_and_decode_metadata(drflac_read_proc onRead, d
for (;;) {
drflac_metadata metadata;
drflac_uint8 isLastBlock = 0;
drflac_uint8 blockType;
drflac_uint8 blockType = 0;
drflac_uint32 blockSize;
if (drflac__read_and_decode_block_header(onRead, pUserData, &isLastBlock, &blockType, &blockSize) == DRFLAC_FALSE) {
return DRFLAC_FALSE;
@@ -8141,6 +8154,7 @@ static drflac* drflac_open_with_metadata_private(drflac_read_proc onRead, drflac
#include <wchar.h> /* For wcslen(), wcsrtombs() */
#endif
/* Errno */
/* drflac_result_from_errno() is only used for fopen() and wfopen() so putting it inside DR_WAV_NO_STDIO for now. If something else needs this later we can move it out. */
#include <errno.h>
static drflac_result drflac_result_from_errno(int e)
@@ -8544,7 +8558,9 @@ static drflac_result drflac_result_from_errno(int e)
default: return DRFLAC_ERROR;
}
}
/* End Errno */
/* fopen */
static drflac_result drflac_fopen(FILE** ppFile, const char* pFilePath, const char* pOpenMode)
{
#if defined(_MSC_VER) && _MSC_VER >= 1400
@@ -8702,6 +8718,7 @@ static drflac_result drflac_wfopen(FILE** ppFile, const wchar_t* pFilePath, cons
return DRFLAC_SUCCESS;
}
#endif
/* End fopen */
static size_t drflac__on_read_stdio(void* pUserData, void* bufferOut, size_t bytesToRead)
{
@@ -11666,6 +11683,7 @@ DRFLAC_API drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 p
/* High Level APIs */
/* SIZE_MAX */
#if defined(SIZE_MAX)
#define DRFLAC_SIZE_MAX SIZE_MAX
#else
@@ -11675,6 +11693,7 @@ DRFLAC_API drflac_bool32 drflac_seek_to_pcm_frame(drflac* pFlac, drflac_uint64 p
#define DRFLAC_SIZE_MAX 0xFFFFFFFF
#endif
#endif
/* End SIZE_MAX */
/* Using a macro as the definition of the drflac__full_decode_and_close_*() API family. Sue me. */
@@ -12058,6 +12077,16 @@ DRFLAC_API drflac_bool32 drflac_next_cuesheet_track(drflac_cuesheet_track_iterat
/*
REVISION HISTORY
================
v0.12.42 - 2023-11-02
- Fix build for ARMv6-M.
- Fix a compilation warning with GCC.
v0.12.41 - 2023-06-17
- Fix an incorrect date in revision history. No functional change.
v0.12.40 - 2023-05-22
- Minor code restructure. No functional change.
v0.12.39 - 2022-09-17
- Fix compilation with DJGPP.
- Fix compilation error with Visual Studio 2019 and the ARM build.
@@ -12488,7 +12517,7 @@ For more information, please refer to <http://unlicense.org/>
===============================================================================
ALTERNATIVE 2 - MIT No Attribution
===============================================================================
Copyright 2020 David Reid
Copyright 2023 David Reid
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
+17 -9
View File
@@ -10,10 +10,12 @@
#pragma once
#ifndef __CDROM_H__
#define __CDROM_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include <stdbool.h>
#include <libchdr/chdconfig.h>
/***************************************************************************
@@ -47,11 +49,15 @@ enum
enum
{
CD_SUB_NORMAL = 0, /* "cooked" 96 bytes per sector */
CD_SUB_RAW, /* raw uninterleaved 96 bytes per sector */
CD_SUB_NONE /* no subcode data stored */
CD_SUB_NONE = 0, /* no subcode data stored */
CD_SUB_RAW_INTERLEAVED, /* raw interleaved 96 bytes per sector */
CD_SUB_RAW, /* raw non-interleaved 96 bytes per sector */
};
const char* cdrom_get_subtype_string(uint32_t subtype);
bool cdrom_parse_subtype_string(const char* typestring, uint32_t* subtype, uint32_t* subsize);
#define CD_FLAG_GDROM 0x00000001 /* disc is a GD-ROM, all tracks should be stored with GD-ROM metadata */
#define CD_FLAG_GDROMLE 0x00000002 /* legacy GD-ROM, with little-endian CDDA data */
@@ -81,10 +87,10 @@ static inline uint32_t lba_to_msf(uint32_t lba)
{
uint8_t m, s, f;
m = lba / (60 * 75);
m = (uint8_t)(lba / (60 * 75));
lba -= m * (60 * 75);
s = lba / 75;
f = lba % 75;
s = (uint8_t)(lba / 75);
f = (uint8_t)(lba % 75);
return ((m / 10) << 20) | ((m % 10) << 16) |
((s / 10) << 12) | ((s % 10) << 8) |
@@ -107,4 +113,6 @@ static inline uint32_t lba_to_msf_alt(int lba)
return ret;
}
#endif /* __CDROM_H__ */
#ifdef __cplusplus
} // extern "C"
#endif
+5 -1
View File
@@ -208,10 +208,12 @@ extern "C" {
#define CHD_CODEC_LZMA CHD_MAKE_TAG('l','z','m','a')
#define CHD_CODEC_HUFFMAN CHD_MAKE_TAG('h','u','f','f')
#define CHD_CODEC_FLAC CHD_MAKE_TAG('f','l','a','c')
#define CHD_CODEC_ZSTD CHD_MAKE_TAG('z', 's', 't', 'd')
/* general codecs with CD frontend */
#define CHD_CODEC_CD_ZLIB CHD_MAKE_TAG('c','d','z','l')
#define CHD_CODEC_CD_LZMA CHD_MAKE_TAG('c','d','l','z')
#define CHD_CODEC_CD_FLAC CHD_MAKE_TAG('c','d','f','l')
#define CHD_CODEC_CD_ZSTD CHD_MAKE_TAG('c','d','z','s')
/* A/V codec configuration parameters */
#define AV_CODEC_COMPRESS_CONFIG 1
@@ -389,11 +391,13 @@ CHD_EXPORT void chd_close(chd_file *chd);
/* return the associated core_file */
CHD_EXPORT core_file *chd_core_file(chd_file *chd);
/* return the overall size of a CHD, and any of its parents */
CHD_EXPORT UINT64 chd_get_compressed_size(chd_file* chd);
/* return an error string for the given CHD error */
CHD_EXPORT const char *chd_error_string(chd_error err);
/* ----- CHD header management ----- */
/* return a pointer to the extracted CHD header data */
+4 -1
View File
@@ -34,7 +34,7 @@
<ClCompile>
<PreprocessorDefinitions>%(PreprocessorDefinitions);_7ZIP_ST</PreprocessorDefinitions>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
<AdditionalIncludeDirectories>$(ProjectDir)include;$(ProjectDir)src;$(SolutionDir)3rdparty\lzma\include;$(SolutionDir)3rdparty\zlib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)include;$(ProjectDir)src;$(SolutionDir)3rdparty\lzma\include;$(SolutionDir)3rdparty\zlib;$(SolutionDir)3rdparty\zstd\zstd\lib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
</ItemDefinitionGroup>
@@ -52,6 +52,9 @@
<ProjectReference Include="..\zlib\zlib.vcxproj">
<Project>{2f6c0388-20cb-4242-9f6c-a6ebb6a83f47}</Project>
</ProjectReference>
<ProjectReference Include="..\zstd\zstd.vcxproj">
<Project>{52244028-937a-44e9-a76b-2bea18fd239a}</Project>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
+29
View File
@@ -20,6 +20,35 @@
#include <libchdr/cdrom.h>
const char* cdrom_get_subtype_string(uint32_t subtype)
{
switch (subtype)
{
case CD_SUB_RAW: return "RW";
case CD_SUB_RAW_INTERLEAVED: return "RW_RAW";
default: return "NONE";
}
}
bool cdrom_parse_subtype_string(const char* typestring, uint32_t* subtype, uint32_t* subsize)
{
// https://github.com/mamedev/mame/blob/d2d54fb8ed53a2e86d308067da8414f85b5929b0/src/lib/util/cdrom.cpp#L767
if (!strcmp(typestring, "RW"))
{
*subtype = CD_SUB_RAW;
*subsize = 96;
return true;
}
else if (!strcmp(typestring, "RW_RAW"))
{
*subtype = CD_SUB_RAW_INTERLEAVED;
*subsize = 96;
return true;
}
return false;
}
#ifdef WANT_RAW_DATA_SECTOR
/***************************************************************************
+246 -3
View File
@@ -48,13 +48,14 @@
#include <libchdr/cdrom.h>
#include <libchdr/flac.h>
#include <libchdr/huffman.h>
#include <zstd.h>
#include "LzmaEnc.h"
#include "LzmaDec.h"
#if defined(__PS3__) || defined(__PSL1GHT__)
#define __MACTYPES__
#endif
#include "zlib.h"
#include <zlib.h>
#undef TRUE
#undef FALSE
@@ -234,6 +235,12 @@ struct _huff_codec_data
struct huffman_decoder* decoder;
};
typedef struct _zstd_codec_data zstd_codec_data;
struct _zstd_codec_data
{
ZSTD_DStream *dstream;
};
/* codec-private data for the CDZL codec */
typedef struct _cdzl_codec_data cdzl_codec_data;
struct _cdzl_codec_data {
@@ -276,6 +283,16 @@ struct _cdfl_codec_data {
uint8_t* buffer;
};
typedef struct _cdzs_codec_data cdzs_codec_data;
struct _cdzs_codec_data
{
zstd_codec_data base_decompressor;
#ifdef WANT_SUBCODE
zstd_codec_data subcode_decompressor;
#endif
uint8_t* buffer;
};
/* internal representation of an open CHD file */
struct _chd_file
{
@@ -303,9 +320,11 @@ struct _chd_file
lzma_codec_data lzma_codec_data; /* lzma codec data */
huff_codec_data huff_codec_data; /* huff codec data */
flac_codec_data flac_codec_data; /* flac codec data */
zstd_codec_data zstd_codec_data; /* zstd codec data */
cdzl_codec_data cdzl_codec_data; /* cdzl codec data */
cdlz_codec_data cdlz_codec_data; /* cdlz codec data */
cdfl_codec_data cdfl_codec_data; /* cdfl codec data */
cdzs_codec_data cdzs_codec_data; /* cdzs codec data */
#ifdef NEED_CACHE_HUNK
UINT32 maxhunk; /* maximum hunk accessed */
@@ -373,6 +392,12 @@ static chd_error flac_codec_init(void *codec, uint32_t hunkbytes);
static void flac_codec_free(void *codec);
static chd_error flac_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
/* zstd compression codec */
static chd_error zstd_codec_init(void *codec, uint32_t hunkbytes);
static void zstd_codec_free(void *codec);
static chd_error zstd_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
/* cdzl compression codec */
static chd_error cdzl_codec_init(void* codec, uint32_t hunkbytes);
static void cdzl_codec_free(void* codec);
@@ -388,6 +413,11 @@ static chd_error cdfl_codec_init(void* codec, uint32_t hunkbytes);
static void cdfl_codec_free(void* codec);
static chd_error cdfl_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
/* cdzs compression codec */
static chd_error cdzs_codec_init(void *codec, uint32_t hunkbytes);
static void cdzs_codec_free(void *codec);
static chd_error cdzs_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen);
/***************************************************************************
* LZMA ALLOCATOR HELPER
***************************************************************************
@@ -812,11 +842,12 @@ static chd_error huff_codec_decompress(void *codec, const uint8_t *src, uint32_t
if (err != HUFFERR_NONE)
{
free(bitbuf);
return err;
return CHDERR_DECOMPRESSION_ERROR;
}
// then decode the data
for (uint32_t cur = 0; cur < destlen; cur++)
uint32_t cur;
for (cur = 0; cur < destlen; cur++)
dest[cur] = huffman_decode_one(huff_codec->decoder, bitbuf);
bitstream_flush(bitbuf);
chd_error result = bitstream_overflow(bitbuf) ? CHDERR_DECOMPRESSION_ERROR : CHDERR_NONE;
@@ -986,6 +1017,163 @@ static chd_error cdfl_codec_decompress(void *codec, const uint8_t *src, uint32_t
return CHDERR_NONE;
}
/***************************************************************************
* ZSTD DECOMPRESSOR
***************************************************************************
*/
/*-------------------------------------------------
* zstd_codec_init - constructor
*-------------------------------------------------
*/
static chd_error zstd_codec_init(void* codec, uint32_t hunkbytes)
{
zstd_codec_data* zstd_codec = (zstd_codec_data*) codec;
zstd_codec->dstream = ZSTD_createDStream();
if (!zstd_codec->dstream) {
printf("NO DSTREAM CREATED!\n");
return CHDERR_DECOMPRESSION_ERROR;
}
return CHDERR_NONE;
}
/*-------------------------------------------------
* zstd_codec_free
*-------------------------------------------------
*/
static void zstd_codec_free(void* codec)
{
zstd_codec_data* zstd_codec = (zstd_codec_data*) codec;
ZSTD_freeDStream(zstd_codec->dstream);
}
/*-------------------------------------------------
* decompress - decompress data using the ZSTD
* codec
*-------------------------------------------------
*/
static chd_error zstd_codec_decompress(void* codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
/* initialize */
zstd_codec_data* zstd_codec = (zstd_codec_data*) codec;
//reset decompressor
size_t zstd_res = ZSTD_initDStream(zstd_codec->dstream);
if (ZSTD_isError(zstd_res))
{
printf("INITI DSTREAM FAILED!\n");
return CHDERR_DECOMPRESSION_ERROR;
}
ZSTD_inBuffer input = {src, complen, 0};
ZSTD_outBuffer output = {dest, destlen, 0 };
while ((input.pos < input.size) && (output.pos < output.size))
{
zstd_res = ZSTD_decompressStream(zstd_codec->dstream, &output, &input);
if (ZSTD_isError(zstd_res))
{
printf("DECOMPRESSION ERROR IN LOOP\n");
return CHDERR_DECOMPRESSION_ERROR;
}
}
if (output.pos != output.size)
{
printf("OUTPUT DOESN'T MATCH!\n");
return CHDERR_DECOMPRESSION_ERROR;
}
return CHDERR_NONE;
}
/* cdzs */
static chd_error cdzs_codec_init(void* codec, uint32_t hunkbytes)
{
chd_error ret;
cdzs_codec_data* cdzs = (cdzs_codec_data*) codec;
/* allocate buffer */
cdzs->buffer = (uint8_t*)malloc(sizeof(uint8_t) * hunkbytes);
if (cdzs->buffer == NULL)
return CHDERR_OUT_OF_MEMORY;
/* make sure the CHD's hunk size is an even multiple of the frame size */
ret = zstd_codec_init(&cdzs->base_decompressor, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SECTOR_DATA);
if (ret != CHDERR_NONE)
return ret;
#ifdef WANT_SUBCODE
ret = zstd_codec_init(&cdzs->subcode_decompressor, (hunkbytes / CD_FRAME_SIZE) * CD_MAX_SUBCODE_DATA);
if (ret != CHDERR_NONE)
return ret;
#endif
if (hunkbytes % CD_FRAME_SIZE != 0)
return CHDERR_CODEC_ERROR;
return CHDERR_NONE;
}
static void cdzs_codec_free(void* codec)
{
cdzs_codec_data* cdzs = (cdzs_codec_data*) codec;
free(cdzs->buffer);
zstd_codec_free(&cdzs->base_decompressor);
#ifdef WANT_SUBCODE
zstd_codec_free(&cdzs->subcode_decompressor);
#endif
}
static chd_error cdzs_codec_decompress(void *codec, const uint8_t *src, uint32_t complen, uint8_t *dest, uint32_t destlen)
{
uint32_t framenum;
cdzs_codec_data* cdzs = (cdzs_codec_data*)codec;
/* determine header bytes */
uint32_t frames = destlen / CD_FRAME_SIZE;
uint32_t complen_bytes = (destlen < 65536) ? 2 : 3;
uint32_t ecc_bytes = (frames + 7) / 8;
uint32_t header_bytes = ecc_bytes + complen_bytes;
/* extract compressed length of base */
uint32_t complen_base = (src[ecc_bytes + 0] << 8) | src[ecc_bytes + 1];
if (complen_bytes > 2)
complen_base = (complen_base << 8) | src[ecc_bytes + 2];
/* reset and decode */
zstd_codec_decompress(&cdzs->base_decompressor, &src[header_bytes], complen_base, &cdzs->buffer[0], frames * CD_MAX_SECTOR_DATA);
#ifdef WANT_SUBCODE
zstd_codec_decompress(&cdzs->subcode_decompressor, &src[header_bytes + complen_base], complen - complen_base - header_bytes, &cdzs->buffer[frames * CD_MAX_SECTOR_DATA], frames * CD_MAX_SUBCODE_DATA);
#endif
/* reassemble the data */
for (framenum = 0; framenum < frames; framenum++)
{
uint8_t *sector;
memcpy(&dest[framenum * CD_FRAME_SIZE], &cdzs->buffer[framenum * CD_MAX_SECTOR_DATA], CD_MAX_SECTOR_DATA);
#ifdef WANT_SUBCODE
memcpy(&dest[framenum * CD_FRAME_SIZE + CD_MAX_SECTOR_DATA], &cdzs->buffer[frames * CD_MAX_SECTOR_DATA + framenum * CD_MAX_SUBCODE_DATA], CD_MAX_SUBCODE_DATA);
#endif
#ifdef WANT_RAW_DATA_SECTOR
/* reconstitute the ECC data and sync header */
sector = (uint8_t *)&dest[framenum * CD_FRAME_SIZE];
if ((src[framenum / 8] & (1 << (framenum % 8))) != 0)
{
memcpy(sector, s_cd_sync_header, sizeof(s_cd_sync_header));
ecc_generate(sector);
}
#endif
}
return CHDERR_NONE;
}
/***************************************************************************
CODEC INTERFACES
***************************************************************************/
@@ -1068,6 +1256,16 @@ static const codec_interface codec_interfaces[] =
flac_codec_decompress,
NULL
},
/* V5 zstd compression */
{
CHD_CODEC_ZSTD,
"ZStandard",
FALSE,
zstd_codec_init,
zstd_codec_free,
zstd_codec_decompress,
NULL
},
/* V5 CD zlib compression */
{
@@ -1101,6 +1299,17 @@ static const codec_interface codec_interfaces[] =
cdfl_codec_decompress,
NULL
},
/* V5 CD zstd compression */
{
CHD_CODEC_CD_ZSTD,
"cdzs (CD ZStandard)",
FALSE,
cdzs_codec_init,
cdzs_codec_free,
cdzs_codec_decompress,
NULL
}
};
/***************************************************************************
@@ -1702,6 +1911,10 @@ CHD_EXPORT chd_error chd_open_core_file(core_file *file, int mode, chd_file *par
codec = &newchd->flac_codec_data;
break;
case CHD_CODEC_ZSTD:
codec = &newchd->zstd_codec_data;
break;
case CHD_CODEC_CD_ZLIB:
codec = &newchd->cdzl_codec_data;
break;
@@ -1713,6 +1926,10 @@ CHD_EXPORT chd_error chd_open_core_file(core_file *file, int mode, chd_file *par
case CHD_CODEC_CD_FLAC:
codec = &newchd->cdfl_codec_data;
break;
case CHD_CODEC_CD_ZSTD:
codec = &newchd->cdzs_codec_data;
break;
}
if (codec == NULL)
@@ -1885,6 +2102,10 @@ CHD_EXPORT void chd_close(chd_file *chd)
codec = &chd->flac_codec_data;
break;
case CHD_CODEC_ZSTD:
codec = &chd->zstd_codec_data;
break;
case CHD_CODEC_CD_ZLIB:
codec = &chd->cdzl_codec_data;
break;
@@ -1896,6 +2117,10 @@ CHD_EXPORT void chd_close(chd_file *chd)
case CHD_CODEC_CD_FLAC:
codec = &chd->cdfl_codec_data;
break;
case CHD_CODEC_CD_ZSTD:
codec = &chd->cdzs_codec_data;
break;
}
if (codec)
@@ -1952,6 +2177,14 @@ CHD_EXPORT core_file *chd_core_file(chd_file *chd)
return chd->file;
}
CHD_EXPORT UINT64 chd_get_compressed_size(chd_file *chd)
{
UINT64 size = chd->file->fsize(chd->file);
if (chd->parent)
size += chd_get_compressed_size(chd->parent);
return size;
}
/*-------------------------------------------------
chd_error_string - return an error string for
the given CHD error
@@ -2546,7 +2779,9 @@ static chd_error hunk_read_into_memory(chd_file *chd, UINT32 hunknum, UINT8 *des
/* read it into the decompression buffer */
compressed_bytes = hunk_read_compressed(chd, entry->offset, entry->length);
if (compressed_bytes == NULL)
{
return CHDERR_READ_ERROR;
}
/* now decompress using the codec */
err = CHDERR_NONE;
@@ -2656,6 +2891,10 @@ static chd_error hunk_read_into_memory(chd_file *chd, UINT32 hunknum, UINT8 *des
codec = &chd->flac_codec_data;
break;
case CHD_CODEC_ZSTD:
codec = &chd->zstd_codec_data;
break;
case CHD_CODEC_CD_ZLIB:
codec = &chd->cdzl_codec_data;
break;
@@ -2667,6 +2906,10 @@ static chd_error hunk_read_into_memory(chd_file *chd, UINT32 hunknum, UINT8 *des
case CHD_CODEC_CD_FLAC:
codec = &chd->cdfl_codec_data;
break;
case CHD_CODEC_CD_ZSTD:
codec = &chd->cdzs_codec_data;
break;
}
if (codec==NULL)
return CHDERR_CODEC_ERROR;
+3
View File
@@ -295,6 +295,9 @@ enum huffman_error huffman_import_tree_huffman(struct huffman_decoder* decoder,
}
}
/* make sure we free the local huffman decoder */
delete_huffman_decoder(smallhuff);
/* make sure we ended up with the right number */
if (curcode != decoder->numcodes)
return HUFFERR_INVALID_DATA;
+67 -156
View File
@@ -28,103 +28,83 @@ include(TestBigEndian)
# Request ISO C secure library functions (memcpy_s &c)
list(APPEND CMAKE_REQUIRED_DEFINITIONS -D__STDC_WANT_LIB_EXT1__=1)
check_function_exists(_close HAVE__CLOSE)
check_function_exists(_dup HAVE__DUP)
check_function_exists(_fdopen HAVE__FDOPEN)
check_function_exists(_fileno HAVE__FILENO)
check_function_exists(_setmode HAVE__SETMODE)
check_symbol_exists(_snprintf stdio.h HAVE__SNPRINTF)
check_symbol_exists(_snprintf_s stdio.h HAVE__SNPRINTF_S)
check_symbol_exists(_snwprintf_s stdio.h HAVE__SNWPRINTF_S)
check_function_exists(_strdup HAVE__STRDUP)
check_symbol_exists(_stricmp string.h HAVE__STRICMP)
check_function_exists(_strtoi64 HAVE__STRTOI64)
check_function_exists(_strtoui64 HAVE__STRTOUI64)
check_function_exists(_unlink HAVE__UNLINK)
check_function_exists(arc4random HAVE_ARC4RANDOM)
check_function_exists(clonefile HAVE_CLONEFILE)
check_function_exists(explicit_bzero HAVE_EXPLICIT_BZERO)
check_function_exists(explicit_memset HAVE_EXPLICIT_MEMSET)
check_function_exists(fchmod HAVE_FCHMOD)
check_function_exists(fileno HAVE_FILENO)
check_function_exists(fseeko HAVE_FSEEKO)
check_function_exists(ftello HAVE_FTELLO)
check_function_exists(getprogname HAVE_GETPROGNAME)
check_symbol_exists(localtime_r time.h HAVE_LOCALTIME_R)
check_symbol_exists(localtime_s time.h HAVE_LOCALTIME_S)
check_function_exists(memcpy_s HAVE_MEMCPY_S)
check_function_exists(random HAVE_RANDOM)
check_function_exists(setmode HAVE_SETMODE)
check_symbol_exists(snprintf stdio.h HAVE_SNPRINTF)
check_symbol_exists(snprintf_s stdio.h HAVE_SNPRINTF_S)
check_symbol_exists(strcasecmp strings.h HAVE_STRCASECMP)
check_function_exists(strdup HAVE_STRDUP)
check_function_exists(strerror_s HAVE_STRERROR_S)
check_function_exists(strerrorlen_s HAVE_STRERRORLEN_S)
check_function_exists(stricmp HAVE_STRICMP)
check_function_exists(strncpy_s HAVE_STRNCPY_S)
check_function_exists(strtoll HAVE_STRTOLL)
check_function_exists(strtoull HAVE_STRTOULL)
# Replaced these dynamic checks with static, otherwise XCode is slow to configure.
set(HAVE__CLOSE ${MSVC})
set(HAVE__DUP ${MSVC})
set(HAVE__FDOPEN ${MSVC})
set(HAVE__FILENO ${MSVC})
set(HAVE__SETMODE ${MSVC})
set(HAVE__SNPRINTF ${MSVC})
set(HAVE__SNPRINTF_S ${MSVC})
set(HAVE__SNWPRINTF_S ${MSVC})
set(HAVE__STRDUP ${MSVC})
set(HAVE__STRICMP ${MSVC})
set(HAVE__STRTOI64 ${MSVC})
set(HAVE__STRTOUI64 ${MSVC})
set(HAVE__UNLINK ${MSVC})
set(HAVE_ARC4RANDOM ${APPLE})
set(HAVE_CLONEFILE ${APPLE})
set(HAVE_EXPLICIT_BZERO ${LINUX})
set(HAVE_EXPLICIT_MEMSET ${LINUX})
set(HAVE_FCHMOD ${UNIX})
set(HAVE_FILENO TRUE)
set(HAVE_FSEEKO ${UNIX})
set(HAVE_FTELLO ${UNIX})
set(HAVE_GETPROGNAME ${APPLE})
set(HAVE_LOCALTIME_R ${UNIX})
set(HAVE_LOCALTIME_S ${MSVC})
set(HAVE_MEMCPY_S ${MSVC})
set(HAVE_RANDOM ${UNIX})
set(HAVE_SETMODE ${APPLE})
set(HAVE_SNPRINTF TRUE)
set(HAVE_SNPRINTF_S FALSE)
set(HAVE_STRCASECMP ${UNIX})
set(HAVE_STRDUP TRUE)
set(HAVE_STRERROR_S ${MSVC})
set(HAVE_STRERRORLEN_S FALSE)
set(HAVE_STRICMP ${MSVC})
set(HAVE_STRNCPY_S ${MSVC})
set(HAVE_STRTOLL TRUE)
set(HAVE_STRTOULL TRUE)
check_include_files("sys/types.h;sys/stat.h;fts.h" HAVE_FTS_H)
# fts functions may be in external library
if(HAVE_FTS_H)
if(UNIX)
set(HAVE_FTS_H TRUE)
check_function_exists(fts_open HAVE_FTS_OPEN)
if(NOT HAVE_FTS_OPEN)
check_library_exists(fts fts_open "" HAVE_LIB_FTS)
else(NOT HAVE_FTS_OPEN)
set(HAVE_LIB_FTS "" CACHE INTERNAL "")
set(HAVE_LIB_FTS "")
endif(NOT HAVE_FTS_OPEN)
else(HAVE_FTS_H)
set(HAVE_LIB_FTS "" CACHE INTERNAL "")
endif(HAVE_FTS_H)
if(HAVE_LIB_FTS)
set(FTS_LIB fts CACHE INTERNAL "")
else()
set(FTS_LIB "" CACHE INTERNAL "")
set(HAVE_LIB_FTS "")
endif()
check_include_files(stdbool.h HAVE_STDBOOL_H)
check_include_files(strings.h HAVE_STRINGS_H)
check_include_files(unistd.h HAVE_UNISTD_H)
if(HAVE_LIB_FTS)
set(FTS_LIB fts)
else()
set(FTS_LIB "")
endif()
check_include_files(inttypes.h HAVE_INTTYPES_H_LIBZIP)
check_include_files(stdint.h HAVE_STDINT_H_LIBZIP)
check_include_files(sys/types.h HAVE_SYS_TYPES_H_LIBZIP)
set(HAVE_STDBOOL_H TRUE)
set(HAVE_STRINGS_H ${UNIX})
set(HAVE_UNISTD_H ${UNIX})
# TODO: fix test
# this test does not find __progname even when it exists
#check_symbol_exists(__progname stdlib.h HAVE___PROGNAME)
check_type_size(__int8 __INT8_LIBZIP)
check_type_size(int8_t INT8_T_LIBZIP)
check_type_size(uint8_t UINT8_T_LIBZIP)
check_type_size(__int16 __INT16_LIBZIP)
check_type_size(int16_t INT16_T_LIBZIP)
check_type_size(uint16_t UINT16_T_LIBZIP)
check_type_size(__int32 __INT32_LIBZIP)
check_type_size(int32_t INT32_T_LIBZIP)
check_type_size(uint32_t UINT32_T_LIBZIP)
check_type_size(__int64 __INT64_LIBZIP)
check_type_size(int64_t INT64_T_LIBZIP)
check_type_size(uint64_t UINT64_T_LIBZIP)
check_type_size("short" SHORT_LIBZIP)
check_type_size("int" INT_LIBZIP)
check_type_size("long" LONG_LIBZIP)
check_type_size("long long" LONG_LONG_LIBZIP)
check_type_size("off_t" SIZEOF_OFF_T)
check_type_size("size_t" SIZEOF_SIZE_T)
check_c_source_compiles("#include <sys/ioctl.h>
#include <linux/fs.h>
int main(int argc, char *argv[]) { unsigned long x = FICLONERANGE; }" HAVE_FICLONERANGE)
if(LINUX)
check_c_source_compiles("#include <sys/ioctl.h>
#include <linux/fs.h>
int main(int argc, char *argv[]) { unsigned long x = FICLONERANGE; }" HAVE_FICLONERANGE)
else()
set(HAVE_FICLONERANGE FALSE)
endif()
check_c_source_compiles("
int foo(char * _Nullable bar);
int main(int argc, char *argv[]) { }" HAVE_NULLABLE)
test_big_endian(WORDS_BIGENDIAN)
set(WORDS_BIGENDIAN FALSE)
if(MSVC)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
@@ -133,88 +113,19 @@ endif(MSVC)
# fixed size integral types
if(HAVE_INTTYPES_H_LIBZIP)
set(LIBZIP_TYPES_INCLUDE "#if !defined(__STDC_FORMAT_MACROS)
set(LIBZIP_TYPES_INCLUDE "#if !defined(__STDC_FORMAT_MACROS)
#define __STDC_FORMAT_MACROS 1
#endif
#include <inttypes.h>")
elseif(HAVE_STDINT_H_LIBZIP)
set(LIBZIP_TYPES_INCLUDE "#include <stdint.h>")
elseif(HAVE_SYS_TYPES_H_LIBZIP)
set(LIBZIP_TYPES_INCLUDE "#include <sys/types.h>")
endif()
if(HAVE_INT8_T_LIBZIP)
set(ZIP_INT8_T int8_t)
elseif(HAVE___INT8_LIBZIP)
set(ZIP_INT8_T __int8)
else()
set(ZIP_INT8_T "signed char")
endif()
if(HAVE_UINT8_T_LIBZIP)
set(ZIP_UINT8_T uint8_t)
elseif(HAVE___INT8_LIBZIP)
set(ZIP_UINT8_T "unsigned __int8")
else()
set(ZIP_UINT8_T "unsigned char")
endif()
if(HAVE_INT16_T_LIBZIP)
set(ZIP_INT16_T int16_t)
elseif(HAVE___INT16_LIBZIP)
set(INT16_T_LIBZIP __int16)
elseif(SHORT_LIBZIP EQUAL 2)
set(INT16_T_LIBZIP short)
endif()
if(HAVE_UINT16_T_LIBZIP)
set(ZIP_UINT16_T uint16_t)
elseif(HAVE___INT16_LIBZIP)
set(UINT16_T_LIBZIP "unsigned __int16")
elseif(SHORT_LIBZIP EQUAL 2)
set(UINT16_T_LIBZIP "unsigned short")
endif()
if(HAVE_INT32_T_LIBZIP)
set(ZIP_INT32_T int32_t)
elseif(HAVE___INT32_LIBZIP)
set(ZIP_INT32_T __int32)
elseif(INT_LIBZIP EQUAL 4)
set(ZIP_INT32_T int)
elseif(LONG_LIBZIP EQUAL 4)
set(ZIP_INT32_T long)
endif()
if(HAVE_UINT32_T_LIBZIP)
set(ZIP_UINT32_T uint32_t)
elseif(HAVE___INT32_LIBZIP)
set(ZIP_UINT32_T "unsigned __int32")
elseif(INT_LIBZIP EQUAL 4)
set(ZIP_UINT32_T "unsigned int")
elseif(LONG_LIBZIP EQUAL 4)
set(ZIP_UINT32_T "unsigned long")
endif()
if(HAVE_INT64_T_LIBZIP)
set(ZIP_INT64_T int64_t)
elseif(HAVE___INT64_LIBZIP)
set(ZIP_INT64_T __int64)
elseif(LONG_LIBZIP EQUAL 8)
set(ZIP_INT64_T long)
elseif(LONG_LONG_LIBZIP EQUAL 8)
set(ZIP_INT64_T "long long")
endif()
if(HAVE_UINT64_T_LIBZIP)
set(ZIP_UINT64_T uint64_t)
elseif(HAVE___INT64_LIBZIP)
set(ZIP_UINT64_T "unsigned __int64")
elseif(LONG_LIBZIP EQUAL 8)
set(ZIP_UINT64_T "unsigned long")
elseif(LONG_LONG_LIBZIP EQUAL 8)
set(ZIP_UINT64_T "unsigned long long")
endif()
set(ZIP_INT8_T int8_t)
set(ZIP_UINT8_T uint8_t)
set(ZIP_INT16_T int16_t)
set(ZIP_UINT16_T uint16_t)
set(ZIP_INT32_T int32_t)
set(ZIP_UINT32_T uint32_t)
set(ZIP_INT64_T int64_t)
set(ZIP_UINT64_T uint64_t)
if(HAVE_NULLABLE)
set(ZIP_NULLABLE_DEFINES)
+13
View File
@@ -0,0 +1,13 @@
add_library(pcsx2-lz4
lz4/lib/lz4.c
lz4/lib/lz4.h
)
target_include_directories(pcsx2-lz4 PUBLIC lz4/lib)
target_compile_definitions(pcsx2-lz4 PUBLIC
LZ4LIB_VISIBILITY=
)
add_library(LZ4::LZ4 ALIAS pcsx2-lz4)
disable_compiler_warnings_for_target(pcsx2-lz4)
Vendored Submodule
+1
Submodule 3rdparty/lz4/lz4 added at b8fd2d1530
+46
View File
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(SolutionDir)common\vsprops\BaseProjectConfig.props" />
<Import Project="$(SolutionDir)common\vsprops\WinSDK.props" />
<PropertyGroup Label="Globals">
<ProjectGuid>{39098635-446A-4FC3-9B1C-8609D94598A8}</ProjectGuid>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<PlatformToolset Condition="!$(Configuration.Contains(Clang))">$(DefaultPlatformToolset)</PlatformToolset>
<PlatformToolset Condition="$(Configuration.Contains(Clang))">ClangCL</PlatformToolset>
<WholeProgramOptimization Condition="$(Configuration.Contains(Release))">true</WholeProgramOptimization>
<UseDebugLibraries Condition="$(Configuration.Contains(Debug))">true</UseDebugLibraries>
<UseDebugLibraries Condition="!$(Configuration.Contains(Debug))">false</UseDebugLibraries>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings" />
<ImportGroup Label="PropertySheets">
<Import Project="..\DefaultProjectRootDir.props" />
<Import Project="..\3rdparty.props" />
<Import Condition="$(Configuration.Contains(Debug))" Project="..\..\common\vsprops\CodeGen_Debug.props" />
<Import Condition="$(Configuration.Contains(Devel))" Project="..\..\common\vsprops\CodeGen_Devel.props" />
<Import Condition="$(Configuration.Contains(Release))" Project="..\..\common\vsprops\CodeGen_Release.props" />
<Import Condition="!$(Configuration.Contains(Release))" Project="..\..\common\vsprops\IncrementalLinking.props" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>LZ4LIB_VISIBILITY=;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(ProjectDir)\lz4\lib</AdditionalIncludeDirectories>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
</ClCompile>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="lz4\lib\lz4.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="lz4\lib\lz4.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets" />
</Project>
+91
View File
@@ -0,0 +1,91 @@
This Font Software is licensed under the SIL Open Font License,
Version 1.1. This license is copied below, and is also available
with a FAQ at <http://scripts.sil.org/OFL>
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
+35273
View File
File diff suppressed because it is too large Load Diff
+15
View File
@@ -0,0 +1,15 @@
# More info: http://EditorConfig.org
root = true
# * here means any file type
[*]
end_of_line = crlf
insert_final_newline = true
# latin1 is a type of ASCII, should work with mbcs
[*.{h,c,cpp}]
charset = latin1
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
curly_bracket_next_line = false
+65
View File
@@ -0,0 +1,65 @@
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
# Visual Studio files
Debug/
Release/
*.user
.vs/
# Repository specific
test/test
test/galaga_nes.h
test/smw_snes.h
validator/validator
.vscode/*
+339
View File
@@ -0,0 +1,339 @@
# v11.1.0
* add rc_client_get_user_agent_clause to generate substring to include in client User-Agents
* add rc_client_can_pause function to control pause spam
* add achievement type and rarity to rc_api_fetch_game_data_response_t and rc_client_achievement_t
* add RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED for achievements that have been unlocked locally but not synced to the server
* add RC_CONSOLE_NEO_GEO_CD to supported consoles for chd file extension
* add hash logic for RC_CONSOLE_NINTENDO_3DS (note: added new file rhash/aes.c to support this)
* add hash logic for RC_CONSOLE_MS_DOS
* add game_hash and hardcore fields to rc_api_start_session_request_t and rc_api_ping_request_t
* add RC_FORMAT_FIXED1/2/3, RC_FORMAT_TENS, RC_FORMAT_HUNDREDS, RC_FORMAT_THOUSANDS, and RC_FORMAT_UNSIGNED_VALUE
* add RC_CONSOLE_STANDALONE
* add extern "C" and __cdecl attributes to public functions
* add __declspec(dllexport/dllimport) attributes to public functions via #define enablement
* add rc_version and rc_version_string functions for accessing version from external linkage
* add unicode path support to default filereader (Windows builds)
* add rc_mutex support for GEKKO (libogc)
* fix async_handle being returned when rc_client_begin_login is aborted synchronously
* fix logic error hashing CD files smaller than one sector
* fix read across region boundary in rc_libretro_memory_read
* fix RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW event not being raised if achievement is reset in the same frame that it's primed
* moved rc_util.h from src/ to include/
* initial (incomplete) support for rc_client_external_t and rc_client_raintegration_t
# v11.0.0
* add rc_client_t and related functions
* add RC_MEMSIZE_FLOAT_BE
* add Game Pak SRAM to GBA memory map
* add hash method for Super Cassettevision
* add PSP to potential consoles for chd iterator
* add content_type to rc_api_request_t for client to pass to server
* add rc_api_process_X_server_response methods to pass status_code and body_length to response processing functions
* add additional error codes to rc_api_process_login_response: RC_INVALID_CREDENTIALS, RC_EXPIRED_TOKEN, RC_ACCESS_DENIED
* rc_api_start_session now also returns unlocks without having to explicitly call rc_api_fetch_user_unlocks separately
* add validation warning for using hit target of 1 on ResetIf condition
* move compat.c up a directory and rename to rc_compat.c as it's shared by all subfolders
* move rc_libretro.c up a directory as it uses files from all subfolders
* convert loosely sized types to strongly sized types (unsigned -> uint32t, unsigned char -> uint8_t, etc)
# v10.7.1
* add rc_runtime_alloc
* add rc_libretro_memory_find_avail
* extract nginx errors from HTML returned for JSON endpoints
* fix real address for 32X extension RAM
* fix crash attempting to calculate gamecube hash for non-existent file
# v10.7.0
* add hash method and memory map for Gamecube
* add console enum, hash method, and memory map for DSi
* add console enum, hash method, and memory map for TI-83
* add console enum, hash method, and memory map for Uzebox
* add constant for rcheevos version; include in start session server API call
* fix SubSource calculations using float values
* fix game identification for homebrew Jaguar CD games
* fix game identification for CD with many files at root directory
* address _CRT_SECURE_NO_WARNINGS warnings
# v10.6.0
* add RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED
* use optimized comparators for most common condition logic
* fix game identification of psx ISOs that have extra slashes in their boot path
* fix game identification of ndd files
# v10.5.0
* add RC_MEMSIZE_MBF32_LE
* add RC_OPERATOR_XOR
* add RC_CONSOLE_ATARI_JAGUAR_CD and hash/memory map for Atari Jaguar CD
* add RC_CONSOLE_ARCADIA_2001 and hash/memory map for Arcadia 2001
* add RC_CONSOLE_INTERTON_VC_4000 and hash/memory map for Interton VC 4000
* add RC_CONSOLE_ELEKTOR_TV_GAMES_COMPUTER and hash/memory map for Elektor TV Games Computer
* split RC_CONSOLE_PC_ENGINE_CD off of RC_CONSOLE_PC_ENGINE
* add hash/memory map for RC_CONSOLE_NEO_GEO_CD
* add additional 256KB of RAM to memory map for RC_CONSOLE_SEGA_32X
* validation: don't report redundancy between trigger and non-trigger conditions
* validation: don't report range validation errors for float comparisons
* change default image host to media.retroachievements.org
* fix decoding of denormalized floats
* fix full line comments in the middle of Display: section causing RC_MISSING_DISPLAY_STRING
# v10.4.0
* add rc_libretro_hash_set_t with support for #SAVEDISK: m3u extension
* add rc_libretro_is_system_allowed for finer-grain control over core support
* fix measured value from hitcount not resetting while paused
* add RC_CONSOLE_WASM and hash/memory map for WASM-4
* add scratchpad memory to RC_CONSOLE_PLAYSTATION_2 memory map
* add hash/memory map for RC_CONSOLE_FAIRCHILD_CHANNEL_F
* add hash/memory map for RC_CONSOLE_COMMODORE_64
* add memory map for RC_CONSOLE_AMIGA
# v10.3.3
* add RC_CONSOLE_ARDUBOY and hash/memory map for Arduboy
* add display_name to rc_api_login_response_t
* detect logical conflicts and redundancies in validator
* fix tab sequences in JSON responses being turned into t
* fix overflow when float value has more than 9 digits after the decimal
* fix libretro memory mapping when disconnect mask breaks a region into multiple blocks
* fix non-virtualized file system call when reading some iso files
# v10.3.2
* fix RC_OPERAND_PRIOR for bit sizes other than RC_MEMSIZE_BIT_0
* add memory map and hash for Amstrad CPC
* fix an issue where fetch_game_data and fetch_user_unlocks could return RC_MISSING_VALUE instead of acknowledging a server error
# v10.3.1
* allow empty description in rc_api_init_update_leaderboard_request
* fix buffered n64 hash when no filereader is registered
* add memory map and hash for Mega Duck
# v10.3.0
* support for floating point memory sizes and logic
* add built-in macros for rich presence: @Number, @Score, @Centisecs, @Seconds, @Minutes, @ASCIIChar, @UnicodeChar
* add rapi functions for fetch_code_notes, update_code_note, upload_achievement, update_leaderboard, fetch_badge_range, and add_game_hash
* add lower_is_better and hidden flags to leaderboards in rc_api_fetch_game_data_response_t
* add achievements_remaining to rc_api_award_achievement_response_t
* add console enums for PC6000, PICO, MEGADUCK and ZEEBO
* add memory map for Dreamcast
* capture leaderboard/rich presence state in rc_runtime_progress data
* support for hashing Dreamcast bin/cues
* support for hashing buffered NDS ROMs
* fix prior for sizes smaller than a byte sometimes returning current value
# v10.2.0
* add RC_MEMSIZE_16_BITS_BE, RC_MEMSIZE_24_BITS_BE, and RC_MEMSIZE_32_BITS_BE
* add secondary flag for RC_CONDITION_MEASURED that tells the UI when to show progress as raw vs. as a percentage
* add rapi calls for fetch_leaderboard_info, fetch_achievement_info and fetch_game_list
* add hash support for RC_CONSOLE_PSP
* add RCHEEVOS_URL_SSL compile flag to use https in rurl functions
* add space to "PC Engine" label
* update RC_CONSOLE_INTELLIVISION memory map to acknowledge non-8-bit addresses
* standardize to z64 format when hashing RC_CONSOLE_N64
* prevent generating hash for PSX disc when requesting RC_CONSOLE_PLAYSTATION2
* fix wrong error message being returned when a leaderboard was only slightly malformed
# v10.1.0
* add RC_RUNTIME_EVENT_ACHIEVEMENT_UNPRIMED
* add rc_runtime_validate_addresses
* add external memory to memory map for Magnavox Odyssey 2
* fix memory map base address for NeoGeo Pocket
* fix bitcount always returning 0 when used in rich presence
# v10.0.0
* add rapi sublibrary for communicating with server (eliminates need for client-side JSON parsing; client must still
provide HTTP functionality). rurl is now deprecated
* renamed 'rhash.h' to 'rc_hash.h' to eliminate conflict with system headers, renamed 'rconsoles.h' and 'rurl.h' for
consistency
* split non-runtime functions out of 'rcheevos.h' as they're not needed by most clients
* allow ranges in rich presence lookups
* add rc_richpresence_size_lines function to fetch line associated to error when processing rich presence script
* add rc_runtime_invalidate_address function to disable achievements when an unknown address is queried
* add RC_CONDITION_RESET_NEXT_IF
* add RC_CONDITION_SUB_HITS
* support MAXOF operator ($) for leaderboard values using trigger syntax
* allow RC_CONDITION_PAUSE_IF and RC_CONDITION_RESET_IF in leaderboard value expression
* changed track parameter of rc_hash_cdreader_open_track_handler to support three virtual tracks:
RC_HASH_CDTRACK_FIRST_DATA, RC_HASH_CDTRACK_LAST and RC_HASH_CDTRACK_LARGEST.
* changed offset parameter of rc_hash_filereader_seek_handler and return value of rc_hash_filereader_tell_handler
from size_t to int64_t to support files larger than 2GB when compiling in 32-bit mode.
* reset to default cd reader if NULL is passed to rc_hash_init_custom_cdreader
* add hash support for RC_CONSOLE_DREAMCAST, RC_CONSOLE_PLAYSTATION_2, RC_CONSOLE_SUPERVISION, and RC_CONSOLE_TIC80
* ignore headers when generating hashs for RC_CONSOLE_PC_ENGINE and RC_CONSOLE_ATARI_7800
* require unique identifier when hashing RC_CONSOLE_SEGA_CD and RC_CONSOLE_SATURN discs
* add expansion memory to RC_CONSOLE_SG1000 memory map
* rename RC_CONSOLE_MAGNAVOX_ODYSSEY -> RC_CONSOLE_MAGNAVOX_ODYSSEY2
* rename RC_CONSOLE_AMIGA_ST -> RC_CONSOLE_ATARI_ST
* add RC_CONSOLE_SUPERVISION, RC_CONSOLE_SHARPX1, RC_CONSOLE_TIC80, RC_CONSOLE_THOMSONTO8
* fix error identifying largest track when track has multiple bins
* fix memory corruption error when cue track has more than 6 INDEXs
* several improvements to data storage for conditions (rc_memref_t and rc_memref_value_t structures have been modified)
# v9.2.0
* fix issue identifying some PC-FX titles where the boot code is not in the first data track
* add enums and labels for RC_CONSOLE_MAGNAVOX_ODYSSEY, RC_CONSOLE_SUPER_CASSETTEVISION, RC_CONSOLE_NEO_GEO_CD,
RC_CONSOLE_FAIRCHILD_CHANNEL_F, RC_CONSOLE_FM_TOWNS, RC_CONSOLE_ZX_SPECTRUM, RC_CONSOLE_GAME_AND_WATCH,
RC_CONSOLE_NOKIA_NGAGE, RC_CONSOLE_NINTENDO_3DS
# v9.1.0
* add hash support and memory map for RC_CONSOLE_MSX
* add hash support and memory map for RC_CONSOLE_PCFX
* include parent directory when hashing non-arcade titles in arcade mode
* support absolute paths in m3u
* make cue scanning case-insensitive
* expand SRAM mapping for RC_CONSOLE_WONDERSWAN
* fix display of measured value when another group has an unmeasured hit count
* fix memory read error when hashing file with no extension
* fix possible divide by zero when using RC_CONDITION_ADD_SOURCE/RC_CONDITION_SUB_SOURCE
* fix classification of secondary RC_CONSOLE_SATURN memory region
# v9.0.0
* new size: RC_MEMSIZE_BITCOUNT
* new flag: RC_CONDITION_OR_NEXT
* new flag: RC_CONDITION_TRIGGER
* new flag: RC_CONDITION_MEASURED_IF
* new operators: RC_OPERATOR_MULT / RC_OPERATOR_DIV
* is_bcd removed from memref - now part of RC_MEMSIZE
* add rc_runtime_t and associated functions
* add rc_hash_ functions
* add rc_error_str function
* add game_hash parameter to rc_url_award_cheevo
* remove hash parameter from rc_url_submit_lboard
* add rc_url_ping function
* add rc_console_ functions
# v8.1.0
* new flag: RC_CONDITION_MEASURED
* new flag: RC_CONDITION_ADD_ADDRESS
* add rc_evaluate_trigger - extended version of rc_test_trigger with more granular return codes
* make rc_evaluate_value return a signed int (was unsigned int)
* new formats: RC_FORMAT_MINUTES and RC_FORMAT_SECONDS_AS_MINUTES
* removed " Points" text from RC_FORMAT_SCORE format
* removed RC_FORMAT_OTHER format. "OTHER" format now parses to RC_FORMAT_SCORE
* bugfix: AddHits will now honor AndNext on previous condition
# v8.0.1
* bugfix: prevent null reference exception if rich presence contains condition without display string
* bugfix: 24-bit read from memory should only read 24-bits
# v8.0.0
* support for prior operand type
* support for AndNext condition flag
* support for rich presence
* bugfix: update delta/prior memory values while group is paused
* bugfix: allow floating point number without leading 0
* bugfix: support empty alt groups
# v7.1.1
* Address signed/unsigned mismatch warnings
# v7.1.0
* Added the RC_DISABLE_LUA macro to compile rcheevos without Lua support
# v7.0.2
* Make sure the code is C89-compliant
* Use 32-bit types in Lua
* Only evaluate Lua operands when the Lua state is not `NULL`
# v7.0.1
* Fix the alignment of memory allocations
# v7.0.0
* Removed **rjson**
# v6.5.0
* Added a schema for errors returned by the server
# v6.4.0
* Added an enumeration with the console identifiers used in RetroAchievements
# v6.3.1
* Pass the peek function and the user data to the Lua functions used in operands.
# v6.3.0
* Added **rurl**, an API to build URLs to access RetroAchievements web services.
# v6.2.0
* Added **rjson**, an API to easily decode RetroAchievements JSON files into C structures.
# v6.1.0
* Added support for 24-bit operands with the `'W'` prefix (`RC_OPERAND_24_BITS`)
# v6.0.2
* Only define RC_ALIGNMENT if it has not been already defined
# v6.0.1
* Use `sizeof(void*)` as a better default for `RC_ALIGNMENT`
# v6.0.0
* Simplified API: separate functions to get the buffer size and to parse `memaddr` into the provided buffer
* Fixed crash trying to call `rc_update_condition_pause` during a dry-run
* The callers are now responsible to pass down a scratch buffer to avoid accesses to out-of-scope memory
# v5.0.0
* Pre-compute if a condition has a pause condition in its group
* Added a pre-computed flag that tells if the condition set has at least one pause condition
* Removed the link to the previous condition in a condition set chain
# v4.0.0
* Fixed `ret` not being properly initialized in `rc_parse_trigger`
* Build the unit tests with optimizations and `-Wall` to help catch more issues
* Added `extern "C"` around the inclusion of the Lua headers so that **rcheevos** can be compiled cleanly as C++
* Exposed `rc_parse_value` and `rc_evaluate_value` to be used with rich presence
* Removed the `reset` and `dirty` flags from the external API
# v3.2.0
* Added the ability to reset triggers and leaderboards
* Add a function to parse a format string and return the format enum, and some unit tests for it
# v3.1.0
* Added `rc_format_value` to the API
# v3.0.1
* Fixed wrong 32-bit value on 64-bit platforms
# v3.0.0
* Removed function rc_evaluate_value from the API
# v2.0.0
* Removed leaderboard callbacks in favor of a simpler scheme
# v1.1.2
* Fixed NULL pointer deference when there's an error during the parse
# v1.1.1
* Removed unwanted garbage
* Should be v1.0.1 :/
# v1.0.0
* First version
+49 -49
View File
@@ -1,54 +1,54 @@
add_library(rcheevos
rcheevos/include/rcheevos.h
rcheevos/include/rc_api_editor.h
rcheevos/include/rc_api_info.h
rcheevos/include/rc_api_request.h
rcheevos/include/rc_api_runtime.h
rcheevos/include/rc_api_user.h
rcheevos/include/rc_client.h
rcheevos/include/rc_consoles.h
rcheevos/include/rc_error.h
rcheevos/include/rc_hash.h
rcheevos/include/rc_runtime.h
rcheevos/include/rc_runtime_types.h
rcheevos/include/rc_url.h
rcheevos/src/rapi/rc_api_common.c
rcheevos/src/rapi/rc_api_common.h
rcheevos/src/rapi/rc_api_editor.c
rcheevos/src/rapi/rc_api_info.c
rcheevos/src/rapi/rc_api_runtime.c
rcheevos/src/rapi/rc_api_user.c
rcheevos/src/rcheevos/alloc.c
rcheevos/src/rcheevos/condition.c
rcheevos/src/rcheevos/condset.c
rcheevos/src/rcheevos/consoleinfo.c
rcheevos/src/rcheevos/format.c
rcheevos/src/rcheevos/lboard.c
rcheevos/src/rcheevos/memref.c
rcheevos/src/rcheevos/operand.c
rcheevos/src/rcheevos/rc_internal.h
rcheevos/src/rcheevos/rc_validate.c
rcheevos/src/rcheevos/rc_validate.h
rcheevos/src/rcheevos/richpresence.c
rcheevos/src/rcheevos/runtime.c
rcheevos/src/rcheevos/runtime_progress.c
rcheevos/src/rcheevos/trigger.c
rcheevos/src/rcheevos/value.c
rcheevos/src/rc_client.c
rcheevos/src/rc_client_internal.h
rcheevos/src/rc_compat.c
rcheevos/src/rc_compat.h
rcheevos/src/rc_util.c
rcheevos/src/rc_util.h
rcheevos/src/rc_version.h
rcheevos/src/rhash/cdreader.c
rcheevos/src/rhash/hash.c
rcheevos/src/rhash/md5.c
rcheevos/src/rhash/md5.h
rcheevos/src/rurl/url.c
include/rcheevos.h
include/rc_api_editor.h
include/rc_api_info.h
include/rc_api_request.h
include/rc_api_runtime.h
include/rc_api_user.h
include/rc_client.h
include/rc_consoles.h
include/rc_error.h
include/rc_hash.h
include/rc_runtime.h
include/rc_runtime_types.h
include/rc_url.h
include/rc_util.h
src/rapi/rc_api_common.c
src/rapi/rc_api_common.h
src/rapi/rc_api_editor.c
src/rapi/rc_api_info.c
src/rapi/rc_api_runtime.c
src/rapi/rc_api_user.c
src/rcheevos/alloc.c
src/rcheevos/condition.c
src/rcheevos/condset.c
src/rcheevos/consoleinfo.c
src/rcheevos/format.c
src/rcheevos/lboard.c
src/rcheevos/memref.c
src/rcheevos/operand.c
src/rcheevos/rc_internal.h
src/rcheevos/rc_validate.c
src/rcheevos/rc_validate.h
src/rcheevos/richpresence.c
src/rcheevos/runtime.c
src/rcheevos/runtime_progress.c
src/rcheevos/trigger.c
src/rcheevos/value.c
src/rc_client.c
src/rc_client_internal.h
src/rc_compat.c
src/rc_compat.h
src/rc_util.c
src/rc_version.h
src/rhash/cdreader.c
src/rhash/hash.c
src/rhash/md5.c
src/rhash/md5.h
src/rurl/url.c
)
target_include_directories(rcheevos PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/rcheevos/include")
target_include_directories(rcheevos INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/rcheevos/include")
target_include_directories(rcheevos PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_include_directories(rcheevos INTERFACE "${CMAKE_CURRENT_SOURCE_DIR}/include")
target_compile_definitions(rcheevos PRIVATE "RC_DISABLE_LUA=1" "RCHEEVOS_URL_SSL")
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 RetroAchievements.org
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.
+2
View File
@@ -0,0 +1,2 @@
- aes and 3DS functions in hash.c removed, due to potential legal issues.
- .github/test/validator directories removed, as they are unnecessary.
+32
View File
@@ -0,0 +1,32 @@
// swift-tools-version: 5.8
// The swift-tools-version declares the minimum version of Swift required to build this package.
import PackageDescription
let package = Package(
name: "rcheevos",
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "rcheevos",
targets: ["rcheevos"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "rcheevos",
dependencies: [],
path: ".",
exclude: ["src/rcheevos/rc_libretro.c"],
sources: ["include", "src/rcheevos", "src/rapi", "src/rhash"],
publicHeadersPath: "include",
cSettings: [
.define("RC_DISABLE_LUA")
]),
]
)
+81
View File
@@ -0,0 +1,81 @@
# **rcheevos**
**rcheevos** is a set of C code, or a library if you will, that tries to make it easier for emulators to process [RetroAchievements](https://retroachievements.org) data, providing support for achievements and leaderboards for their players.
Keep in mind that **rcheevos** does *not* provide HTTP network connections. Clients must get data from RetroAchievements, and pass the response down to **rcheevos** for processing.
Not all structures defined by **rcheevos** can be created via the public API, but are exposed to allow interactions beyond just creation, destruction, and testing, such as the ones required by UI code that helps to create them.
## Lua
RetroAchievements previously considered the use of the [Lua](https://www.lua.org) language to expand the syntax supported for creating achievements.
To enable Lua support, you must compile with an additional compilation flag: `HAVE_LUA`, as neither the backend nor the UI for editing achievements are currently Lua-enabled. We do not foresee enabling it any time soon, but the code has not yet been completely eliminated as many of the low-level API fuctions have parameters for LUA data.
> **rcheevos** does *not* create or maintain a Lua state, you have to create your own state and provide it to **rcheevos** to be used when Lua-coded achievements are found. Calls to **rcheevos** may allocate and/or free additional memory as part of the Lua runtime.
Lua functions used in trigger operands receive two parameters: `peek`, which is used to read from the emulated system's memory, and `userdata`, which must be passed to `peek`. `peek`'s signature is the same as its C counterpart:
```lua
function peek(address, num_bytes, userdata)
```
## API
An understanding about how achievements are developed may be useful, you can read more about it [here](http://docs.retroachievements.org/Developer-docs/).
Most of the exposed APIs are documented [here](https://github.com/RetroAchievements/rcheevos/wiki)
### Return values
Any function in the rcheevos library that returns a success indicator will return `RC_OK` or one of the constants defined in `rc_error.h`.
To convert the return code into something human-readable, pass it to:
```c
const char* rc_error_str(int ret);
```
### Console identifiers
Platforms supported by RetroAchievements are enumerated in `rc_consoles.h`. Note that some consoles in the enum are not yet fully supported (may require a memory map or some way to uniquely identify games).
## Runtime support
Provides a set of functions for managing an active game - initializing and processing achievements, leaderboards, and rich presence. When important things occur, events are raised for the caller via a callback.
These are in `rc_runtime.h`.
Note: `rc_runtime_t` still requires the client implement all of the logic that calls the APIs to retrieve the data and perform the unlocks.
The `rc_client_t` functions wrap a `rc_runtime_t` and manage the API calls and other common functionality (like managing the user information, identifying/loading a game, and building the active/inactive achievements list for the UI). Please see [the wiki](https://github.com/RetroAchievements/rcheevos/wiki/rc_client-integration) for details on using the `rc_client_t` functions.
## Server Communication
**rapi** builds URLs to access many RetroAchievements web services. Its purpose it to just to free the developer from having to URL-encode parameters and build correct URLs that are valid for the server.
**rapi** does *not* make HTTP requests.
NOTE: **rapi** is a replacement for **rurl**. **rurl** has been deprecated.
NOTE: `rc_client` is the preferred way to have a client interact with the server.
These are in `rc_api_user.h`, `rc_api_runtime.h` and `rc_api_common.h`.
The basic process of making an **rapi** call is to initialize a params object, call a function to convert it to a URL, send that to the server, then pass the response to a function to convert it into a response object, and handle the response values.
An example can be found on the [rc_api_init_login_request](https://github.com/RetroAchievements/rcheevos/wiki/rc_api_init_login_request#example) page.
### Functions
Please see the [wiki](https://github.com/RetroAchievements/rcheevos/wiki) for details on the functions exposed for **rapi**.
## Game Identification
**rhash** provides logic for generating a RetroAchievements hash for a given game. There are two ways to use the API - you can pass the filename and let rhash open and process the file, or you can pass the buffered copy of the file to rhash if you've already loaded it into memory.
These are in `rc_hash.h`.
```c
int rc_hash_generate_from_buffer(char hash[33], int console_id, uint8_t* buffer, size_t buffer_size);
int rc_hash_generate_from_file(char hash[33], int console_id, const char* path);
```
+253
View File
@@ -0,0 +1,253 @@
#ifndef RC_API_EDITOR_H
#define RC_API_EDITOR_H
#include "rc_api_request.h"
#include <stdint.h>
RC_BEGIN_C_DECLS
/* --- 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 */
uint32_t 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 */
uint32_t 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 */
uint32_t num_notes;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_code_notes_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV 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 */
uint32_t game_id;
/* The address the note is associated to */
uint32_t 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;
RC_EXPORT int RC_CCONV rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV 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) */
uint32_t achievement_id;
/* The unique identifier of the game */
uint32_t 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 */
uint32_t points;
/* The category of the achievement */
uint32_t category;
/* The type of the achievement */
uint32_t type;
}
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 */
uint32_t achievement_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_update_achievement_response_t;
RC_EXPORT int RC_CCONV rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV 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) */
uint32_t leaderboard_id;
/* The unique identifier of the game */
uint32_t 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 */
uint32_t 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 */
uint32_t leaderboard_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_update_leaderboard_response_t;
RC_EXPORT int RC_CCONV rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV 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 */
uint32_t 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 */
uint32_t first_badge_id;
/* The numeric identifier of the first unassigned badge ID */
uint32_t next_badge_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_badge_range_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV 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) */
uint32_t game_id;
/* The unique identifier of the console for the game */
uint32_t 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 */
uint32_t game_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_add_game_hash_response_t;
RC_EXPORT int RC_CCONV rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response);
RC_END_C_DECLS
#endif /* RC_EDITOR_H */
+182
View File
@@ -0,0 +1,182 @@
#ifndef RC_API_INFO_H
#define RC_API_INFO_H
#include "rc_api_request.h"
#include <stdint.h>
#include <time.h>
RC_BEGIN_C_DECLS
/* --- 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 */
uint32_t achievement_id;
/* The 1-based index of the first entry to retrieve */
uint32_t first_entry;
/* The number of entries to retrieve */
uint32_t count;
/* Non-zero to only return unlocks earned by the user's friends */
uint32_t 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 */
uint32_t id;
/* The unique identifier of the game to which the leaderboard is associated */
uint32_t game_id;
/* The number of times the achievement has been awarded */
uint32_t num_awarded;
/* The number of players that have earned at least one achievement for the game */
uint32_t 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 */
uint32_t num_recently_awarded;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_achievement_info_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV 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 */
uint32_t leaderboard_id;
/* The number of entries to retrieve */
uint32_t count;
/* The 1-based index of the first entry to retrieve */
uint32_t 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 */
uint32_t rank;
/* The index of the entry */
uint32_t index;
/* The value of the entry */
int32_t 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 */
uint32_t 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 */
uint32_t 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 */
uint32_t 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 */
uint32_t num_entries;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_leaderboard_info_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV 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 */
uint32_t 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 */
uint32_t 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 */
uint32_t num_entries;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_games_list_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response);
RC_END_C_DECLS
#endif /* RC_API_INFO_H */
+64
View File
@@ -0,0 +1,64 @@
#ifndef RC_API_REQUEST_H
#define RC_API_REQUEST_H
#include "rc_error.h"
#include "rc_util.h"
#include <stddef.h>
RC_BEGIN_C_DECLS
/**
* 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;
/* The HTTP Content-Type of the POST data. */
const char* content_type;
/* Storage for the url and post_data */
rc_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 success, zero on failure) */
int succeeded;
/* Server-provided message associated to the failure */
const char* error_message;
/* Server-provided error code associated to the failure */
const char* error_code;
/* Storage for the response data */
rc_buffer_t buffer;
}
rc_api_response_t;
RC_EXPORT void RC_CCONV rc_api_destroy_request(rc_api_request_t* request);
RC_EXPORT void RC_CCONV rc_api_set_host(const char* hostname);
RC_EXPORT void RC_CCONV rc_api_set_image_host(const char* hostname);
typedef struct rc_api_server_response_t {
/* Pointer to the data returned from the server */
const char* body;
/* Length of data returned from the server (Content-Length) */
size_t body_length;
/* HTTP status code returned from the server */
int http_status_code;
} rc_api_server_response_t;
enum {
RC_API_SERVER_RESPONSE_CLIENT_ERROR = -1,
RC_API_SERVER_RESPONSE_RETRYABLE_CLIENT_ERROR = -2
};
RC_END_C_DECLS
#endif /* RC_API_REQUEST_H */
+310
View File
@@ -0,0 +1,310 @@
#ifndef RC_API_RUNTIME_H
#define RC_API_RUNTIME_H
#include "rc_api_request.h"
#include <stdint.h>
#include <time.h>
RC_BEGIN_C_DECLS
/* --- 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 */
uint32_t 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
RC_EXPORT int RC_CCONV 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 */
uint32_t game_id;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_resolve_hash_response_t;
RC_EXPORT int RC_CCONV rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV 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 */
uint32_t 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 */
uint32_t 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 */
uint8_t lower_is_better;
/* Non-zero if the leaderboard should not be displayed in a list of leaderboards */
uint8_t hidden;
}
rc_api_leaderboard_definition_t;
/* An achievement definition */
typedef struct rc_api_achievement_definition_t {
/* The unique identifier of the achievement */
uint32_t id;
/* The number of points the achievement is worth */
uint32_t points;
/* The achievement category (core, unofficial) */
uint32_t 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;
/* The achievement type (win/progression/missable) */
uint32_t type;
/* The approximate rarity of the achievement (X% of users have earned the achievement) */
float rarity;
/* The approximate rarity of the achievement in hardcore (X% of users have earned the achievement in hardcore) */
float rarity_hardcore;
}
rc_api_achievement_definition_t;
#define RC_ACHIEVEMENT_CATEGORY_CORE 3
#define RC_ACHIEVEMENT_CATEGORY_UNOFFICIAL 5
#define RC_ACHIEVEMENT_TYPE_STANDARD 0
#define RC_ACHIEVEMENT_TYPE_MISSABLE 1
#define RC_ACHIEVEMENT_TYPE_PROGRESSION 2
#define RC_ACHIEVEMENT_TYPE_WIN 3
/**
* Response data for a fetch game data request.
*/
typedef struct rc_api_fetch_game_data_response_t {
/* The unique identifier of the game */
uint32_t id;
/* The console associated to the game */
uint32_t 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 */
uint32_t num_achievements;
/* An array of leaderboards for the game */
rc_api_leaderboard_definition_t* leaderboards;
/* The number of items in the leaderboards array */
uint32_t num_leaderboards;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_game_data_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV 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 */
uint32_t game_id;
/* (optional) The current rich presence evaluation for the user */
const char* rich_presence;
/* (recommended) The hash associated to the game being played */
const char* game_hash;
/* Non-zero if hardcore is currently enabled (ignored if game_hash is null) */
uint32_t hardcore;
}
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;
RC_EXPORT int RC_CCONV rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV 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 */
uint32_t achievement_id;
/* Non-zero if the achievement was earned in hardcore */
uint32_t 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 */
uint32_t awarded_achievement_id;
/* The updated player score */
uint32_t new_player_score;
/* The updated player softcore score */
uint32_t new_player_score_softcore;
/* The number of achievements the user has not yet unlocked for this game
* (in hardcore/non-hardcore per hardcore flag in request) */
uint32_t achievements_remaining;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_award_achievement_response_t;
RC_EXPORT int RC_CCONV rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV 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 */
uint32_t leaderboard_id;
/* The value being submitted */
int32_t 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 */
uint32_t rank;
/* The value of the entry */
int32_t 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 */
int32_t submitted_score;
/* The player's best submitted value */
int32_t best_score;
/* The player's new rank within the leaderboard */
uint32_t new_rank;
/* The total number of entries in the leaderboard */
uint32_t 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 */
uint32_t num_top_entries;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_submit_lboard_entry_response_t;
RC_EXPORT int RC_CCONV rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response);
RC_END_C_DECLS
#endif /* RC_API_RUNTIME_H */
+149
View File
@@ -0,0 +1,149 @@
#ifndef RC_API_USER_H
#define RC_API_USER_H
#include "rc_api_request.h"
#include <stdint.h>
#include <time.h>
RC_BEGIN_C_DECLS
/* --- 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 */
uint32_t score;
/* The current softcore score of the player */
uint32_t score_softcore;
/* The number of unread messages waiting for the player on the web site */
uint32_t 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;
RC_EXPORT int RC_CCONV rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV 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 */
uint32_t game_id;
/* (recommended) The hash associated to the game being played */
const char* game_hash;
/* Non-zero if hardcore is currently enabled (ignored if game_hash is null) */
uint32_t hardcore;
}
rc_api_start_session_request_t;
/**
* Response data for an achievement unlock.
*/
typedef struct rc_api_unlock_entry_t {
/* The unique identifier of the unlocked achievement */
uint32_t achievement_id;
/* When the achievement was unlocked */
time_t when;
}
rc_api_unlock_entry_t;
/**
* Response data for a start session request.
*/
typedef struct rc_api_start_session_response_t {
/* An array of hardcore user unlocks */
rc_api_unlock_entry_t* hardcore_unlocks;
/* An array of user unlocks */
rc_api_unlock_entry_t* unlocks;
/* The number of items in the hardcore_unlocks array */
uint32_t num_hardcore_unlocks;
/* The number of items in the unlocks array */
uint32_t num_unlocks;
/* The server timestamp when the response was generated */
time_t server_now;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_start_session_response_t;
RC_EXPORT int RC_CCONV rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV 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 */
uint32_t game_id;
/* Non-zero to fetch hardcore unlocks, 0 to fetch non-hardcore unlocks */
uint32_t 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 */
uint32_t* achievement_ids;
/* The number of items in the achievement_ids array */
uint32_t num_achievement_ids;
/* Common server-provided response information */
rc_api_response_t response;
}
rc_api_fetch_user_unlocks_response_t;
RC_EXPORT int RC_CCONV rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params);
RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response);
RC_EXPORT int RC_CCONV rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response);
RC_EXPORT void RC_CCONV rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response);
RC_END_C_DECLS
#endif /* RC_API_H */
+677
View File
@@ -0,0 +1,677 @@
#ifndef RC_CLIENT_H
#define RC_CLIENT_H
#include "rc_api_request.h"
#include "rc_error.h"
#include <stddef.h>
#include <stdint.h>
#include <time.h>
RC_BEGIN_C_DECLS
/* implementation abstracted in rc_client_internal.h */
typedef struct rc_client_t rc_client_t;
typedef struct rc_client_async_handle_t rc_client_async_handle_t;
/*****************************************************************************\
| Callbacks |
\*****************************************************************************/
/**
* Callback used to read num_bytes bytes from memory starting at address into buffer.
* Returns the number of bytes read. A return value of 0 indicates the address was invalid.
*/
typedef uint32_t (RC_CCONV *rc_client_read_memory_func_t)(uint32_t address, uint8_t* buffer, uint32_t num_bytes, rc_client_t* client);
/**
* Internal method passed to rc_client_server_call_t to process the server response.
*/
typedef void (RC_CCONV *rc_client_server_callback_t)(const rc_api_server_response_t* server_response, void* callback_data);
/**
* Callback used to issue a request to the server.
*/
typedef void (RC_CCONV *rc_client_server_call_t)(const rc_api_request_t* request, rc_client_server_callback_t callback, void* callback_data, rc_client_t* client);
/**
* Generic callback for asynchronous eventing.
*/
typedef void (RC_CCONV *rc_client_callback_t)(int result, const char* error_message, rc_client_t* client, void* userdata);
/**
* Callback for logging or displaying a message.
*/
typedef void (RC_CCONV *rc_client_message_callback_t)(const char* message, const rc_client_t* client);
/*****************************************************************************\
| Runtime |
\*****************************************************************************/
/**
* Creates a new rc_client_t object.
*/
RC_EXPORT rc_client_t* RC_CCONV rc_client_create(rc_client_read_memory_func_t read_memory_function, rc_client_server_call_t server_call_function);
/**
* Releases resources associated to a rc_client_t object.
* Pointer will no longer be valid after making this call.
*/
RC_EXPORT void RC_CCONV rc_client_destroy(rc_client_t* client);
/**
* Sets whether hardcore is enabled (on by default).
* Can be called with a game loaded.
* Enabling hardcore with a game loaded will raise an RC_CLIENT_EVENT_RESET
* event. Processing will be disabled until rc_client_reset is called.
*/
RC_EXPORT void RC_CCONV rc_client_set_hardcore_enabled(rc_client_t* client, int enabled);
/**
* Gets whether hardcore is enabled (on by default).
*/
RC_EXPORT int RC_CCONV rc_client_get_hardcore_enabled(const rc_client_t* client);
/**
* Sets whether encore mode is enabled (off by default).
* Evaluated when loading a game. Has no effect while a game is loaded.
*/
RC_EXPORT void RC_CCONV rc_client_set_encore_mode_enabled(rc_client_t* client, int enabled);
/**
* Gets whether encore mode is enabled (off by default).
*/
RC_EXPORT int RC_CCONV rc_client_get_encore_mode_enabled(const rc_client_t* client);
/**
* Sets whether unofficial achievements should be loaded.
* Evaluated when loading a game. Has no effect while a game is loaded.
*/
RC_EXPORT void RC_CCONV rc_client_set_unofficial_enabled(rc_client_t* client, int enabled);
/**
* Gets whether unofficial achievements should be loaded.
*/
RC_EXPORT int RC_CCONV rc_client_get_unofficial_enabled(const rc_client_t* client);
/**
* Sets whether spectator mode is enabled (off by default).
* If enabled, events for achievement unlocks and leaderboard submissions will be
* raised, but server calls to actually perform the unlock/submit will not occur.
* Can be modified while a game is loaded. Evaluated at unlock/submit time.
* Cannot be modified if disabled before a game is loaded.
*/
RC_EXPORT void RC_CCONV rc_client_set_spectator_mode_enabled(rc_client_t* client, int enabled);
/**
* Gets whether spectator mode is enabled (off by default).
*/
RC_EXPORT int RC_CCONV rc_client_get_spectator_mode_enabled(const rc_client_t* client);
/**
* Attaches client-specific data to the runtime.
*/
RC_EXPORT void RC_CCONV rc_client_set_userdata(rc_client_t* client, void* userdata);
/**
* Gets the client-specific data attached to the runtime.
*/
RC_EXPORT void* RC_CCONV rc_client_get_userdata(const rc_client_t* client);
/**
* Sets the name of the server to use.
*/
RC_EXPORT void RC_CCONV rc_client_set_host(const rc_client_t* client, const char* hostname);
typedef uint64_t rc_clock_t;
typedef rc_clock_t (RC_CCONV *rc_get_time_millisecs_func_t)(const rc_client_t* client);
/**
* Specifies a function that returns a value that increases once per millisecond.
*/
RC_EXPORT void RC_CCONV rc_client_set_get_time_millisecs_function(rc_client_t* client, rc_get_time_millisecs_func_t handler);
/**
* Marks an async process as aborted. The associated callback will not be called.
*/
RC_EXPORT void RC_CCONV rc_client_abort_async(rc_client_t* client, rc_client_async_handle_t* async_handle);
/**
* Gets a clause that can be added to the User-Agent to identify the version of rcheevos being used.
*/
RC_EXPORT size_t RC_CCONV rc_client_get_user_agent_clause(rc_client_t* client, char buffer[], size_t buffer_size);
/*****************************************************************************\
| Logging |
\*****************************************************************************/
/**
* Sets the logging level and provides a callback to be called to do the logging.
*/
RC_EXPORT void RC_CCONV rc_client_enable_logging(rc_client_t* client, int level, rc_client_message_callback_t callback);
enum {
RC_CLIENT_LOG_LEVEL_NONE = 0,
RC_CLIENT_LOG_LEVEL_ERROR = 1,
RC_CLIENT_LOG_LEVEL_WARN = 2,
RC_CLIENT_LOG_LEVEL_INFO = 3,
RC_CLIENT_LOG_LEVEL_VERBOSE = 4,
NUM_RC_CLIENT_LOG_LEVELS = 5
};
/*****************************************************************************\
| User |
\*****************************************************************************/
/**
* Attempt to login a user.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_login_with_password(rc_client_t* client,
const char* username, const char* password,
rc_client_callback_t callback, void* callback_userdata);
/**
* Attempt to login a user.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_login_with_token(rc_client_t* client,
const char* username, const char* token,
rc_client_callback_t callback, void* callback_userdata);
/**
* Logout the user.
*/
RC_EXPORT void RC_CCONV rc_client_logout(rc_client_t* client);
typedef struct rc_client_user_t {
const char* display_name;
const char* username;
const char* token;
uint32_t score;
uint32_t score_softcore;
uint32_t num_unread_messages;
} rc_client_user_t;
/**
* Gets information about the logged in user. Will return NULL if the user is not logged in.
*/
RC_EXPORT const rc_client_user_t* RC_CCONV rc_client_get_user_info(const rc_client_t* client);
/**
* Gets the URL for the user's profile picture.
* Returns RC_OK on success.
*/
RC_EXPORT int RC_CCONV rc_client_user_get_image_url(const rc_client_user_t* user, char buffer[], size_t buffer_size);
typedef struct rc_client_user_game_summary_t {
uint32_t num_core_achievements;
uint32_t num_unofficial_achievements;
uint32_t num_unlocked_achievements;
uint32_t num_unsupported_achievements;
uint32_t points_core;
uint32_t points_unlocked;
} rc_client_user_game_summary_t;
/**
* Gets a breakdown of the number of achievements in the game, and how many the user has unlocked.
* Used for the "You have unlocked X of Y achievements" message shown when the game starts.
*/
RC_EXPORT void RC_CCONV rc_client_get_user_game_summary(const rc_client_t* client, rc_client_user_game_summary_t* summary);
/*****************************************************************************\
| Game |
\*****************************************************************************/
/**
* Start loading an unidentified game.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_identify_and_load_game(rc_client_t* client,
uint32_t console_id, const char* file_path,
const uint8_t* data, size_t data_size,
rc_client_callback_t callback, void* callback_userdata);
/**
* Start loading a game.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_game(rc_client_t* client, const char* hash,
rc_client_callback_t callback, void* callback_userdata);
/**
* Unloads the current game.
*/
RC_EXPORT void RC_CCONV rc_client_unload_game(rc_client_t* client);
typedef struct rc_client_game_t {
uint32_t id;
uint32_t console_id;
const char* title;
const char* hash;
const char* badge_name;
} rc_client_game_t;
/**
* Get information about the current game. Returns NULL if no game is loaded.
*/
RC_EXPORT const rc_client_game_t* RC_CCONV rc_client_get_game_info(const rc_client_t* client);
/**
* Gets the URL for the game image.
* Returns RC_OK on success.
*/
RC_EXPORT int RC_CCONV rc_client_game_get_image_url(const rc_client_game_t* game, char buffer[], size_t buffer_size);
/**
* Changes the active disc in a multi-disc game.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_change_media(rc_client_t* client, const char* file_path,
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
/*****************************************************************************\
| Subsets |
\*****************************************************************************/
typedef struct rc_client_subset_t {
uint32_t id;
const char* title;
char badge_name[16];
uint32_t num_achievements;
uint32_t num_leaderboards;
} rc_client_subset_t;
RC_EXPORT const rc_client_subset_t* RC_CCONV rc_client_get_subset_info(rc_client_t* client, uint32_t subset_id);
/*****************************************************************************\
| Achievements |
\*****************************************************************************/
enum {
RC_CLIENT_ACHIEVEMENT_STATE_INACTIVE = 0, /* unprocessed */
RC_CLIENT_ACHIEVEMENT_STATE_ACTIVE = 1, /* eligible to trigger */
RC_CLIENT_ACHIEVEMENT_STATE_UNLOCKED = 2, /* earned by user */
RC_CLIENT_ACHIEVEMENT_STATE_DISABLED = 3, /* not supported by this version of the runtime */
NUM_RC_CLIENT_ACHIEVEMENT_STATES = 4
};
enum {
RC_CLIENT_ACHIEVEMENT_CATEGORY_NONE = 0,
RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE = (1 << 0),
RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL = (1 << 1),
RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE_AND_UNOFFICIAL = RC_CLIENT_ACHIEVEMENT_CATEGORY_CORE | RC_CLIENT_ACHIEVEMENT_CATEGORY_UNOFFICIAL
};
enum {
RC_CLIENT_ACHIEVEMENT_TYPE_STANDARD = 0,
RC_CLIENT_ACHIEVEMENT_TYPE_MISSABLE = 1,
RC_CLIENT_ACHIEVEMENT_TYPE_PROGRESSION = 2,
RC_CLIENT_ACHIEVEMENT_TYPE_WIN = 3
};
enum {
RC_CLIENT_ACHIEVEMENT_BUCKET_UNKNOWN = 0,
RC_CLIENT_ACHIEVEMENT_BUCKET_LOCKED = 1,
RC_CLIENT_ACHIEVEMENT_BUCKET_UNLOCKED = 2,
RC_CLIENT_ACHIEVEMENT_BUCKET_UNSUPPORTED = 3,
RC_CLIENT_ACHIEVEMENT_BUCKET_UNOFFICIAL = 4,
RC_CLIENT_ACHIEVEMENT_BUCKET_RECENTLY_UNLOCKED = 5,
RC_CLIENT_ACHIEVEMENT_BUCKET_ACTIVE_CHALLENGE = 6,
RC_CLIENT_ACHIEVEMENT_BUCKET_ALMOST_THERE = 7,
RC_CLIENT_ACHIEVEMENT_BUCKET_UNSYNCED = 8,
NUM_RC_CLIENT_ACHIEVEMENT_BUCKETS = 9
};
enum {
RC_CLIENT_ACHIEVEMENT_UNLOCKED_NONE = 0,
RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE = (1 << 0),
RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE = (1 << 1),
RC_CLIENT_ACHIEVEMENT_UNLOCKED_BOTH = RC_CLIENT_ACHIEVEMENT_UNLOCKED_SOFTCORE | RC_CLIENT_ACHIEVEMENT_UNLOCKED_HARDCORE
};
typedef struct rc_client_achievement_t {
const char* title;
const char* description;
char badge_name[8];
char measured_progress[24];
float measured_percent;
uint32_t id;
uint32_t points;
time_t unlock_time;
uint8_t state;
uint8_t category;
uint8_t bucket;
uint8_t unlocked;
/* minimum version: 11.1 */
float rarity;
float rarity_hardcore;
uint8_t type;
} rc_client_achievement_t;
/**
* Get information about an achievement. Returns NULL if not found.
*/
RC_EXPORT const rc_client_achievement_t* RC_CCONV rc_client_get_achievement_info(rc_client_t* client, uint32_t id);
/**
* Gets the URL for the achievement image.
* Returns RC_OK on success.
*/
RC_EXPORT int RC_CCONV rc_client_achievement_get_image_url(const rc_client_achievement_t* achievement, int state, char buffer[], size_t buffer_size);
typedef struct rc_client_achievement_bucket_t {
rc_client_achievement_t** achievements;
uint32_t num_achievements;
const char* label;
uint32_t subset_id;
uint8_t bucket_type;
} rc_client_achievement_bucket_t;
typedef struct rc_client_achievement_list_t {
rc_client_achievement_bucket_t* buckets;
uint32_t num_buckets;
} rc_client_achievement_list_t;
enum {
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_LOCK_STATE = 0,
RC_CLIENT_ACHIEVEMENT_LIST_GROUPING_PROGRESS = 1
};
/**
* Creates a list of achievements matching the specified category and grouping.
* Returns an allocated list that must be free'd by calling rc_client_destroy_achievement_list.
*/
RC_EXPORT rc_client_achievement_list_t* RC_CCONV rc_client_create_achievement_list(rc_client_t* client, int category, int grouping);
/**
* Destroys a list allocated by rc_client_get_achievement_list.
*/
RC_EXPORT void RC_CCONV rc_client_destroy_achievement_list(rc_client_achievement_list_t* list);
/**
* Returns non-zero if there are any achievements that can be queried through rc_client_create_achievement_list().
*/
RC_EXPORT int RC_CCONV rc_client_has_achievements(rc_client_t* client);
/*****************************************************************************\
| Leaderboards |
\*****************************************************************************/
enum {
RC_CLIENT_LEADERBOARD_STATE_INACTIVE = 0,
RC_CLIENT_LEADERBOARD_STATE_ACTIVE = 1,
RC_CLIENT_LEADERBOARD_STATE_TRACKING = 2,
RC_CLIENT_LEADERBOARD_STATE_DISABLED = 3,
NUM_RC_CLIENT_LEADERBOARD_STATES = 4
};
enum {
RC_CLIENT_LEADERBOARD_FORMAT_TIME = 0,
RC_CLIENT_LEADERBOARD_FORMAT_SCORE = 1,
RC_CLIENT_LEADERBOARD_FORMAT_VALUE = 2,
NUM_RC_CLIENT_LEADERBOARD_FORMATS = 3
};
#define RC_CLIENT_LEADERBOARD_DISPLAY_SIZE 24
typedef struct rc_client_leaderboard_t {
const char* title;
const char* description;
const char* tracker_value;
uint32_t id;
uint8_t state;
uint8_t format;
uint8_t lower_is_better;
} rc_client_leaderboard_t;
/**
* Get information about a leaderboard. Returns NULL if not found.
*/
RC_EXPORT const rc_client_leaderboard_t* RC_CCONV rc_client_get_leaderboard_info(const rc_client_t* client, uint32_t id);
typedef struct rc_client_leaderboard_tracker_t {
char display[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
uint32_t id;
} rc_client_leaderboard_tracker_t;
typedef struct rc_client_leaderboard_bucket_t {
rc_client_leaderboard_t** leaderboards;
uint32_t num_leaderboards;
const char* label;
uint32_t subset_id;
uint8_t bucket_type;
} rc_client_leaderboard_bucket_t;
typedef struct rc_client_leaderboard_list_t {
rc_client_leaderboard_bucket_t* buckets;
uint32_t num_buckets;
} rc_client_leaderboard_list_t;
enum {
RC_CLIENT_LEADERBOARD_BUCKET_UNKNOWN = 0,
RC_CLIENT_LEADERBOARD_BUCKET_INACTIVE = 1,
RC_CLIENT_LEADERBOARD_BUCKET_ACTIVE = 2,
RC_CLIENT_LEADERBOARD_BUCKET_UNSUPPORTED = 3,
RC_CLIENT_LEADERBOARD_BUCKET_ALL = 4,
NUM_RC_CLIENT_LEADERBOARD_BUCKETS = 5
};
enum {
RC_CLIENT_LEADERBOARD_LIST_GROUPING_NONE = 0,
RC_CLIENT_LEADERBOARD_LIST_GROUPING_TRACKING = 1
};
/**
* Creates a list of leaderboards matching the specified grouping.
* Returns an allocated list that must be free'd by calling rc_client_destroy_leaderboard_list.
*/
RC_EXPORT rc_client_leaderboard_list_t* RC_CCONV rc_client_create_leaderboard_list(rc_client_t* client, int grouping);
/**
* Destroys a list allocated by rc_client_get_leaderboard_list.
*/
RC_EXPORT void RC_CCONV rc_client_destroy_leaderboard_list(rc_client_leaderboard_list_t* list);
/**
* Returns non-zero if the current game has any leaderboards.
*/
RC_EXPORT int RC_CCONV rc_client_has_leaderboards(rc_client_t* client);
typedef struct rc_client_leaderboard_entry_t {
const char* user;
char display[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
time_t submitted;
uint32_t rank;
uint32_t index;
} rc_client_leaderboard_entry_t;
typedef struct rc_client_leaderboard_entry_list_t {
rc_client_leaderboard_entry_t* entries;
uint32_t num_entries;
int32_t user_index;
} rc_client_leaderboard_entry_list_t;
typedef void (RC_CCONV *rc_client_fetch_leaderboard_entries_callback_t)(int result, const char* error_message,
rc_client_leaderboard_entry_list_t* list, rc_client_t* client, void* callback_userdata);
/**
* Fetches a list of leaderboard entries from the server.
* Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_leaderboard_entries(rc_client_t* client, uint32_t leaderboard_id,
uint32_t first_entry, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata);
/**
* Fetches a list of leaderboard entries from the server containing the logged-in user.
* Callback receives an allocated list that must be free'd by calling rc_client_destroy_leaderboard_entry_list.
*/
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_fetch_leaderboard_entries_around_user(rc_client_t* client, uint32_t leaderboard_id,
uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata);
/**
* Gets the URL for the profile picture of the user associated to a leaderboard entry.
* Returns RC_OK on success.
*/
RC_EXPORT int RC_CCONV rc_client_leaderboard_entry_get_user_image_url(const rc_client_leaderboard_entry_t* entry, char buffer[], size_t buffer_size);
/**
* Destroys a list allocated by rc_client_begin_fetch_leaderboard_entries or rc_client_begin_fetch_leaderboard_entries_around_user.
*/
RC_EXPORT void RC_CCONV rc_client_destroy_leaderboard_entry_list(rc_client_leaderboard_entry_list_t* list);
/**
* Used for scoreboard events. Contains the response from the server when a leaderboard entry is submitted.
* NOTE: This structure is only valid within the event callback. If you want to make use of the data outside
* of the callback, you should create copies of both the top entries and usernames within.
*/
typedef struct rc_client_leaderboard_scoreboard_entry_t {
/* The user associated to the entry */
const char* username;
/* The rank of the entry */
uint32_t rank;
/* The value of the entry */
char score[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
} rc_client_leaderboard_scoreboard_entry_t;
typedef struct rc_client_leaderboard_scoreboard_t {
/* The ID of the leaderboard which was submitted */
uint32_t leaderboard_id;
/* The value that was submitted */
char submitted_score[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
/* The player's best submitted value */
char best_score[RC_CLIENT_LEADERBOARD_DISPLAY_SIZE];
/* The player's new rank within the leaderboard */
uint32_t new_rank;
/* The total number of entries in the leaderboard */
uint32_t num_entries;
/* An array of the top entries for the leaderboard */
rc_client_leaderboard_scoreboard_entry_t* top_entries;
/* The number of items in the top_entries array */
uint32_t num_top_entries;
} rc_client_leaderboard_scoreboard_t;
/*****************************************************************************\
| Rich Presence |
\*****************************************************************************/
/**
* Returns non-zero if the current game supports rich presence.
*/
RC_EXPORT int RC_CCONV rc_client_has_rich_presence(rc_client_t* client);
/**
* Gets the current rich presence message.
* Returns the number of characters written to buffer.
*/
RC_EXPORT size_t RC_CCONV rc_client_get_rich_presence_message(rc_client_t* client, char buffer[], size_t buffer_size);
/*****************************************************************************\
| Processing |
\*****************************************************************************/
enum {
RC_CLIENT_EVENT_TYPE_NONE = 0,
RC_CLIENT_EVENT_ACHIEVEMENT_TRIGGERED = 1, /* [achievement] was earned by the player */
RC_CLIENT_EVENT_LEADERBOARD_STARTED = 2, /* [leaderboard] attempt has started */
RC_CLIENT_EVENT_LEADERBOARD_FAILED = 3, /* [leaderboard] attempt failed */
RC_CLIENT_EVENT_LEADERBOARD_SUBMITTED = 4, /* [leaderboard] attempt submitted */
RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_SHOW = 5, /* [achievement] challenge indicator should be shown */
RC_CLIENT_EVENT_ACHIEVEMENT_CHALLENGE_INDICATOR_HIDE = 6, /* [achievement] challenge indicator should be hidden */
RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_SHOW = 7, /* progress indicator should be shown for [achievement] */
RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_HIDE = 8, /* progress indicator should be hidden */
RC_CLIENT_EVENT_ACHIEVEMENT_PROGRESS_INDICATOR_UPDATE = 9, /* progress indicator should be updated to reflect new badge/progress for [achievement] */
RC_CLIENT_EVENT_LEADERBOARD_TRACKER_SHOW = 10, /* [leaderboard_tracker] should be shown */
RC_CLIENT_EVENT_LEADERBOARD_TRACKER_HIDE = 11, /* [leaderboard_tracker] should be hidden */
RC_CLIENT_EVENT_LEADERBOARD_TRACKER_UPDATE = 12, /* [leaderboard_tracker] updated */
RC_CLIENT_EVENT_LEADERBOARD_SCOREBOARD = 13, /* [leaderboard_scoreboard] possibly-new ranking received for [leaderboard] */
RC_CLIENT_EVENT_RESET = 14, /* emulated system should be reset (as the result of enabling hardcore) */
RC_CLIENT_EVENT_GAME_COMPLETED = 15, /* all achievements for the game have been earned */
RC_CLIENT_EVENT_SERVER_ERROR = 16, /* an API response returned a [server_error] and will not be retried */
RC_CLIENT_EVENT_DISCONNECTED = 17, /* an unlock request could not be completed and is pending */
RC_CLIENT_EVENT_RECONNECTED = 18 /* all pending unlocks have been completed */
};
typedef struct rc_client_server_error_t {
const char* error_message;
const char* api;
int result;
uint32_t related_id;
} rc_client_server_error_t;
typedef struct rc_client_event_t {
uint32_t type;
rc_client_achievement_t* achievement;
rc_client_leaderboard_t* leaderboard;
rc_client_leaderboard_tracker_t* leaderboard_tracker;
rc_client_leaderboard_scoreboard_t* leaderboard_scoreboard;
rc_client_server_error_t* server_error;
} rc_client_event_t;
/**
* Callback used to notify the client when certain events occur.
*/
typedef void (RC_CCONV *rc_client_event_handler_t)(const rc_client_event_t* event, rc_client_t* client);
/**
* Provides a callback for event handling.
*/
RC_EXPORT void RC_CCONV rc_client_set_event_handler(rc_client_t* client, rc_client_event_handler_t handler);
/**
* Provides a callback for reading memory.
*/
RC_EXPORT void RC_CCONV rc_client_set_read_memory_function(rc_client_t* client, rc_client_read_memory_func_t handler);
/**
* Determines if there are any active achievements/leaderboards/rich presence that need processing.
*/
RC_EXPORT int RC_CCONV rc_client_is_processing_required(rc_client_t* client);
/**
* Processes achievements for the current frame.
*/
RC_EXPORT void RC_CCONV rc_client_do_frame(rc_client_t* client);
/**
* Processes the periodic queue.
* Called internally by rc_client_do_frame.
* Should be explicitly called if rc_client_do_frame is not being called because emulation is paused.
*/
RC_EXPORT void RC_CCONV rc_client_idle(rc_client_t* client);
/**
* Determines if a sufficient amount of frames have been processed since the last call to rc_client_can_pause.
* Should not be called unless the client is trying to pause.
* If false is returned, and frames_remaining is not NULL, frames_remaining will be set to the number of frames
* still required before pause is allowed, which can be converted to a time in seconds for displaying to the user.
*/
RC_EXPORT int RC_CCONV rc_client_can_pause(rc_client_t* client, uint32_t* frames_remaining);
/**
* Informs the runtime that the emulator has been reset. Will reset all achievements and leaderboards
* to their initial state (includes hiding indicators/trackers).
*/
RC_EXPORT void RC_CCONV rc_client_reset(rc_client_t* client);
/**
* Gets the number of bytes needed to serialized the runtime state.
*/
RC_EXPORT size_t RC_CCONV rc_client_progress_size(rc_client_t* client);
/**
* Serializes the runtime state into a buffer.
* Returns RC_OK on success, or an error indicator.
*/
RC_EXPORT int RC_CCONV rc_client_serialize_progress(rc_client_t* client, uint8_t* buffer);
/**
* Deserializes the runtime state from a buffer.
* Returns RC_OK on success, or an error indicator.
*/
RC_EXPORT int RC_CCONV rc_client_deserialize_progress(rc_client_t* client, const uint8_t* serialized);
RC_END_C_DECLS
#endif /* RC_RUNTIME_H */
+85
View File
@@ -0,0 +1,85 @@
#ifndef RC_CLIENT_RAINTEGRATION_H
#define RC_CLIENT_RAINTEGRATION_H
#ifndef _WIN32
#undef RC_CLIENT_SUPPORTS_RAINTEGRATION /* Windows required for RAIntegration */
#endif
#include <stdint.h>
#include "rc_export.h"
RC_BEGIN_C_DECLS
typedef struct rc_client_t rc_client_t; /* forward reference; in rc_client.h */
/* types needed to implement raintegration */
typedef struct rc_client_raintegration_menu_item_t {
const char* label;
uint32_t id;
uint8_t checked;
uint8_t enabled;
} rc_client_raintegration_menu_item_t;
typedef struct rc_client_raintegration_menu_t {
rc_client_raintegration_menu_item_t* items;
uint32_t num_items;
} rc_client_raintegration_menu_t;
enum {
RC_CLIENT_RAINTEGRATION_EVENT_TYPE_NONE = 0,
RC_CLIENT_RAINTEGRATION_EVENT_MENUITEM_CHECKED_CHANGED = 1, /* [menu_item] checked changed */
RC_CLIENT_RAINTEGRATION_EVENT_HARDCORE_CHANGED = 2, /* hardcore was enabled or disabled */
RC_CLIENT_RAINTEGRATION_EVENT_PAUSE = 3 /* emulated system should be paused */
};
typedef struct rc_client_raintegration_event_t {
uint32_t type;
const rc_client_raintegration_menu_item_t* menu_item;
} rc_client_raintegration_event_t;
typedef void (RC_CCONV *rc_client_raintegration_event_handler_t)(const rc_client_raintegration_event_t* event,
rc_client_t* client);
typedef void (RC_CCONV *rc_client_raintegration_write_memory_func_t)(uint32_t address, uint8_t* buffer,
uint32_t num_bytes, rc_client_t* client);
/* types needed to integrate raintegration */
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
#ifndef RC_CLIENT_SUPPORTS_EXTERNAL
#define RC_CLIENT_SUPPORTS_EXTERNAL /* external rc_client required for RAIntegration */
#endif
#include <wtypes.h> /* HWND */
#include "rc_client.h"
RC_EXPORT rc_client_async_handle_t* RC_CCONV rc_client_begin_load_raintegration(rc_client_t* client,
const wchar_t* search_directory, HWND main_window_handle,
const char* client_name, const char* client_version,
rc_client_callback_t callback, void* callback_userdata);
RC_EXPORT void RC_CCONV rc_client_unload_raintegration(rc_client_t* client);
RC_EXPORT void RC_CCONV rc_client_raintegration_update_main_window_handle(rc_client_t* client, HWND main_window_handle);
RC_EXPORT const rc_client_raintegration_menu_t* RC_CCONV rc_client_raintegration_get_menu(const rc_client_t* client);
RC_EXPORT void RC_CCONV rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu);
RC_EXPORT void RC_CCONV rc_client_raintegration_update_menu_item(const rc_client_t* client, const rc_client_raintegration_menu_item_t* menu_item);
RC_EXPORT int RC_CCONV rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t nMenuItemId);
RC_EXPORT void RC_CCONV rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler);
RC_EXPORT void RC_CCONV rc_client_raintegration_set_event_handler(rc_client_t* client,
rc_client_raintegration_event_handler_t handler);
#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */
RC_END_C_DECLS
#endif /* RC_CLIENT_RAINTEGRATION_H */
+137
View File
@@ -0,0 +1,137 @@
#ifndef RC_CONSOLES_H
#define RC_CONSOLES_H
#include "rc_export.h"
#include <stdint.h>
RC_BEGIN_C_DECLS
/*****************************************************************************\
| Console identifiers |
\*****************************************************************************/
enum {
RC_CONSOLE_UNKNOWN = 0,
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_NINTENDO_DSI = 78,
RC_CONSOLE_TI83 = 79,
RC_CONSOLE_UZEBOX = 80,
RC_CONSOLE_HUBS = 100,
RC_CONSOLE_EVENTS = 101,
RC_CONSOLE_STANDALONE = 102
};
RC_EXPORT const char* RC_CCONV rc_console_name(uint32_t 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 {
uint32_t start_address; /* first address of block as queried by RetroAchievements */
uint32_t end_address; /* last address of block as queried by RetroAchievements */
uint32_t real_address; /* real address for first address of block */
uint8_t 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;
uint32_t num_regions;
}
rc_memory_regions_t;
RC_EXPORT const rc_memory_regions_t* RC_CCONV rc_console_memory_regions(uint32_t console_id);
RC_END_C_DECLS
#endif /* RC_CONSOLES_H */
+55
View File
@@ -0,0 +1,55 @@
#ifndef RC_ERROR_H
#define RC_ERROR_H
#include "rc_export.h"
RC_BEGIN_C_DECLS
/*****************************************************************************\
| 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,
RC_API_FAILURE = -27,
RC_LOGIN_REQUIRED = -28,
RC_NO_GAME_LOADED = -29,
RC_HARDCORE_DISABLED = -30,
RC_ABORTED = -31,
RC_NO_RESPONSE = -32,
RC_ACCESS_DENIED = -33,
RC_INVALID_CREDENTIALS = -34,
RC_EXPIRED_TOKEN = -35
};
RC_EXPORT const char* RC_CCONV rc_error_str(int ret);
RC_END_C_DECLS
#endif /* RC_ERROR_H */
+100
View File
@@ -0,0 +1,100 @@
#ifndef RC_EXPORT_H
#define RC_EXPORT_H
/* These macros control how callbacks and public functions are defined */
/* RC_SHARED should be defined when building rcheevos as a shared library (e.g. dll/dylib/so). External code should not define this macro. */
/* RC_STATIC should be defined when building rcheevos as a static library. External code should also define this macro. */
/* RC_IMPORT should be defined for external code using rcheevos as a shared library. */
/* For compatibility, if none of these three macros are defined, then the build is assumed to be RC_STATIC */
#if !defined(RC_SHARED) && !defined(RC_STATIC) && !defined(RC_IMPORT)
#define RC_STATIC
#endif
#if (defined(RC_SHARED) && defined(RC_STATIC)) || (defined(RC_SHARED) && defined(RC_IMPORT)) || (defined(RC_STATIC) && defined(RC_IMPORT))
#error RC_SHARED, RC_STATIC, and RC_IMPORT are mutually exclusive
#endif
/* RC_BEGIN_C_DECLS and RC_END_C_DECLS should be used for all headers, to enforce C linkage and the C calling convention */
/* RC_BEGIN_C_DECLS should be placed after #include's and before header declarations */
/* RC_END_C_DECLS should be placed after header declarations */
/* example usage
*
* #ifndef RC_HEADER_H
* #define RC_HEADER_H
*
* #include <stdint.h>
*
* RC_BEGIN_C_DECLS
*
* uint8_t rc_function(void);
*
* RC_END_C_DECLS
*
* #endif
*/
#ifdef __cplusplus
#define RC_BEGIN_C_DECLS extern "C" {
#define RC_END_C_DECLS }
#else
#define RC_BEGIN_C_DECLS
#define RC_END_C_DECLS
#endif
/* RC_CCONV should be used for public functions and callbacks, to enforce the cdecl calling convention, if applicable */
/* RC_CCONV should be placed after the return type, and between the ( and * for callbacks */
/* example usage */
/* void RC_CCONV rc_function(void) */
/* void (RC_CCONV *rc_callback)(void) */
#if defined(_WIN32)
/* Windows compilers will ignore __cdecl when not applicable */
#define RC_CCONV __cdecl
#elif defined(__GNUC__) && defined(__i386__)
/* GNU C compilers will warn if cdecl is defined on an unsupported platform */
#define RC_CCONV __attribute__((cdecl))
#else
#define RC_CCONV
#endif
/* RC_EXPORT should be used for public functions */
/* RC_EXPORT will provide necessary hints for shared library usage, if applicable */
/* RC_EXPORT should be placed before the return type */
/* example usage */
/* RC_EXPORT void rc_function(void) */
#ifdef RC_SHARED
#if defined(_WIN32)
#define RC_EXPORT __declspec(dllexport)
#elif defined(__GNUC__) && __GNUC__ >= 4
#define RC_EXPORT __attribute__((visibility("default")))
#else
#define RC_EXPORT
#endif
#endif
#ifdef RC_IMPORT
#if defined(_WIN32)
#define RC_EXPORT __declspec(dllimport)
#elif defined(__GNUC__) && __GNUC__ >= 4
#define RC_EXPORT __attribute__((visibility("default")))
#else
#define RC_EXPORT
#endif
#endif
#ifdef RC_STATIC
#if defined(__GNUC__) && __GNUC__ >= 4
#define RC_EXPORT __attribute__((visibility("default")))
#else
#define RC_EXPORT
#endif
#endif
#endif /* RC_EXPORT_H */
+130
View File
@@ -0,0 +1,130 @@
#ifndef RC_HASH_H
#define RC_HASH_H
#include <stddef.h>
#include <stdio.h>
#include <stdint.h>
#include "rc_consoles.h"
RC_BEGIN_C_DECLS
/* ===================================================== */
/* generates a hash from a block of memory.
* returns non-zero on success, or zero on failure.
*/
RC_EXPORT int RC_CCONV rc_hash_generate_from_buffer(char hash[33], uint32_t 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.
*/
RC_EXPORT int RC_CCONV rc_hash_generate_from_file(char hash[33], uint32_t console_id, const char* path);
/* ===================================================== */
/* data for rc_hash_iterate
*/
typedef struct rc_hash_iterator
{
const uint8_t* buffer;
size_t buffer_size;
uint8_t consoles[12];
int index;
const char* path;
} rc_hash_iterator_t;
/* 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)
*/
RC_EXPORT void RC_CCONV rc_hash_initialize_iterator(struct rc_hash_iterator* iterator, const char* path, const uint8_t* buffer, size_t buffer_size);
/* releases resources associated to a rc_hash_iterator
*/
RC_EXPORT void RC_CCONV 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.
*/
RC_EXPORT int RC_CCONV 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_CCONV *rc_hash_message_callback)(const char*);
RC_EXPORT void RC_CCONV rc_hash_init_error_message_callback(rc_hash_message_callback callback);
/* specifies a function to call for verbose logging */
RC_EXPORT void rc_hash_init_verbose_message_callback(rc_hash_message_callback callback);
/* ===================================================== */
/* opens a file */
typedef void* (RC_CCONV *rc_hash_filereader_open_file_handler)(const char* path_utf8);
/* moves the file pointer - standard fseek parameters */
typedef void (RC_CCONV *rc_hash_filereader_seek_handler)(void* file_handle, int64_t offset, int origin);
/* locates the file pointer */
typedef int64_t (RC_CCONV *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_CCONV *rc_hash_filereader_read_handler)(void* file_handle, void* buffer, size_t requested_bytes);
/* closes the file */
typedef void (RC_CCONV *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;
};
RC_EXPORT void RC_CCONV rc_hash_init_custom_filereader(struct rc_hash_filereader* reader);
/* ===================================================== */
#define RC_HASH_CDTRACK_FIRST_DATA ((uint32_t)-1) /* the first data track (skip audio tracks) */
#define RC_HASH_CDTRACK_LAST ((uint32_t)-2) /* the last data/audio track */
#define RC_HASH_CDTRACK_LARGEST ((uint32_t)-3) /* the largest data/audio track */
#define RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION ((uint32_t)-4) /* the first data/audio track of the second session */
/* opens a track from the specified file. see the RC_HASH_CDTRACK_ defines for special tracks.
* returns a handle to be passed to the other functions, or NULL if the track could not be opened.
*/
typedef void* (RC_CCONV *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_CCONV *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_CCONV *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_CCONV *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;
};
RC_EXPORT void RC_CCONV rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader);
RC_EXPORT void RC_CCONV rc_hash_init_default_cdreader(void);
RC_EXPORT void RC_CCONV rc_hash_init_custom_cdreader(struct rc_hash_cdreader* reader);
/* ===================================================== */
RC_END_C_DECLS
#endif /* RC_HASH_H */
+152
View File
@@ -0,0 +1,152 @@
#ifndef RC_RUNTIME_H
#define RC_RUNTIME_H
#include "rc_error.h"
#include <stddef.h>
#include <stdint.h>
RC_BEGIN_C_DECLS
/*****************************************************************************\
| 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 uint32_t(RC_CCONV *rc_runtime_peek_t)(uint32_t address, uint32_t num_bytes, void* ud);
/*****************************************************************************\
| Runtime |
\*****************************************************************************/
typedef struct rc_runtime_trigger_t {
uint32_t id;
rc_trigger_t* trigger;
void* buffer;
rc_memref_t* invalid_memref;
uint8_t md5[16];
int32_t serialized_size;
uint8_t owns_memrefs;
}
rc_runtime_trigger_t;
typedef struct rc_runtime_lboard_t {
uint32_t id;
int32_t value;
rc_lboard_t* lboard;
void* buffer;
rc_memref_t* invalid_memref;
uint8_t md5[16];
uint32_t serialized_size;
uint8_t owns_memrefs;
}
rc_runtime_lboard_t;
typedef struct rc_runtime_richpresence_t {
rc_richpresence_t* richpresence;
void* buffer;
struct rc_runtime_richpresence_t* previous;
uint8_t md5[16];
uint8_t owns_memrefs;
}
rc_runtime_richpresence_t;
typedef struct rc_runtime_t {
rc_runtime_trigger_t* triggers;
uint32_t trigger_count;
uint32_t trigger_capacity;
rc_runtime_lboard_t* lboards;
uint32_t lboard_count;
uint32_t 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;
uint8_t owns_self;
}
rc_runtime_t;
RC_EXPORT rc_runtime_t* RC_CCONV rc_runtime_alloc(void);
RC_EXPORT void RC_CCONV rc_runtime_init(rc_runtime_t* runtime);
RC_EXPORT void RC_CCONV rc_runtime_destroy(rc_runtime_t* runtime);
RC_EXPORT int RC_CCONV rc_runtime_activate_achievement(rc_runtime_t* runtime, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx);
RC_EXPORT void RC_CCONV rc_runtime_deactivate_achievement(rc_runtime_t* runtime, uint32_t id);
RC_EXPORT rc_trigger_t* RC_CCONV rc_runtime_get_achievement(const rc_runtime_t* runtime, uint32_t id);
RC_EXPORT int RC_CCONV rc_runtime_get_achievement_measured(const rc_runtime_t* runtime, uint32_t id, unsigned* measured_value, unsigned* measured_target);
RC_EXPORT int RC_CCONV rc_runtime_format_achievement_measured(const rc_runtime_t* runtime, uint32_t id, char *buffer, size_t buffer_size);
RC_EXPORT int RC_CCONV rc_runtime_activate_lboard(rc_runtime_t* runtime, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx);
RC_EXPORT void RC_CCONV rc_runtime_deactivate_lboard(rc_runtime_t* runtime, uint32_t id);
RC_EXPORT rc_lboard_t* RC_CCONV rc_runtime_get_lboard(const rc_runtime_t* runtime, uint32_t id);
RC_EXPORT int RC_CCONV rc_runtime_format_lboard_value(char* buffer, int size, int32_t value, int format);
RC_EXPORT int RC_CCONV rc_runtime_activate_richpresence(rc_runtime_t* runtime, const char* script, lua_State* L, int funcs_idx);
RC_EXPORT int RC_CCONV rc_runtime_get_richpresence(const rc_runtime_t* runtime, char* buffer, size_t 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,
RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED
};
typedef struct rc_runtime_event_t {
uint32_t id;
int32_t value;
uint8_t type;
}
rc_runtime_event_t;
typedef void (RC_CCONV *rc_runtime_event_handler_t)(const rc_runtime_event_t* runtime_event);
RC_EXPORT void RC_CCONV 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);
RC_EXPORT void RC_CCONV rc_runtime_reset(rc_runtime_t* runtime);
typedef int (RC_CCONV *rc_runtime_validate_address_t)(uint32_t address);
RC_EXPORT void RC_CCONV rc_runtime_validate_addresses(rc_runtime_t* runtime, rc_runtime_event_handler_t event_handler, rc_runtime_validate_address_t validate_handler);
RC_EXPORT void RC_CCONV rc_runtime_invalidate_address(rc_runtime_t* runtime, uint32_t address);
RC_EXPORT int RC_CCONV rc_runtime_progress_size(const rc_runtime_t* runtime, lua_State* L);
RC_EXPORT int RC_CCONV rc_runtime_serialize_progress(void* buffer, const rc_runtime_t* runtime, lua_State* L);
RC_EXPORT int RC_CCONV rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L);
RC_END_C_DECLS
#endif /* RC_RUNTIME_H */
+429
View File
@@ -0,0 +1,429 @@
#ifndef RC_RUNTIME_TYPES_H
#define RC_RUNTIME_TYPES_H
#include "rc_error.h"
#include <stddef.h>
#include <stdint.h>
RC_BEGIN_C_DECLS
#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 uint32_t(RC_CCONV *rc_peek_t)(uint32_t address, uint32_t 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_FLOAT_BE,
RC_MEMSIZE_VARIABLE
};
typedef struct rc_memref_value_t {
/* The current value of this memory reference. */
uint32_t value;
/* The last differing value of this memory reference. */
uint32_t prior;
/* The size of the value. */
uint8_t size;
/* True if the value changed this frame. */
uint8_t changed;
/* The value type of the value (for variables) */
uint8_t 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 */
uint8_t 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. */
uint32_t 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. */
uint32_t 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 */
uint8_t type;
/* the actual RC_MEMSIZE of the operand - memref.size may differ */
uint8_t size;
}
rc_operand_t;
RC_EXPORT int RC_CCONV 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. */
uint32_t required_hits;
/* Number of hits so far. */
uint32_t current_hits;
/* The next condition in the chain. */
rc_condition_t* next;
/* The type of the condition. (RC_CONDITION_*) */
uint8_t type;
/* The comparison operator to use. (RC_OPERATOR_*) */
uint8_t oper; /* operator is a reserved word in C++. */
/* Set if the condition needs to processed as part of the "check if paused" pass. (bool) */
uint8_t pause;
/* Whether or not the condition evaluated true on the last check. (bool) */
uint8_t is_true;
/* Unique identifier of optimized comparator to use. (RC_PROCESSING_COMPARE_*) */
uint8_t optimized_comparator;
};
/*****************************************************************************\
| 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. */
uint8_t has_pause;
/* True if the set is currently paused. */
uint8_t is_paused;
/* True if the set has indirect memory references. */
uint8_t 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. */
uint32_t measured_value;
/* The target state of the MEASURED condition */
uint32_t measured_target;
/* The current state of the trigger */
uint8_t state;
/* True if at least one condition has a non-zero hit count */
uint8_t has_hits;
/* True if at least one condition has a non-zero required hit count */
uint8_t has_required_hits;
/* True if the measured value should be displayed as a percentage */
uint8_t measured_as_percent;
};
RC_EXPORT int RC_CCONV rc_trigger_size(const char* memaddr);
RC_EXPORT rc_trigger_t* RC_CCONV rc_parse_trigger(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
RC_EXPORT int RC_CCONV rc_evaluate_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L);
RC_EXPORT int RC_CCONV rc_test_trigger(rc_trigger_t* trigger, rc_peek_t peek, void* ud, lua_State* L);
RC_EXPORT void RC_CCONV 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;
};
RC_EXPORT int RC_CCONV rc_value_size(const char* memaddr);
RC_EXPORT rc_value_t* RC_CCONV rc_parse_value(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
RC_EXPORT int32_t RC_CCONV 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;
uint8_t state;
};
RC_EXPORT int RC_CCONV rc_lboard_size(const char* memaddr);
RC_EXPORT rc_lboard_t* RC_CCONV rc_parse_lboard(void* buffer, const char* memaddr, lua_State* L, int funcs_ndx);
RC_EXPORT int RC_CCONV rc_evaluate_lboard(rc_lboard_t* lboard, int32_t* value, rc_peek_t peek, void* peek_ud, lua_State* L);
RC_EXPORT void RC_CCONV 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,
RC_FORMAT_FIXED1,
RC_FORMAT_FIXED2,
RC_FORMAT_FIXED3,
RC_FORMAT_TENS,
RC_FORMAT_HUNDREDS,
RC_FORMAT_THOUSANDS,
RC_FORMAT_UNSIGNED_VALUE
};
RC_EXPORT int RC_CCONV rc_parse_format(const char* format_str);
RC_EXPORT int RC_CCONV rc_format_value(char* buffer, int size, int32_t value, int format);
/*****************************************************************************\
| Rich Presence |
\*****************************************************************************/
typedef struct rc_richpresence_lookup_item_t rc_richpresence_lookup_item_t;
struct rc_richpresence_lookup_item_t {
uint32_t first;
uint32_t 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;
uint8_t 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;
uint8_t 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;
};
RC_EXPORT int RC_CCONV rc_richpresence_size(const char* script);
RC_EXPORT int RC_CCONV rc_richpresence_size_lines(const char* script, int* lines_read);
RC_EXPORT rc_richpresence_t* RC_CCONV rc_parse_richpresence(void* buffer, const char* script, lua_State* L, int funcs_ndx);
RC_EXPORT int RC_CCONV rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L);
RC_EXPORT void RC_CCONV rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, lua_State* L);
RC_EXPORT int RC_CCONV rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, lua_State* L);
RC_EXPORT void RC_CCONV rc_reset_richpresence(rc_richpresence_t* self);
RC_END_C_DECLS
#endif /* RC_RUNTIME_TYPES_H */
+36
View File
@@ -0,0 +1,36 @@
#ifndef RC_URL_H
#define RC_URL_H
#include "rc_export.h"
#include <stddef.h>
RC_BEGIN_C_DECLS
RC_EXPORT int RC_CCONV 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);
RC_EXPORT int RC_CCONV rc_url_submit_lboard(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned lboard_id, int value);
RC_EXPORT int RC_CCONV rc_url_get_lboard_entries(char* buffer, size_t size, unsigned lboard_id, unsigned first_index, unsigned count);
RC_EXPORT int RC_CCONV rc_url_get_lboard_entries_near_user(char* buffer, size_t size, unsigned lboard_id, const char* user_name, unsigned count);
RC_EXPORT int RC_CCONV rc_url_get_gameid(char* buffer, size_t size, const char* hash);
RC_EXPORT int RC_CCONV rc_url_get_patch(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid);
RC_EXPORT int RC_CCONV rc_url_get_badge_image(char* buffer, size_t size, const char* badge_name);
RC_EXPORT int RC_CCONV rc_url_login_with_password(char* buffer, size_t size, const char* user_name, const char* password);
RC_EXPORT int RC_CCONV rc_url_login_with_token(char* buffer, size_t size, const char* user_name, const char* login_token);
RC_EXPORT int RC_CCONV rc_url_get_unlock_list(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid, int hardcore);
RC_EXPORT int RC_CCONV rc_url_post_playing(char* buffer, size_t size, const char* user_name, const char* login_token, unsigned gameid);
RC_EXPORT int RC_CCONV 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);
RC_END_C_DECLS
#endif /* RC_URL_H */
+51
View File
@@ -0,0 +1,51 @@
#ifndef RC_UTIL_H
#define RC_UTIL_H
#include "rc_export.h"
#include <stddef.h>
#include <stdint.h>
RC_BEGIN_C_DECLS
/**
* A block of memory for variable length data (like strings and arrays).
*/
typedef struct rc_buffer_chunk_t {
/* The current location where data is being written */
uint8_t* write;
/* The first byte past the end of data where writing cannot occur */
uint8_t* end;
/* The first byte of the data */
uint8_t* start;
/* The next block in the allocated memory chain */
struct rc_buffer_chunk_t* next;
}
rc_buffer_chunk_t;
/**
* A preallocated block of memory for variable length data (like strings and arrays).
*/
typedef struct rc_buffer_t {
/* The chunk data (will point at the local data member) */
struct rc_buffer_chunk_t chunk;
/* Small chunk of memory pre-allocated for the chunk */
uint8_t data[256];
}
rc_buffer_t;
void rc_buffer_init(rc_buffer_t* buffer);
void rc_buffer_destroy(rc_buffer_t* buffer);
uint8_t* rc_buffer_reserve(rc_buffer_t* buffer, size_t amount);
void rc_buffer_consume(rc_buffer_t* buffer, const uint8_t* start, uint8_t* end);
void* rc_buffer_alloc(rc_buffer_t* buffer, size_t amount);
char* rc_buffer_strcpy(rc_buffer_t* buffer, const char* src);
char* rc_buffer_strncpy(rc_buffer_t* buffer, const char* src, size_t len);
uint32_t rc_djb2(const char* input);
void rc_format_md5(char checksum[33], const uint8_t digest[16]);
RC_END_C_DECLS
#endif /* RC_UTIL_H */
+8
View File
@@ -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 */
+47 -47
View File
@@ -30,59 +30,59 @@
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<ClCompile Include="rcheevos\src\rapi\rc_api_common.c" />
<ClCompile Include="rcheevos\src\rapi\rc_api_editor.c" />
<ClCompile Include="rcheevos\src\rapi\rc_api_info.c" />
<ClCompile Include="rcheevos\src\rapi\rc_api_runtime.c" />
<ClCompile Include="rcheevos\src\rapi\rc_api_user.c" />
<ClCompile Include="rcheevos\src\rcheevos\alloc.c" />
<ClCompile Include="rcheevos\src\rcheevos\condition.c" />
<ClCompile Include="rcheevos\src\rcheevos\condset.c" />
<ClCompile Include="rcheevos\src\rcheevos\consoleinfo.c" />
<ClCompile Include="rcheevos\src\rcheevos\format.c" />
<ClCompile Include="rcheevos\src\rcheevos\lboard.c" />
<ClCompile Include="rcheevos\src\rcheevos\memref.c" />
<ClCompile Include="rcheevos\src\rcheevos\operand.c" />
<ClCompile Include="rcheevos\src\rcheevos\rc_validate.c" />
<ClCompile Include="rcheevos\src\rcheevos\richpresence.c" />
<ClCompile Include="rcheevos\src\rcheevos\runtime.c" />
<ClCompile Include="rcheevos\src\rcheevos\runtime_progress.c" />
<ClCompile Include="rcheevos\src\rcheevos\trigger.c" />
<ClCompile Include="rcheevos\src\rcheevos\value.c" />
<ClCompile Include="rcheevos\src\rc_client.c" />
<ClCompile Include="rcheevos\src\rc_compat.c" />
<ClCompile Include="rcheevos\src\rc_util.c" />
<ClCompile Include="rcheevos\src\rhash\hash.c" />
<ClCompile Include="rcheevos\src\rhash\md5.c" />
<ClCompile Include="rcheevos\src\rurl\url.c" />
<ClCompile Include="src\rapi\rc_api_common.c" />
<ClCompile Include="src\rapi\rc_api_editor.c" />
<ClCompile Include="src\rapi\rc_api_info.c" />
<ClCompile Include="src\rapi\rc_api_runtime.c" />
<ClCompile Include="src\rapi\rc_api_user.c" />
<ClCompile Include="src\rcheevos\alloc.c" />
<ClCompile Include="src\rcheevos\condition.c" />
<ClCompile Include="src\rcheevos\condset.c" />
<ClCompile Include="src\rcheevos\consoleinfo.c" />
<ClCompile Include="src\rcheevos\format.c" />
<ClCompile Include="src\rcheevos\lboard.c" />
<ClCompile Include="src\rcheevos\memref.c" />
<ClCompile Include="src\rcheevos\operand.c" />
<ClCompile Include="src\rcheevos\rc_validate.c" />
<ClCompile Include="src\rcheevos\richpresence.c" />
<ClCompile Include="src\rcheevos\runtime.c" />
<ClCompile Include="src\rcheevos\runtime_progress.c" />
<ClCompile Include="src\rcheevos\trigger.c" />
<ClCompile Include="src\rcheevos\value.c" />
<ClCompile Include="src\rc_client.c" />
<ClCompile Include="src\rc_compat.c" />
<ClCompile Include="src\rc_util.c" />
<ClCompile Include="src\rhash\hash.c" />
<ClCompile Include="src\rhash\md5.c" />
<ClCompile Include="src\rurl\url.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="rcheevos\include\rcheevos.h" />
<ClInclude Include="rcheevos\include\rc_api_editor.h" />
<ClInclude Include="rcheevos\include\rc_api_info.h" />
<ClInclude Include="rcheevos\include\rc_api_request.h" />
<ClInclude Include="rcheevos\include\rc_api_runtime.h" />
<ClInclude Include="rcheevos\include\rc_api_user.h" />
<ClInclude Include="rcheevos\include\rc_client.h" />
<ClInclude Include="rcheevos\include\rc_consoles.h" />
<ClInclude Include="rcheevos\include\rc_error.h" />
<ClInclude Include="rcheevos\include\rc_hash.h" />
<ClInclude Include="rcheevos\include\rc_runtime.h" />
<ClInclude Include="rcheevos\include\rc_runtime_types.h" />
<ClInclude Include="rcheevos\include\rc_url.h" />
<ClInclude Include="rcheevos\src\rapi\rc_api_common.h" />
<ClInclude Include="rcheevos\src\rcheevos\rc_internal.h" />
<ClInclude Include="rcheevos\src\rcheevos\rc_validate.h" />
<ClInclude Include="rcheevos\src\rc_client_internal.h" />
<ClInclude Include="rcheevos\src\rc_compat.h" />
<ClInclude Include="rcheevos\src\rc_util.h" />
<ClInclude Include="rcheevos\src\rc_version.h" />
<ClInclude Include="rcheevos\src\rhash\md5.h" />
<ClInclude Include="include\rcheevos.h" />
<ClInclude Include="include\rc_api_editor.h" />
<ClInclude Include="include\rc_api_info.h" />
<ClInclude Include="include\rc_api_request.h" />
<ClInclude Include="include\rc_api_runtime.h" />
<ClInclude Include="include\rc_api_user.h" />
<ClInclude Include="include\rc_client.h" />
<ClInclude Include="include\rc_consoles.h" />
<ClInclude Include="include\rc_error.h" />
<ClInclude Include="include\rc_hash.h" />
<ClInclude Include="include\rc_runtime.h" />
<ClInclude Include="include\rc_runtime_types.h" />
<ClInclude Include="include\rc_url.h" />
<ClInclude Include="include\rc_util.h" />
<ClInclude Include="src\rapi\rc_api_common.h" />
<ClInclude Include="src\rcheevos\rc_internal.h" />
<ClInclude Include="src\rcheevos\rc_validate.h" />
<ClInclude Include="src\rc_client_internal.h" />
<ClInclude Include="src\rc_compat.h" />
<ClInclude Include="src\rc_version.h" />
<ClInclude Include="src\rhash\md5.h" />
</ItemGroup>
<ItemDefinitionGroup>
<ClCompile>
<PreprocessorDefinitions>_CRT_NONSTDC_NO_WARNINGS;RC_DISABLE_LUA;RCHEEVOS_URL_SSL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(ProjectDir)rcheevos\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>$(ProjectDir)include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<WarningLevel>TurnOffAllWarnings</WarningLevel>
</ClCompile>
</ItemDefinitionGroup>
+48 -46
View File
@@ -18,130 +18,132 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="rcheevos\src\rapi\rc_api_editor.c">
<ClCompile Include="src\rapi\rc_api_editor.c">
<Filter>rapi</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rapi\rc_api_info.c">
<ClCompile Include="src\rapi\rc_api_info.c">
<Filter>rapi</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rapi\rc_api_runtime.c">
<ClCompile Include="src\rapi\rc_api_runtime.c">
<Filter>rapi</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rapi\rc_api_user.c">
<ClCompile Include="src\rapi\rc_api_user.c">
<Filter>rapi</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rapi\rc_api_common.c">
<ClCompile Include="src\rapi\rc_api_common.c">
<Filter>rapi</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rhash\hash.c">
<ClCompile Include="src\rhash\hash.c">
<Filter>rhash</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rhash\md5.c">
<ClCompile Include="src\rhash\md5.c">
<Filter>rhash</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rurl\url.c">
<ClCompile Include="src\rurl\url.c">
<Filter>rurl</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rc_client.c" />
<ClCompile Include="rcheevos\src\rc_compat.c" />
<ClCompile Include="rcheevos\src\rc_util.c" />
<ClCompile Include="rcheevos\src\rcheevos\richpresence.c">
<ClCompile Include="src\rc_client.c" />
<ClCompile Include="src\rc_compat.c" />
<ClCompile Include="src\rc_util.c" />
<ClCompile Include="src\rcheevos\richpresence.c">
<Filter>rcheevos</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rcheevos\runtime.c">
<ClCompile Include="src\rcheevos\runtime.c">
<Filter>rcheevos</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rcheevos\runtime_progress.c">
<ClCompile Include="src\rcheevos\runtime_progress.c">
<Filter>rcheevos</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rcheevos\trigger.c">
<ClCompile Include="src\rcheevos\trigger.c">
<Filter>rcheevos</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rcheevos\value.c">
<ClCompile Include="src\rcheevos\value.c">
<Filter>rcheevos</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rcheevos\alloc.c">
<ClCompile Include="src\rcheevos\alloc.c">
<Filter>rcheevos</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rcheevos\condition.c">
<ClCompile Include="src\rcheevos\condition.c">
<Filter>rcheevos</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rcheevos\condset.c">
<ClCompile Include="src\rcheevos\condset.c">
<Filter>rcheevos</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rcheevos\consoleinfo.c">
<ClCompile Include="src\rcheevos\consoleinfo.c">
<Filter>rcheevos</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rcheevos\format.c">
<ClCompile Include="src\rcheevos\format.c">
<Filter>rcheevos</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rcheevos\lboard.c">
<ClCompile Include="src\rcheevos\lboard.c">
<Filter>rcheevos</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rcheevos\memref.c">
<ClCompile Include="src\rcheevos\memref.c">
<Filter>rcheevos</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rcheevos\operand.c">
<ClCompile Include="src\rcheevos\operand.c">
<Filter>rcheevos</Filter>
</ClCompile>
<ClCompile Include="rcheevos\src\rcheevos\rc_validate.c">
<ClCompile Include="src\rcheevos\rc_validate.c">
<Filter>rcheevos</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="rcheevos\src\rapi\rc_api_common.h">
<ClInclude Include="src\rapi\rc_api_common.h">
<Filter>rapi</Filter>
</ClInclude>
<ClInclude Include="rcheevos\src\rhash\md5.h">
<ClInclude Include="src\rhash\md5.h">
<Filter>rhash</Filter>
</ClInclude>
<ClInclude Include="rcheevos\include\rc_api_runtime.h">
<ClInclude Include="include\rc_api_runtime.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="rcheevos\include\rc_api_user.h">
<ClInclude Include="include\rc_api_user.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="rcheevos\include\rc_consoles.h">
<ClInclude Include="include\rc_consoles.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="rcheevos\include\rc_error.h">
<ClInclude Include="include\rc_error.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="rcheevos\include\rc_hash.h">
<ClInclude Include="include\rc_hash.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="rcheevos\include\rc_runtime.h">
<ClInclude Include="include\rc_runtime.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="rcheevos\include\rc_runtime_types.h">
<ClInclude Include="include\rc_runtime_types.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="rcheevos\include\rc_url.h">
<ClInclude Include="include\rc_url.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="rcheevos\include\rcheevos.h">
<ClInclude Include="include\rcheevos.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="rcheevos\include\rc_api_editor.h">
<ClInclude Include="include\rc_api_editor.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="rcheevos\include\rc_api_info.h">
<ClInclude Include="include\rc_api_info.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="rcheevos\include\rc_api_request.h">
<ClInclude Include="include\rc_api_request.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="rcheevos\include\rc_client.h">
<ClInclude Include="include\rc_client.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="rcheevos\src\rc_client_internal.h" />
<ClInclude Include="rcheevos\src\rc_compat.h" />
<ClInclude Include="rcheevos\src\rc_util.h" />
<ClInclude Include="rcheevos\src\rc_version.h" />
<ClInclude Include="rcheevos\src\rcheevos\rc_validate.h">
<ClInclude Include="src\rc_client_internal.h" />
<ClInclude Include="src\rc_compat.h" />
<ClInclude Include="include\rc_util.h">
<Filter>include</Filter>
</ClInclude>
<ClInclude Include="src\rc_version.h" />
<ClInclude Include="src\rcheevos\rc_validate.h">
<Filter>rcheevos</Filter>
</ClInclude>
<ClInclude Include="rcheevos\src\rcheevos\rc_internal.h">
<ClInclude Include="src\rcheevos\rc_internal.h">
<Filter>rcheevos</Filter>
</ClInclude>
</ItemGroup>
File diff suppressed because it is too large Load Diff
+80
View File
@@ -0,0 +1,80 @@
#ifndef RC_API_COMMON_H
#define RC_API_COMMON_H
#include "rc_api_request.h"
#include <stddef.h>
#include <time.h>
RC_BEGIN_C_DECLS
#define RC_CONTENT_TYPE_URLENCODED "application/x-www-form-urlencoded"
typedef struct rc_api_url_builder_t {
char* write;
char* start;
char* end;
/* pointer to a preallocated rc_buffer_t */
rc_buffer_t* buffer;
int result;
}
rc_api_url_builder_t;
void rc_url_builder_init(rc_api_url_builder_t* builder, rc_buffer_t* buffer, size_t estimated_size);
void rc_url_builder_append(rc_api_url_builder_t* builder, const char* data, size_t len);
const char* rc_url_builder_finalize(rc_api_url_builder_t* builder);
#define RC_JSON_NEW_FIELD(n) {NULL,NULL,n,sizeof(n)-1,0}
typedef struct rc_json_field_t {
const char* value_start;
const char* value_end;
const char* name;
size_t name_len;
uint32_t array_size;
}
rc_json_field_t;
typedef struct rc_json_iterator_t {
const char* json;
const char* end;
}
rc_json_iterator_t;
int rc_json_parse_server_response(rc_api_response_t* response, const rc_api_server_response_t* server_response, rc_json_field_t* fields, size_t field_count);
int rc_json_get_string(const char** out, rc_buffer_t* buffer, const rc_json_field_t* field, const char* field_name);
int rc_json_get_num(int32_t* out, const rc_json_field_t* field, const char* field_name);
int rc_json_get_unum(uint32_t* out, const rc_json_field_t* field, const char* field_name);
int rc_json_get_float(float* out, const rc_json_field_t* field, const char* field_name);
int rc_json_get_bool(int* out, const rc_json_field_t* field, const char* field_name);
int rc_json_get_datetime(time_t* out, const rc_json_field_t* field, const char* field_name);
void rc_json_get_optional_string(const char** out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name, const char* default_value);
void rc_json_get_optional_num(int32_t* out, const rc_json_field_t* field, const char* field_name, int default_value);
void rc_json_get_optional_unum(uint32_t* out, const rc_json_field_t* field, const char* field_name, uint32_t default_value);
void rc_json_get_optional_float(float* out, const rc_json_field_t* field, const char* field_name, float default_value);
void rc_json_get_optional_bool(int* out, const rc_json_field_t* field, const char* field_name, int default_value);
int rc_json_get_optional_array(uint32_t* num_entries, rc_json_field_t* iterator, const rc_json_field_t* field, const char* field_name);
int rc_json_get_required_string(const char** out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_required_num(int32_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_required_unum(uint32_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_required_float(float* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_required_bool(int* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_required_datetime(time_t* out, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_required_object(rc_json_field_t* fields, size_t field_count, rc_api_response_t* response, rc_json_field_t* field, const char* field_name);
int rc_json_get_required_unum_array(uint32_t** entries, uint32_t* num_entries, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_required_array(uint32_t* num_entries, rc_json_field_t* array_field, rc_api_response_t* response, const rc_json_field_t* field, const char* field_name);
int rc_json_get_array_entry_object(rc_json_field_t* fields, size_t field_count, rc_json_iterator_t* iterator);
int rc_json_get_next_object_field(rc_json_iterator_t* iterator, rc_json_field_t* field);
int rc_json_get_object_string_length(const char* json);
void rc_url_builder_append_encoded_str(rc_api_url_builder_t* builder, const char* str);
void rc_url_builder_append_num_param(rc_api_url_builder_t* builder, const char* param, int32_t value);
void rc_url_builder_append_unum_param(rc_api_url_builder_t* builder, const char* param, uint32_t value);
void rc_url_builder_append_str_param(rc_api_url_builder_t* builder, const char* param, const char* value);
void rc_api_url_build_dorequest_url(rc_api_request_t* request);
int rc_api_url_build_dorequest(rc_api_url_builder_t* builder, const char* api, const char* username, const char* api_token);
RC_END_C_DECLS
#endif /* RC_API_COMMON_H */
+529
View File
@@ -0,0 +1,529 @@
#include "rc_api_editor.h"
#include "rc_api_common.h"
#include "rc_api_runtime.h"
#include "../rc_compat.h"
#include "../rhash/md5.h"
#include <stdlib.h>
#include <string.h>
/* --- Fetch Code Notes --- */
int rc_api_init_fetch_code_notes_request(rc_api_request_t* request, const rc_api_fetch_code_notes_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "codenotes2");
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_code_notes_server_response(response, &response_obj);
}
int rc_api_process_fetch_code_notes_server_response(rc_api_fetch_code_notes_response_t* response, const rc_api_server_response_t* server_response) {
rc_json_field_t array_field;
rc_json_iterator_t iterator;
rc_api_code_note_t* note;
const char* address_str;
const char* last_author = "";
size_t last_author_len = 0;
size_t len;
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("CodeNotes")
};
rc_json_field_t note_fields[] = {
RC_JSON_NEW_FIELD("Address"),
RC_JSON_NEW_FIELD("User"),
RC_JSON_NEW_FIELD("Note")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
if (!rc_json_get_required_array(&response->num_notes, &array_field, &response->response, &fields[2], "CodeNotes"))
return RC_MISSING_VALUE;
if (response->num_notes) {
response->notes = (rc_api_code_note_t*)rc_buffer_alloc(&response->response.buffer, response->num_notes * sizeof(rc_api_code_note_t));
if (!response->notes)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
note = response->notes;
while (rc_json_get_array_entry_object(note_fields, sizeof(note_fields) / sizeof(note_fields[0]), &iterator)) {
/* an empty note represents a record that was deleted on the server */
/* a note set to '' also represents a deleted note (remnant of a bug) */
/* NOTE: len will include the quotes */
if (note_fields[2].value_start) {
len = note_fields[2].value_end - note_fields[2].value_start;
if (len == 2 || (len == 4 && note_fields[2].value_start[1] == '\'' && note_fields[2].value_start[2] == '\'')) {
--response->num_notes;
continue;
}
}
if (!rc_json_get_required_string(&address_str, &response->response, &note_fields[0], "Address"))
return RC_MISSING_VALUE;
note->address = (unsigned)strtol(address_str, NULL, 16);
if (!rc_json_get_required_string(&note->note, &response->response, &note_fields[2], "Note"))
return RC_MISSING_VALUE;
len = note_fields[1].value_end - note_fields[1].value_start;
if (len == last_author_len && memcmp(note_fields[1].value_start, last_author, len) == 0) {
note->author = last_author;
}
else {
if (!rc_json_get_required_string(&note->author, &response->response, &note_fields[1], "User"))
return RC_MISSING_VALUE;
last_author = note->author;
last_author_len = len;
}
++note;
}
}
return RC_OK;
}
void rc_api_destroy_fetch_code_notes_response(rc_api_fetch_code_notes_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Update Code Note --- */
int rc_api_init_update_code_note_request(rc_api_request_t* request, const rc_api_update_code_note_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 128);
if (!rc_api_url_build_dorequest(&builder, "submitcodenote", api_params->username, api_params->api_token))
return builder.result;
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
rc_url_builder_append_unum_param(&builder, "m", api_params->address);
if (api_params->note && *api_params->note)
rc_url_builder_append_str_param(&builder, "n", api_params->note);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_update_code_note_response(rc_api_update_code_note_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_update_code_note_server_response(response, &response_obj);
}
int rc_api_process_update_code_note_server_response(rc_api_update_code_note_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error")
/* unused fields
RC_JSON_NEW_FIELD("GameID"),
RC_JSON_NEW_FIELD("Address"),
RC_JSON_NEW_FIELD("Note")
*/
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
return RC_OK;
}
void rc_api_destroy_update_code_note_response(rc_api_update_code_note_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Update Achievement --- */
static const char* rc_type_string(uint32_t type) {
switch (type) {
case RC_ACHIEVEMENT_TYPE_MISSABLE: return "missable";
case RC_ACHIEVEMENT_TYPE_PROGRESSION: return "progression";
case RC_ACHIEVEMENT_TYPE_WIN: return "win_condition";
default: return "";
}
}
int rc_api_init_update_achievement_request(rc_api_request_t* request, const rc_api_update_achievement_request_t* api_params) {
rc_api_url_builder_t builder;
char buffer[33];
md5_state_t md5;
md5_byte_t hash[16];
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0 || api_params->category == 0)
return RC_INVALID_STATE;
if (!api_params->title || !*api_params->title)
return RC_INVALID_STATE;
if (!api_params->description || !*api_params->description)
return RC_INVALID_STATE;
if (!api_params->trigger || !*api_params->trigger)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 128);
if (!rc_api_url_build_dorequest(&builder, "uploadachievement", api_params->username, api_params->api_token))
return builder.result;
if (api_params->achievement_id)
rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id);
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
rc_url_builder_append_str_param(&builder, "n", api_params->title);
rc_url_builder_append_str_param(&builder, "d", api_params->description);
rc_url_builder_append_str_param(&builder, "m", api_params->trigger);
rc_url_builder_append_unum_param(&builder, "z", api_params->points);
rc_url_builder_append_unum_param(&builder, "f", api_params->category);
if (api_params->badge)
rc_url_builder_append_str_param(&builder, "b", api_params->badge);
rc_url_builder_append_str_param(&builder, "x", rc_type_string(api_params->type));
/* Evaluate the signature. */
md5_init(&md5);
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
md5_append(&md5, (md5_byte_t*)"SECRET", 6);
snprintf(buffer, sizeof(buffer), "%u", api_params->achievement_id);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_append(&md5, (md5_byte_t*)"SEC", 3);
md5_append(&md5, (md5_byte_t*)api_params->trigger, (int)strlen(api_params->trigger));
snprintf(buffer, sizeof(buffer), "%u", api_params->points);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_append(&md5, (md5_byte_t*)"RE2", 3);
snprintf(buffer, sizeof(buffer), "%u", api_params->points * 3);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_finish(&md5, hash);
rc_format_md5(buffer, hash);
rc_url_builder_append_str_param(&builder, "h", buffer);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_update_achievement_response(rc_api_update_achievement_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_update_achievement_server_response(response, &response_obj);
}
int rc_api_process_update_achievement_server_response(rc_api_update_achievement_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("AchievementID")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
if (!rc_json_get_required_unum(&response->achievement_id, &response->response, &fields[2], "AchievementID"))
return RC_MISSING_VALUE;
return RC_OK;
}
void rc_api_destroy_update_achievement_response(rc_api_update_achievement_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Update Leaderboard --- */
int rc_api_init_update_leaderboard_request(rc_api_request_t* request, const rc_api_update_leaderboard_request_t* api_params) {
rc_api_url_builder_t builder;
char buffer[33];
md5_state_t md5;
md5_byte_t hash[16];
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
if (!api_params->title || !*api_params->title)
return RC_INVALID_STATE;
if (!api_params->description)
return RC_INVALID_STATE;
if (!api_params->start_trigger || !*api_params->start_trigger)
return RC_INVALID_STATE;
if (!api_params->submit_trigger || !*api_params->submit_trigger)
return RC_INVALID_STATE;
if (!api_params->cancel_trigger || !*api_params->cancel_trigger)
return RC_INVALID_STATE;
if (!api_params->value_definition || !*api_params->value_definition)
return RC_INVALID_STATE;
if (!api_params->format || !*api_params->format)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 128);
if (!rc_api_url_build_dorequest(&builder, "uploadleaderboard", api_params->username, api_params->api_token))
return builder.result;
if (api_params->leaderboard_id)
rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id);
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
rc_url_builder_append_str_param(&builder, "n", api_params->title);
rc_url_builder_append_str_param(&builder, "d", api_params->description);
rc_url_builder_append_str_param(&builder, "s", api_params->start_trigger);
rc_url_builder_append_str_param(&builder, "b", api_params->submit_trigger);
rc_url_builder_append_str_param(&builder, "c", api_params->cancel_trigger);
rc_url_builder_append_str_param(&builder, "l", api_params->value_definition);
rc_url_builder_append_num_param(&builder, "w", api_params->lower_is_better);
rc_url_builder_append_str_param(&builder, "f", api_params->format);
/* Evaluate the signature. */
md5_init(&md5);
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
md5_append(&md5, (md5_byte_t*)"SECRET", 6);
snprintf(buffer, sizeof(buffer), "%u", api_params->leaderboard_id);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_append(&md5, (md5_byte_t*)"SEC", 3);
md5_append(&md5, (md5_byte_t*)api_params->start_trigger, (int)strlen(api_params->start_trigger));
md5_append(&md5, (md5_byte_t*)api_params->submit_trigger, (int)strlen(api_params->submit_trigger));
md5_append(&md5, (md5_byte_t*)api_params->cancel_trigger, (int)strlen(api_params->cancel_trigger));
md5_append(&md5, (md5_byte_t*)api_params->value_definition, (int)strlen(api_params->value_definition));
md5_append(&md5, (md5_byte_t*)"RE2", 3);
md5_append(&md5, (md5_byte_t*)api_params->format, (int)strlen(api_params->format));
md5_finish(&md5, hash);
rc_format_md5(buffer, hash);
rc_url_builder_append_str_param(&builder, "h", buffer);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_update_leaderboard_response(rc_api_update_leaderboard_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_update_leaderboard_server_response(response, &response_obj);
}
int rc_api_process_update_leaderboard_server_response(rc_api_update_leaderboard_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("LeaderboardID")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
if (!rc_json_get_required_unum(&response->leaderboard_id, &response->response, &fields[2], "LeaderboardID"))
return RC_MISSING_VALUE;
return RC_OK;
}
void rc_api_destroy_update_leaderboard_response(rc_api_update_leaderboard_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Fetch Badge Range --- */
int rc_api_init_fetch_badge_range_request(rc_api_request_t* request, const rc_api_fetch_badge_range_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "badgeiter");
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
(void)api_params;
return builder.result;
}
int rc_api_process_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_badge_range_server_response(response, &response_obj);
}
int rc_api_process_fetch_badge_range_server_response(rc_api_fetch_badge_range_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("FirstBadge"),
RC_JSON_NEW_FIELD("NextBadge")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
if (!rc_json_get_required_unum(&response->first_badge_id, &response->response, &fields[2], "FirstBadge"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->next_badge_id, &response->response, &fields[3], "NextBadge"))
return RC_MISSING_VALUE;
return RC_OK;
}
void rc_api_destroy_fetch_badge_range_response(rc_api_fetch_badge_range_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Add Game Hash --- */
int rc_api_init_add_game_hash_request(rc_api_request_t* request, const rc_api_add_game_hash_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->console_id == 0)
return RC_INVALID_STATE;
if (!api_params->hash || !*api_params->hash)
return RC_INVALID_STATE;
if (api_params->game_id == 0 && (!api_params->title || !*api_params->title))
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 128);
if (!rc_api_url_build_dorequest(&builder, "submitgametitle", api_params->username, api_params->api_token))
return builder.result;
rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
rc_url_builder_append_str_param(&builder, "m", api_params->hash);
if (api_params->title)
rc_url_builder_append_str_param(&builder, "i", api_params->title);
if (api_params->game_id)
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
if (api_params->hash_description && *api_params->hash_description)
rc_url_builder_append_str_param(&builder, "d", api_params->hash_description);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_add_game_hash_response(rc_api_add_game_hash_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_add_game_hash_server_response(response, &response_obj);
}
int rc_api_process_add_game_hash_server_response(rc_api_add_game_hash_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Response")
};
rc_json_field_t response_fields[] = {
RC_JSON_NEW_FIELD("GameID")
/* unused fields
RC_JSON_NEW_FIELD("MD5"),
RC_JSON_NEW_FIELD("ConsoleID"),
RC_JSON_NEW_FIELD("GameTitle"),
RC_JSON_NEW_FIELD("Success")
*/
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[2], "Response"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[0], "GameID"))
return RC_MISSING_VALUE;
return RC_OK;
}
void rc_api_destroy_add_game_hash_response(rc_api_add_game_hash_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
+373
View File
@@ -0,0 +1,373 @@
#include "rc_api_info.h"
#include "rc_api_common.h"
#include "rc_runtime_types.h"
#include <stdlib.h>
#include <string.h>
/* --- Fetch Achievement Info --- */
int rc_api_init_fetch_achievement_info_request(rc_api_request_t* request, const rc_api_fetch_achievement_info_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->achievement_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "achievementwondata", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id);
if (api_params->friends_only)
rc_url_builder_append_unum_param(&builder, "f", 1);
if (api_params->first_entry > 1)
rc_url_builder_append_unum_param(&builder, "o", api_params->first_entry - 1); /* number of entries to skip */
rc_url_builder_append_unum_param(&builder, "c", api_params->count);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
return builder.result;
}
int rc_api_process_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_achievement_info_server_response(response, &response_obj);
}
int rc_api_process_fetch_achievement_info_server_response(rc_api_fetch_achievement_info_response_t* response, const rc_api_server_response_t* server_response) {
rc_api_achievement_awarded_entry_t* entry;
rc_json_field_t array_field;
rc_json_iterator_t iterator;
uint32_t timet;
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("AchievementID"),
RC_JSON_NEW_FIELD("Response")
/* unused fields
RC_JSON_NEW_FIELD("Offset"),
RC_JSON_NEW_FIELD("Count"),
RC_JSON_NEW_FIELD("FriendsOnly")
* unused fields */
};
rc_json_field_t response_fields[] = {
RC_JSON_NEW_FIELD("NumEarned"),
RC_JSON_NEW_FIELD("TotalPlayers"),
RC_JSON_NEW_FIELD("GameID"),
RC_JSON_NEW_FIELD("RecentWinner") /* array */
};
rc_json_field_t entry_fields[] = {
RC_JSON_NEW_FIELD("User"),
RC_JSON_NEW_FIELD("DateAwarded")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
if (!rc_json_get_required_unum(&response->id, &response->response, &fields[2], "AchievementID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[3], "Response"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->num_awarded, &response->response, &response_fields[0], "NumEarned"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->num_players, &response->response, &response_fields[1], "TotalPlayers"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->game_id, &response->response, &response_fields[2], "GameID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_array(&response->num_recently_awarded, &array_field, &response->response, &response_fields[3], "RecentWinner"))
return RC_MISSING_VALUE;
if (response->num_recently_awarded) {
response->recently_awarded = (rc_api_achievement_awarded_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_recently_awarded * sizeof(rc_api_achievement_awarded_entry_t));
if (!response->recently_awarded)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
entry = response->recently_awarded;
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&timet, &response->response, &entry_fields[1], "DateAwarded"))
return RC_MISSING_VALUE;
entry->awarded = (time_t)timet;
++entry;
}
}
return RC_OK;
}
void rc_api_destroy_fetch_achievement_info_response(rc_api_fetch_achievement_info_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Fetch Leaderboard Info --- */
int rc_api_init_fetch_leaderboard_info_request(rc_api_request_t* request, const rc_api_fetch_leaderboard_info_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->leaderboard_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "lbinfo");
rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id);
if (api_params->username)
rc_url_builder_append_str_param(&builder, "u", api_params->username);
else if (api_params->first_entry > 1)
rc_url_builder_append_unum_param(&builder, "o", api_params->first_entry - 1); /* number of entries to skip */
rc_url_builder_append_unum_param(&builder, "c", api_params->count);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_leaderboard_info_server_response(response, &response_obj);
}
int rc_api_process_fetch_leaderboard_info_server_response(rc_api_fetch_leaderboard_info_response_t* response, const rc_api_server_response_t* server_response) {
rc_api_lboard_info_entry_t* entry;
rc_json_field_t array_field;
rc_json_iterator_t iterator;
uint32_t timet;
int result;
size_t len;
char format[16];
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("LeaderboardData")
};
rc_json_field_t leaderboarddata_fields[] = {
RC_JSON_NEW_FIELD("LBID"),
RC_JSON_NEW_FIELD("LBFormat"),
RC_JSON_NEW_FIELD("LowerIsBetter"),
RC_JSON_NEW_FIELD("LBTitle"),
RC_JSON_NEW_FIELD("LBDesc"),
RC_JSON_NEW_FIELD("LBMem"),
RC_JSON_NEW_FIELD("GameID"),
RC_JSON_NEW_FIELD("LBAuthor"),
RC_JSON_NEW_FIELD("LBCreated"),
RC_JSON_NEW_FIELD("LBUpdated"),
RC_JSON_NEW_FIELD("Entries") /* array */
/* unused fields
RC_JSON_NEW_FIELD("GameTitle"),
RC_JSON_NEW_FIELD("ConsoleID"),
RC_JSON_NEW_FIELD("ConsoleName"),
RC_JSON_NEW_FIELD("ForumTopicID"),
RC_JSON_NEW_FIELD("GameIcon")
* unused fields */
};
rc_json_field_t entry_fields[] = {
RC_JSON_NEW_FIELD("User"),
RC_JSON_NEW_FIELD("Rank"),
RC_JSON_NEW_FIELD("Index"),
RC_JSON_NEW_FIELD("Score"),
RC_JSON_NEW_FIELD("DateSubmitted")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
if (!rc_json_get_required_object(leaderboarddata_fields, sizeof(leaderboarddata_fields) / sizeof(leaderboarddata_fields[0]), &response->response, &fields[2], "LeaderboardData"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->id, &response->response, &leaderboarddata_fields[0], "LBID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->lower_is_better, &response->response, &leaderboarddata_fields[2], "LowerIsBetter"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->title, &response->response, &leaderboarddata_fields[3], "LBTitle"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->description, &response->response, &leaderboarddata_fields[4], "LBDesc"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->definition, &response->response, &leaderboarddata_fields[5], "LBMem"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->game_id, &response->response, &leaderboarddata_fields[6], "GameID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->author, &response->response, &leaderboarddata_fields[7], "LBAuthor"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_datetime(&response->created, &response->response, &leaderboarddata_fields[8], "LBCreated"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_datetime(&response->updated, &response->response, &leaderboarddata_fields[9], "LBUpdated"))
return RC_MISSING_VALUE;
if (!leaderboarddata_fields[1].value_end)
return RC_MISSING_VALUE;
len = leaderboarddata_fields[1].value_end - leaderboarddata_fields[1].value_start - 2;
if (len < sizeof(format) - 1) {
memcpy(format, leaderboarddata_fields[1].value_start + 1, len);
format[len] = '\0';
response->format = rc_parse_format(format);
}
else {
response->format = RC_FORMAT_VALUE;
}
if (!rc_json_get_required_array(&response->num_entries, &array_field, &response->response, &leaderboarddata_fields[10], "Entries"))
return RC_MISSING_VALUE;
if (response->num_entries) {
response->entries = (rc_api_lboard_info_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_lboard_info_entry_t));
if (!response->entries)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
entry = response->entries;
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&entry->rank, &response->response, &entry_fields[1], "Rank"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&entry->index, &response->response, &entry_fields[2], "Index"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_num(&entry->score, &response->response, &entry_fields[3], "Score"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&timet, &response->response, &entry_fields[4], "DateSubmitted"))
return RC_MISSING_VALUE;
entry->submitted = (time_t)timet;
++entry;
}
}
return RC_OK;
}
void rc_api_destroy_fetch_leaderboard_info_response(rc_api_fetch_leaderboard_info_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Fetch Games List --- */
int rc_api_init_fetch_games_list_request(rc_api_request_t* request, const rc_api_fetch_games_list_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->console_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "gameslist");
rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_fetch_games_list_response(rc_api_fetch_games_list_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_games_list_server_response(response, &response_obj);
}
int rc_api_process_fetch_games_list_server_response(rc_api_fetch_games_list_response_t* response, const rc_api_server_response_t* server_response) {
rc_api_game_list_entry_t* entry;
rc_json_iterator_t iterator;
rc_json_field_t field;
int result;
char* end;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Response")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
if (!fields[2].value_start) {
/* call rc_json_get_required_object to generate the error message */
rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "Response");
return RC_MISSING_VALUE;
}
response->num_entries = fields[2].array_size;
rc_buffer_reserve(&response->response.buffer, response->num_entries * (32 + sizeof(rc_api_game_list_entry_t)));
response->entries = (rc_api_game_list_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_entries * sizeof(rc_api_game_list_entry_t));
if (!response->entries)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = fields[2].value_start;
iterator.end = fields[2].value_end;
entry = response->entries;
while (rc_json_get_next_object_field(&iterator, &field)) {
entry->id = strtol(field.name, &end, 10);
field.name = "";
if (!rc_json_get_string(&entry->name, &response->response.buffer, &field, ""))
return RC_MISSING_VALUE;
++entry;
}
return RC_OK;
}
void rc_api_destroy_fetch_games_list_response(rc_api_fetch_games_list_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
+648
View File
@@ -0,0 +1,648 @@
#include "rc_api_runtime.h"
#include "rc_api_common.h"
#include "rc_runtime.h"
#include "rc_runtime_types.h"
#include "../rc_compat.h"
#include "../rhash/md5.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/* --- Resolve Hash --- */
int rc_api_init_resolve_hash_request(rc_api_request_t* request, const rc_api_resolve_hash_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (!api_params->game_hash || !*api_params->game_hash)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "gameid");
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_resolve_hash_response(rc_api_resolve_hash_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_resolve_hash_server_response(response, &response_obj);
}
int rc_api_process_resolve_hash_server_response(rc_api_resolve_hash_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("GameID")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
rc_json_get_required_unum(&response->game_id, &response->response, &fields[2], "GameID");
return RC_OK;
}
void rc_api_destroy_resolve_hash_response(rc_api_resolve_hash_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Fetch Game Data --- */
int rc_api_init_fetch_game_data_request(rc_api_request_t* request, const rc_api_fetch_game_data_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "patch", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
return builder.result;
}
int rc_api_process_fetch_game_data_response(rc_api_fetch_game_data_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_game_data_server_response(response, &response_obj);
}
static int rc_parse_achievement_type(const char* type)
{
if (strcmp(type, "missable") == 0)
return RC_ACHIEVEMENT_TYPE_MISSABLE;
if (strcmp(type, "win_condition") == 0)
return RC_ACHIEVEMENT_TYPE_WIN;
if (strcmp(type, "progression") == 0)
return RC_ACHIEVEMENT_TYPE_PROGRESSION;
return RC_ACHIEVEMENT_TYPE_STANDARD;
}
int rc_api_process_fetch_game_data_server_response(rc_api_fetch_game_data_response_t* response, const rc_api_server_response_t* server_response) {
rc_api_achievement_definition_t* achievement;
rc_api_leaderboard_definition_t* leaderboard;
rc_json_field_t array_field;
rc_json_iterator_t iterator;
const char* str;
const char* last_author = "";
const char* last_author_field = "";
size_t last_author_len = 0;
size_t len;
uint32_t timet;
int result;
char format[16];
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("PatchData") /* nested object */
};
rc_json_field_t patchdata_fields[] = {
RC_JSON_NEW_FIELD("ID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("ConsoleID"),
RC_JSON_NEW_FIELD("ImageIcon"),
RC_JSON_NEW_FIELD("RichPresencePatch"),
RC_JSON_NEW_FIELD("Achievements"), /* array */
RC_JSON_NEW_FIELD("Leaderboards") /* array */
};
rc_json_field_t achievement_fields[] = {
RC_JSON_NEW_FIELD("ID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("Description"),
RC_JSON_NEW_FIELD("Flags"),
RC_JSON_NEW_FIELD("Points"),
RC_JSON_NEW_FIELD("MemAddr"),
RC_JSON_NEW_FIELD("Author"),
RC_JSON_NEW_FIELD("BadgeName"),
RC_JSON_NEW_FIELD("Created"),
RC_JSON_NEW_FIELD("Modified"),
RC_JSON_NEW_FIELD("Type"),
RC_JSON_NEW_FIELD("Rarity"),
RC_JSON_NEW_FIELD("RarityHardcore")
};
rc_json_field_t leaderboard_fields[] = {
RC_JSON_NEW_FIELD("ID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("Description"),
RC_JSON_NEW_FIELD("Mem"),
RC_JSON_NEW_FIELD("Format"),
RC_JSON_NEW_FIELD("LowerIsBetter"),
RC_JSON_NEW_FIELD("Hidden")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
if (!rc_json_get_required_object(patchdata_fields, sizeof(patchdata_fields) / sizeof(patchdata_fields[0]), &response->response, &fields[2], "PatchData"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->id, &response->response, &patchdata_fields[0], "ID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->title, &response->response, &patchdata_fields[1], "Title"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->console_id, &response->response, &patchdata_fields[2], "ConsoleID"))
return RC_MISSING_VALUE;
/* ImageIcon will be '/Images/0123456.png' - only return the '0123456' */
if (patchdata_fields[3].value_end) {
str = patchdata_fields[3].value_end - 5;
if (memcmp(str, ".png\"", 5) == 0) {
patchdata_fields[3].value_end -= 5;
while (str > patchdata_fields[3].value_start && str[-1] != '/')
--str;
patchdata_fields[3].value_start = str;
}
}
rc_json_get_optional_string(&response->image_name, &response->response, &patchdata_fields[3], "ImageIcon", "");
/* estimate the amount of space necessary to store the rich presence script, achievements, and leaderboards.
determine how much space each takes as a string in the JSON, then subtract out the non-data (field names, punctuation)
and add space for the structures. */
len = patchdata_fields[4].value_end - patchdata_fields[4].value_start; /* rich presence */
len += (patchdata_fields[5].value_end - patchdata_fields[5].value_start) - /* achievements */
patchdata_fields[5].array_size * (130 - sizeof(rc_api_achievement_definition_t));
len += (patchdata_fields[6].value_end - patchdata_fields[6].value_start) - /* leaderboards */
patchdata_fields[6].array_size * (60 - sizeof(rc_api_leaderboard_definition_t));
rc_buffer_reserve(&response->response.buffer, len);
/* end estimation */
rc_json_get_optional_string(&response->rich_presence_script, &response->response, &patchdata_fields[4], "RichPresencePatch", "");
if (!response->rich_presence_script)
response->rich_presence_script = "";
if (!rc_json_get_required_array(&response->num_achievements, &array_field, &response->response, &patchdata_fields[5], "Achievements"))
return RC_MISSING_VALUE;
if (response->num_achievements) {
response->achievements = (rc_api_achievement_definition_t*)rc_buffer_alloc(&response->response.buffer, response->num_achievements * sizeof(rc_api_achievement_definition_t));
if (!response->achievements)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
achievement = response->achievements;
while (rc_json_get_array_entry_object(achievement_fields, sizeof(achievement_fields) / sizeof(achievement_fields[0]), &iterator)) {
if (!rc_json_get_required_unum(&achievement->id, &response->response, &achievement_fields[0], "ID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->title, &response->response, &achievement_fields[1], "Title"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->description, &response->response, &achievement_fields[2], "Description"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&achievement->category, &response->response, &achievement_fields[3], "Flags"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&achievement->points, &response->response, &achievement_fields[4], "Points"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->definition, &response->response, &achievement_fields[5], "MemAddr"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&achievement->badge_name, &response->response, &achievement_fields[7], "BadgeName"))
return RC_MISSING_VALUE;
len = achievement_fields[6].value_end - achievement_fields[6].value_start;
if (len == last_author_len && memcmp(achievement_fields[6].value_start, last_author_field, len) == 0) {
achievement->author = last_author;
}
else {
if (!rc_json_get_required_string(&achievement->author, &response->response, &achievement_fields[6], "Author"))
return RC_MISSING_VALUE;
last_author = achievement->author;
last_author_field = achievement_fields[6].value_start;
last_author_len = len;
}
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[8], "Created"))
return RC_MISSING_VALUE;
achievement->created = (time_t)timet;
if (!rc_json_get_required_unum(&timet, &response->response, &achievement_fields[9], "Modified"))
return RC_MISSING_VALUE;
achievement->updated = (time_t)timet;
achievement->type = RC_ACHIEVEMENT_TYPE_STANDARD;
if (achievement_fields[10].value_end) {
len = achievement_fields[10].value_end - achievement_fields[10].value_start - 2;
if (len < sizeof(format) - 1) {
memcpy(format, achievement_fields[10].value_start + 1, len);
format[len] = '\0';
achievement->type = rc_parse_achievement_type(format);
}
}
/* legacy support : if title contains[m], change type to missable and remove[m] from title */
if (memcmp(achievement->title, "[m]", 3) == 0) {
len = 3;
while (achievement->title[len] == ' ')
++len;
achievement->title += len;
achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE;
}
else if (achievement_fields[1].value_end && memcmp(achievement_fields[1].value_end - 4, "[m]", 3) == 0) {
len = strlen(achievement->title) - 3;
while (achievement->title[len - 1] == ' ')
--len;
((char*)achievement->title)[len] = '\0';
achievement->type = RC_ACHIEVEMENT_TYPE_MISSABLE;
}
rc_json_get_optional_float(&achievement->rarity, &achievement_fields[11], "Rarity", 100.0);
rc_json_get_optional_float(&achievement->rarity_hardcore, &achievement_fields[12], "RarityHardcore", 100.0);
++achievement;
}
}
if (!rc_json_get_required_array(&response->num_leaderboards, &array_field, &response->response, &patchdata_fields[6], "Leaderboards"))
return RC_MISSING_VALUE;
if (response->num_leaderboards) {
response->leaderboards = (rc_api_leaderboard_definition_t*)rc_buffer_alloc(&response->response.buffer, response->num_leaderboards * sizeof(rc_api_leaderboard_definition_t));
if (!response->leaderboards)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
leaderboard = response->leaderboards;
while (rc_json_get_array_entry_object(leaderboard_fields, sizeof(leaderboard_fields) / sizeof(leaderboard_fields[0]), &iterator)) {
if (!rc_json_get_required_unum(&leaderboard->id, &response->response, &leaderboard_fields[0], "ID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&leaderboard->title, &response->response, &leaderboard_fields[1], "Title"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&leaderboard->description, &response->response, &leaderboard_fields[2], "Description"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&leaderboard->definition, &response->response, &leaderboard_fields[3], "Mem"))
return RC_MISSING_VALUE;
rc_json_get_optional_bool(&result, &leaderboard_fields[5], "LowerIsBetter", 0);
leaderboard->lower_is_better = (uint8_t)result;
rc_json_get_optional_bool(&result, &leaderboard_fields[6], "Hidden", 0);
leaderboard->hidden = (uint8_t)result;
if (!leaderboard_fields[4].value_end)
return RC_MISSING_VALUE;
len = leaderboard_fields[4].value_end - leaderboard_fields[4].value_start - 2;
if (len < sizeof(format) - 1) {
memcpy(format, leaderboard_fields[4].value_start + 1, len);
format[len] = '\0';
leaderboard->format = rc_parse_format(format);
}
else {
leaderboard->format = RC_FORMAT_VALUE;
}
++leaderboard;
}
}
return RC_OK;
}
void rc_api_destroy_fetch_game_data_response(rc_api_fetch_game_data_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Ping --- */
int rc_api_init_ping_request(rc_api_request_t* request, const rc_api_ping_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "ping", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
if (api_params->rich_presence && *api_params->rich_presence)
rc_url_builder_append_str_param(&builder, "m", api_params->rich_presence);
if (api_params->game_hash && *api_params->game_hash) {
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore);
rc_url_builder_append_str_param(&builder, "x", api_params->game_hash);
}
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
return builder.result;
}
int rc_api_process_ping_response(rc_api_ping_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_ping_server_response(response, &response_obj);
}
int rc_api_process_ping_server_response(rc_api_ping_response_t* response, const rc_api_server_response_t* server_response) {
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
return rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
}
void rc_api_destroy_ping_response(rc_api_ping_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Award Achievement --- */
int rc_api_init_award_achievement_request(rc_api_request_t* request, const rc_api_award_achievement_request_t* api_params) {
rc_api_url_builder_t builder;
char buffer[33];
md5_state_t md5;
md5_byte_t digest[16];
rc_api_url_build_dorequest_url(request);
if (api_params->achievement_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 96);
if (rc_api_url_build_dorequest(&builder, "awardachievement", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "a", api_params->achievement_id);
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0);
if (api_params->game_hash && *api_params->game_hash)
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
/* Evaluate the signature. */
md5_init(&md5);
snprintf(buffer, sizeof(buffer), "%u", api_params->achievement_id);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
snprintf(buffer, sizeof(buffer), "%d", api_params->hardcore ? 1 : 0);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_finish(&md5, digest);
rc_format_md5(buffer, digest);
rc_url_builder_append_str_param(&builder, "v", buffer);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
return builder.result;
}
int rc_api_process_award_achievement_response(rc_api_award_achievement_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_award_achievement_server_response(response, &response_obj);
}
int rc_api_process_award_achievement_server_response(rc_api_award_achievement_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Score"),
RC_JSON_NEW_FIELD("SoftcoreScore"),
RC_JSON_NEW_FIELD("AchievementID"),
RC_JSON_NEW_FIELD("AchievementsRemaining")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK)
return result;
if (!response->response.succeeded) {
if (response->response.error_message &&
memcmp(response->response.error_message, "User already has", 16) == 0) {
/* not really an error, the achievement is unlocked, just not by the current call.
* hardcore: User already has hardcore and regular achievements awarded.
* non-hardcore: User already has this achievement awarded.
*/
response->response.succeeded = 1;
} else {
return result;
}
}
rc_json_get_optional_unum(&response->new_player_score, &fields[2], "Score", 0);
rc_json_get_optional_unum(&response->new_player_score_softcore, &fields[3], "SoftcoreScore", 0);
rc_json_get_optional_unum(&response->awarded_achievement_id, &fields[4], "AchievementID", 0);
rc_json_get_optional_unum(&response->achievements_remaining, &fields[5], "AchievementsRemaining", (unsigned)-1);
return RC_OK;
}
void rc_api_destroy_award_achievement_response(rc_api_award_achievement_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Submit Leaderboard Entry --- */
int rc_api_init_submit_lboard_entry_request(rc_api_request_t* request, const rc_api_submit_lboard_entry_request_t* api_params) {
rc_api_url_builder_t builder;
char buffer[33];
md5_state_t md5;
md5_byte_t digest[16];
rc_api_url_build_dorequest_url(request);
if (api_params->leaderboard_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 96);
if (rc_api_url_build_dorequest(&builder, "submitlbentry", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "i", api_params->leaderboard_id);
rc_url_builder_append_num_param(&builder, "s", api_params->score);
if (api_params->game_hash && *api_params->game_hash)
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
/* Evaluate the signature. */
md5_init(&md5);
snprintf(buffer, sizeof(buffer), "%u", api_params->leaderboard_id);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_append(&md5, (md5_byte_t*)api_params->username, (int)strlen(api_params->username));
snprintf(buffer, sizeof(buffer), "%d", api_params->score);
md5_append(&md5, (md5_byte_t*)buffer, (int)strlen(buffer));
md5_finish(&md5, digest);
rc_format_md5(buffer, digest);
rc_url_builder_append_str_param(&builder, "v", buffer);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
return builder.result;
}
int rc_api_process_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_submit_lboard_entry_server_response(response, &response_obj);
}
int rc_api_process_submit_lboard_entry_server_response(rc_api_submit_lboard_entry_response_t* response, const rc_api_server_response_t* server_response) {
rc_api_lboard_entry_t* entry;
rc_json_field_t array_field;
rc_json_iterator_t iterator;
const char* str;
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Response") /* nested object */
};
rc_json_field_t response_fields[] = {
RC_JSON_NEW_FIELD("Score"),
RC_JSON_NEW_FIELD("BestScore"),
RC_JSON_NEW_FIELD("RankInfo"), /* nested object */
RC_JSON_NEW_FIELD("TopEntries") /* array */
/* unused fields
RC_JSON_NEW_FIELD("LBData"), / * array * /
RC_JSON_NEW_FIELD("ScoreFormatted"),
RC_JSON_NEW_FIELD("TopEntriesFriends") / * array * /
* unused fields */
};
/* unused fields
rc_json_field_t lbdata_fields[] = {
RC_JSON_NEW_FIELD("Format"),
RC_JSON_NEW_FIELD("LeaderboardID"),
RC_JSON_NEW_FIELD("GameID"),
RC_JSON_NEW_FIELD("Title"),
RC_JSON_NEW_FIELD("LowerIsBetter")
};
* unused fields */
rc_json_field_t entry_fields[] = {
RC_JSON_NEW_FIELD("User"),
RC_JSON_NEW_FIELD("Rank"),
RC_JSON_NEW_FIELD("Score")
/* unused fields
RC_JSON_NEW_FIELD("DateSubmitted")
* unused fields */
};
rc_json_field_t rank_info_fields[] = {
RC_JSON_NEW_FIELD("Rank"),
RC_JSON_NEW_FIELD("NumEntries")
/* unused fields
RC_JSON_NEW_FIELD("LowerIsBetter"),
RC_JSON_NEW_FIELD("UserRank")
* unused fields */
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
if (!rc_json_get_required_object(response_fields, sizeof(response_fields) / sizeof(response_fields[0]), &response->response, &fields[2], "Response"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_num(&response->submitted_score, &response->response, &response_fields[0], "Score"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_num(&response->best_score, &response->response, &response_fields[1], "BestScore"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_object(rank_info_fields, sizeof(rank_info_fields) / sizeof(rank_info_fields[0]), &response->response, &response_fields[2], "RankInfo"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&response->new_rank, &response->response, &rank_info_fields[0], "Rank"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&str, &response->response, &rank_info_fields[1], "NumEntries"))
return RC_MISSING_VALUE;
response->num_entries = (unsigned)atoi(str);
if (!rc_json_get_required_array(&response->num_top_entries, &array_field, &response->response, &response_fields[3], "TopEntries"))
return RC_MISSING_VALUE;
if (response->num_top_entries) {
response->top_entries = (rc_api_lboard_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_top_entries * sizeof(rc_api_lboard_entry_t));
if (!response->top_entries)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
entry = response->top_entries;
while (rc_json_get_array_entry_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]), &iterator)) {
if (!rc_json_get_required_string(&entry->username, &response->response, &entry_fields[0], "User"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&entry->rank, &response->response, &entry_fields[1], "Rank"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_num(&entry->score, &response->response, &entry_fields[2], "Score"))
return RC_MISSING_VALUE;
++entry;
}
}
return RC_OK;
}
void rc_api_destroy_submit_lboard_entry_response(rc_api_submit_lboard_entry_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
+254
View File
@@ -0,0 +1,254 @@
#include "rc_api_user.h"
#include "rc_api_common.h"
#include "../rc_version.h"
#include <string.h>
/* --- Login --- */
int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (!api_params->username || !*api_params->username)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "login2");
rc_url_builder_append_str_param(&builder, "u", api_params->username);
if (api_params->password && api_params->password[0])
rc_url_builder_append_str_param(&builder, "p", api_params->password);
else if (api_params->api_token && api_params->api_token[0])
rc_url_builder_append_str_param(&builder, "t", api_params->api_token);
else
return RC_INVALID_STATE;
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
return builder.result;
}
int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_login_server_response(response, &response_obj);
}
int rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Code"),
RC_JSON_NEW_FIELD("User"),
RC_JSON_NEW_FIELD("Token"),
RC_JSON_NEW_FIELD("Score"),
RC_JSON_NEW_FIELD("SoftcoreScore"),
RC_JSON_NEW_FIELD("Messages"),
RC_JSON_NEW_FIELD("DisplayName")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
if (!rc_json_get_required_string(&response->username, &response->response, &fields[3], "User"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_string(&response->api_token, &response->response, &fields[4], "Token"))
return RC_MISSING_VALUE;
rc_json_get_optional_unum(&response->score, &fields[5], "Score", 0);
rc_json_get_optional_unum(&response->score_softcore, &fields[6], "SoftcoreScore", 0);
rc_json_get_optional_unum(&response->num_unread_messages, &fields[7], "Messages", 0);
rc_json_get_optional_string(&response->display_name, &response->response, &fields[8], "DisplayName", response->username);
return RC_OK;
}
void rc_api_destroy_login_response(rc_api_login_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Start Session --- */
int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
if (api_params->game_id == 0)
return RC_INVALID_STATE;
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "startsession", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
if (api_params->game_hash && *api_params->game_hash) {
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore);
rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
}
rc_url_builder_append_str_param(&builder, "l", RCHEEVOS_VERSION_STRING);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
return builder.result;
}
int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_start_session_server_response(response, &response_obj);
}
int rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response) {
rc_api_unlock_entry_t* unlock;
rc_json_field_t array_field;
rc_json_iterator_t iterator;
uint32_t timet;
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("Unlocks"),
RC_JSON_NEW_FIELD("HardcoreUnlocks"),
RC_JSON_NEW_FIELD("ServerNow")
};
rc_json_field_t unlock_entry_fields[] = {
RC_JSON_NEW_FIELD("ID"),
RC_JSON_NEW_FIELD("When")
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
if (rc_json_get_optional_array(&response->num_unlocks, &array_field, &fields[2], "Unlocks") && response->num_unlocks) {
response->unlocks = (rc_api_unlock_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_unlocks * sizeof(rc_api_unlock_entry_t));
if (!response->unlocks)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
unlock = response->unlocks;
while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) {
if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When"))
return RC_MISSING_VALUE;
unlock->when = (time_t)timet;
++unlock;
}
}
if (rc_json_get_optional_array(&response->num_hardcore_unlocks, &array_field, &fields[3], "HardcoreUnlocks") && response->num_hardcore_unlocks) {
response->hardcore_unlocks = (rc_api_unlock_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_hardcore_unlocks * sizeof(rc_api_unlock_entry_t));
if (!response->hardcore_unlocks)
return RC_OUT_OF_MEMORY;
memset(&iterator, 0, sizeof(iterator));
iterator.json = array_field.value_start;
iterator.end = array_field.value_end;
unlock = response->hardcore_unlocks;
while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) {
if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID"))
return RC_MISSING_VALUE;
if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When"))
return RC_MISSING_VALUE;
unlock->when = (time_t)timet;
++unlock;
}
}
rc_json_get_optional_unum(&timet, &fields[4], "ServerNow", 0);
response->server_now = (time_t)timet;
return RC_OK;
}
void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
/* --- Fetch User Unlocks --- */
int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params) {
rc_api_url_builder_t builder;
rc_api_url_build_dorequest_url(request);
rc_url_builder_init(&builder, &request->buffer, 48);
if (rc_api_url_build_dorequest(&builder, "unlocks", api_params->username, api_params->api_token)) {
rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0);
request->post_data = rc_url_builder_finalize(&builder);
request->content_type = RC_CONTENT_TYPE_URLENCODED;
}
return builder.result;
}
int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response) {
rc_api_server_response_t response_obj;
memset(&response_obj, 0, sizeof(response_obj));
response_obj.body = server_response;
response_obj.body_length = rc_json_get_object_string_length(server_response);
return rc_api_process_fetch_user_unlocks_server_response(response, &response_obj);
}
int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response) {
int result;
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("UserUnlocks")
/* unused fields
RC_JSON_NEW_FIELD("GameID"),
RC_JSON_NEW_FIELD("HardcoreMode")
* unused fields */
};
memset(response, 0, sizeof(*response));
rc_buffer_init(&response->response.buffer);
result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result != RC_OK || !response->response.succeeded)
return result;
result = rc_json_get_required_unum_array(&response->achievement_ids, &response->num_achievement_ids, &response->response, &fields[2], "UserUnlocks");
return result;
}
void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response) {
rc_buffer_destroy(&response->response.buffer);
}
+5635
View File
File diff suppressed because it is too large Load Diff
+132
View File
@@ -0,0 +1,132 @@
#ifndef RC_CLIENT_EXTERNAL_H
#define RC_CLIENT_EXTERNAL_H
#include "rc_client.h"
RC_BEGIN_C_DECLS
/* NOTE: any function that is passed a callback also needs to be passed a client instance to pass
* to the callback, and the external interface has to capture both. */
typedef void (RC_CCONV *rc_client_external_enable_logging_func_t)(rc_client_t* client, int level, rc_client_message_callback_t callback);
typedef void (RC_CCONV *rc_client_external_set_event_handler_func_t)(rc_client_t* client, rc_client_event_handler_t handler);
typedef void (RC_CCONV *rc_client_external_set_read_memory_func_t)(rc_client_t* client, rc_client_read_memory_func_t handler);
typedef void (RC_CCONV *rc_client_external_set_get_time_millisecs_func_t)(rc_client_t* client, rc_get_time_millisecs_func_t handler);
typedef int (RC_CCONV *rc_client_external_can_pause_func_t)(uint32_t* frames_remaining);
typedef void (RC_CCONV *rc_client_external_set_int_func_t)(int value);
typedef int (RC_CCONV *rc_client_external_get_int_func_t)(void);
typedef void (RC_CCONV *rc_client_external_set_string_func_t)(const char* value);
typedef size_t (RC_CCONV *rc_client_external_copy_string_func_t)(char buffer[], size_t buffer_size);
typedef void (RC_CCONV *rc_client_external_action_func_t)(void);
typedef void (RC_CCONV *rc_client_external_async_handle_func_t)(rc_client_async_handle_t* handle);
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_login_func_t)(rc_client_t* client,
const char* username, const char* pass_token, rc_client_callback_t callback, void* callback_userdata);
typedef const rc_client_user_t* (RC_CCONV *rc_client_external_get_user_info_func_t)(void);
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_identify_and_load_game_func_t)(
rc_client_t* client, uint32_t console_id, const char* file_path,
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_load_game_func_t)(rc_client_t* client,
const char* hash, rc_client_callback_t callback, void* callback_userdata);
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_load_subset_t)(rc_client_t* client,
uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata);
typedef const rc_client_game_t* (RC_CCONV *rc_client_external_get_game_info_func_t)(void);
typedef const rc_client_subset_t* (RC_CCONV *rc_client_external_get_subset_info_func_t)(uint32_t subset_id);
typedef void (RC_CCONV *rc_client_external_get_user_game_summary_func_t)(rc_client_user_game_summary_t* summary);
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_change_media_func_t)(rc_client_t* client, const char* file_path,
const uint8_t* data, size_t data_size, rc_client_callback_t callback, void* callback_userdata);
/* NOTE: rc_client_external_create_achievement_list_func_t returns an internal wrapper structure which contains the public list
* and a destructor function. */
struct rc_client_achievement_list_info_t;
typedef struct rc_client_achievement_list_info_t* (RC_CCONV *rc_client_external_create_achievement_list_func_t)(int category, int grouping);
typedef const rc_client_achievement_t* (RC_CCONV *rc_client_external_get_achievement_info_func_t)(uint32_t id);
/* NOTE: rc_client_external_create_leaderboard_list_func_t returns an internal wrapper structure which contains the public list
* and a destructor function. */
struct rc_client_leaderboard_list_info_t;
typedef struct rc_client_leaderboard_list_info_t* (RC_CCONV *rc_client_external_create_leaderboard_list_func_t)(int grouping);
typedef const rc_client_leaderboard_t* (RC_CCONV *rc_client_external_get_leaderboard_info_func_t)(uint32_t id);
/* NOTE: rc_client_external_begin_fetch_leaderboard_entries_func_t and rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t
* pass an internal wrapper structure around the list, which contains the public list and a destructor function. */
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_fetch_leaderboard_entries_func_t)(rc_client_t* client,
uint32_t leaderboard_id, uint32_t first_entry, uint32_t count,
rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata);
typedef rc_client_async_handle_t* (RC_CCONV *rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t)(rc_client_t* client,
uint32_t leaderboard_id, uint32_t count, rc_client_fetch_leaderboard_entries_callback_t callback, void* callback_userdata);
typedef size_t (RC_CCONV *rc_client_external_progress_size_func_t)(void);
typedef int (RC_CCONV *rc_client_external_serialize_progress_func_t)(uint8_t* buffer);
typedef int (RC_CCONV *rc_client_external_deserialize_progress_func_t)(const uint8_t* buffer);
typedef struct rc_client_external_t
{
rc_client_external_action_func_t destroy;
rc_client_external_enable_logging_func_t enable_logging;
rc_client_external_set_event_handler_func_t set_event_handler;
rc_client_external_set_read_memory_func_t set_read_memory;
rc_client_external_set_get_time_millisecs_func_t set_get_time_millisecs;
rc_client_external_set_string_func_t set_host;
rc_client_external_copy_string_func_t get_user_agent_clause;
rc_client_external_set_int_func_t set_hardcore_enabled;
rc_client_external_get_int_func_t get_hardcore_enabled;
rc_client_external_set_int_func_t set_unofficial_enabled;
rc_client_external_get_int_func_t get_unofficial_enabled;
rc_client_external_set_int_func_t set_encore_mode_enabled;
rc_client_external_get_int_func_t get_encore_mode_enabled;
rc_client_external_set_int_func_t set_spectator_mode_enabled;
rc_client_external_get_int_func_t get_spectator_mode_enabled;
rc_client_external_async_handle_func_t abort_async;
rc_client_external_begin_login_func_t begin_login_with_password;
rc_client_external_begin_login_func_t begin_login_with_token;
rc_client_external_action_func_t logout;
rc_client_external_get_user_info_func_t get_user_info;
rc_client_external_begin_identify_and_load_game_func_t begin_identify_and_load_game;
rc_client_external_begin_load_game_func_t begin_load_game;
rc_client_external_get_game_info_func_t get_game_info;
rc_client_external_begin_load_subset_t begin_load_subset;
rc_client_external_get_subset_info_func_t get_subset_info;
rc_client_external_action_func_t unload_game;
rc_client_external_get_user_game_summary_func_t get_user_game_summary;
rc_client_external_begin_change_media_func_t begin_change_media;
rc_client_external_create_achievement_list_func_t create_achievement_list;
rc_client_external_get_int_func_t has_achievements;
rc_client_external_get_achievement_info_func_t get_achievement_info;
rc_client_external_create_leaderboard_list_func_t create_leaderboard_list;
rc_client_external_get_int_func_t has_leaderboards;
rc_client_external_get_leaderboard_info_func_t get_leaderboard_info;
rc_client_external_begin_fetch_leaderboard_entries_func_t begin_fetch_leaderboard_entries;
rc_client_external_begin_fetch_leaderboard_entries_around_user_func_t begin_fetch_leaderboard_entries_around_user;
rc_client_external_copy_string_func_t get_rich_presence_message;
rc_client_external_get_int_func_t has_rich_presence;
rc_client_external_action_func_t do_frame;
rc_client_external_action_func_t idle;
rc_client_external_get_int_func_t is_processing_required;
rc_client_external_can_pause_func_t can_pause;
rc_client_external_action_func_t reset;
rc_client_external_progress_size_func_t progress_size;
rc_client_external_serialize_progress_func_t serialize_progress;
rc_client_external_deserialize_progress_func_t deserialize_progress;
} rc_client_external_t;
#define RC_CLIENT_EXTERNAL_VERSION 1
RC_END_C_DECLS
#endif /* RC_CLIENT_EXTERNAL_H */
+394
View File
@@ -0,0 +1,394 @@
#ifndef RC_CLIENT_INTERNAL_H
#define RC_CLIENT_INTERNAL_H
#include "rc_client.h"
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
#include "rc_client_raintegration_internal.h"
#endif
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
#include "rc_client_external.h"
#endif
#include "rc_compat.h"
#include "rc_runtime.h"
#include "rc_runtime_types.h"
RC_BEGIN_C_DECLS
/*****************************************************************************\
| Callbacks |
\*****************************************************************************/
struct rc_api_fetch_game_data_response_t;
typedef void (RC_CCONV *rc_client_post_process_game_data_response_t)(const rc_api_server_response_t* server_response,
struct rc_api_fetch_game_data_response_t* game_data_response, rc_client_t* client, void* userdata);
typedef int (RC_CCONV *rc_client_can_submit_achievement_unlock_t)(uint32_t achievement_id, rc_client_t* client);
typedef int (RC_CCONV *rc_client_can_submit_leaderboard_entry_t)(uint32_t leaderboard_id, rc_client_t* client);
typedef int (RC_CCONV *rc_client_rich_presence_override_t)(rc_client_t* client, char buffer[], size_t buffersize);
typedef struct rc_client_callbacks_t {
rc_client_read_memory_func_t read_memory;
rc_client_event_handler_t event_handler;
rc_client_server_call_t server_call;
rc_client_message_callback_t log_call;
rc_get_time_millisecs_func_t get_time_millisecs;
rc_client_post_process_game_data_response_t post_process_game_data_response;
rc_client_can_submit_achievement_unlock_t can_submit_achievement_unlock;
rc_client_can_submit_leaderboard_entry_t can_submit_leaderboard_entry;
rc_client_rich_presence_override_t rich_presence_override;
void* client_data;
} rc_client_callbacks_t;
struct rc_client_scheduled_callback_data_t;
typedef void (RC_CCONV *rc_client_scheduled_callback_t)(struct rc_client_scheduled_callback_data_t* callback_data, rc_client_t* client, rc_clock_t now);
typedef struct rc_client_scheduled_callback_data_t
{
rc_clock_t when;
uint32_t related_id;
rc_client_scheduled_callback_t callback;
void* data;
struct rc_client_scheduled_callback_data_t* next;
} rc_client_scheduled_callback_data_t;
void rc_client_schedule_callback(rc_client_t* client, rc_client_scheduled_callback_data_t* scheduled_callback);
struct rc_client_async_handle_t {
uint8_t aborted;
};
int rc_client_async_handle_aborted(rc_client_t* client, rc_client_async_handle_t* async_handle);
/*****************************************************************************\
| Achievements |
\*****************************************************************************/
enum {
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_NONE = 0,
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_TRIGGERED = (1 << 1),
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_SHOW = (1 << 2),
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_CHALLENGE_INDICATOR_HIDE = (1 << 3),
RC_CLIENT_ACHIEVEMENT_PENDING_EVENT_UPDATE = (1 << 4) /* not a real event, just triggers update */
};
typedef struct rc_client_achievement_info_t {
rc_client_achievement_t public_;
rc_trigger_t* trigger;
uint8_t md5[16];
time_t unlock_time_hardcore;
time_t unlock_time_softcore;
uint8_t pending_events;
const char* author;
time_t created_time;
time_t updated_time;
} rc_client_achievement_info_t;
struct rc_client_achievement_list_info_t;
typedef void (RC_CCONV *rc_client_destroy_achievement_list_func_t)(struct rc_client_achievement_list_info_t* list);
typedef struct rc_client_achievement_list_info_t {
rc_client_achievement_list_t public_;
rc_client_destroy_achievement_list_func_t destroy_func;
} rc_client_achievement_list_info_t;
enum {
RC_CLIENT_PROGRESS_TRACKER_ACTION_NONE,
RC_CLIENT_PROGRESS_TRACKER_ACTION_SHOW,
RC_CLIENT_PROGRESS_TRACKER_ACTION_UPDATE,
RC_CLIENT_PROGRESS_TRACKER_ACTION_HIDE
};
typedef struct rc_client_progress_tracker_t {
rc_client_achievement_info_t* achievement;
float progress;
rc_client_scheduled_callback_data_t* hide_callback;
uint8_t action;
} rc_client_progress_tracker_t;
/*****************************************************************************\
| Leaderboard Trackers |
\*****************************************************************************/
enum {
RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_NONE = 0,
RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_UPDATE = (1 << 1),
RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_SHOW = (1 << 2),
RC_CLIENT_LEADERBOARD_TRACKER_PENDING_EVENT_HIDE = (1 << 3)
};
typedef struct rc_client_leaderboard_tracker_info_t {
rc_client_leaderboard_tracker_t public_;
struct rc_client_leaderboard_tracker_info_t* next;
int32_t raw_value;
uint32_t value_djb2;
uint8_t format;
uint8_t pending_events;
uint8_t reference_count;
uint8_t value_from_hits;
} rc_client_leaderboard_tracker_info_t;
/*****************************************************************************\
| Leaderboards |
\*****************************************************************************/
enum {
RC_CLIENT_LEADERBOARD_PENDING_EVENT_NONE = 0,
RC_CLIENT_LEADERBOARD_PENDING_EVENT_STARTED = (1 << 1),
RC_CLIENT_LEADERBOARD_PENDING_EVENT_FAILED = (1 << 2),
RC_CLIENT_LEADERBOARD_PENDING_EVENT_SUBMITTED = (1 << 3)
};
typedef struct rc_client_leaderboard_info_t {
rc_client_leaderboard_t public_;
rc_lboard_t* lboard;
uint8_t md5[16];
rc_client_leaderboard_tracker_info_t* tracker;
uint32_t value_djb2;
int32_t value;
uint8_t format;
uint8_t pending_events;
uint8_t bucket;
uint8_t hidden;
} rc_client_leaderboard_info_t;
struct rc_client_leaderboard_list_info_t;
typedef void (RC_CCONV *rc_client_destroy_leaderboard_list_func_t)(struct rc_client_leaderboard_list_info_t* list);
typedef struct rc_client_leaderboard_list_info_t {
rc_client_leaderboard_list_t public_;
rc_client_destroy_leaderboard_list_func_t destroy_func;
} rc_client_leaderboard_list_info_t;
struct rc_client_leaderboard_entry_list_info_t;
typedef void (RC_CCONV *rc_client_destroy_leaderboard_entry_list_func_t)(struct rc_client_leaderboard_entry_list_info_t* list);
typedef struct rc_client_leaderboard_entry_list_info_t {
rc_client_leaderboard_entry_list_t public_;
rc_client_destroy_leaderboard_entry_list_func_t destroy_func;
} rc_client_leaderboard_entry_list_info_t;
uint8_t rc_client_map_leaderboard_format(int format);
/*****************************************************************************\
| Subsets |
\*****************************************************************************/
enum {
RC_CLIENT_SUBSET_PENDING_EVENT_NONE = 0,
RC_CLIENT_SUBSET_PENDING_EVENT_ACHIEVEMENT = (1 << 1),
RC_CLIENT_SUBSET_PENDING_EVENT_LEADERBOARD = (1 << 2)
};
typedef struct rc_client_subset_info_t {
rc_client_subset_t public_;
rc_client_achievement_info_t* achievements;
rc_client_leaderboard_info_t* leaderboards;
struct rc_client_subset_info_t* next;
const char* all_label;
const char* inactive_label;
const char* locked_label;
const char* unlocked_label;
const char* unofficial_label;
const char* unsupported_label;
uint8_t active;
uint8_t mastery;
uint8_t pending_events;
} rc_client_subset_info_t;
rc_client_async_handle_t* rc_client_begin_load_subset(rc_client_t* client, uint32_t subset_id, rc_client_callback_t callback, void* callback_userdata);
/*****************************************************************************\
| Game |
\*****************************************************************************/
typedef struct rc_client_game_hash_t {
char hash[33];
uint32_t game_id;
struct rc_client_game_hash_t* next;
} rc_client_game_hash_t;
rc_client_game_hash_t* rc_client_find_game_hash(rc_client_t* client, const char* hash);
typedef struct rc_client_media_hash_t {
rc_client_game_hash_t* game_hash;
struct rc_client_media_hash_t* next;
uint32_t path_djb2;
} rc_client_media_hash_t;
enum {
RC_CLIENT_GAME_PENDING_EVENT_NONE = 0,
RC_CLIENT_GAME_PENDING_EVENT_LEADERBOARD_TRACKER = (1 << 1),
RC_CLIENT_GAME_PENDING_EVENT_UPDATE_ACTIVE_ACHIEVEMENTS = (1 << 2),
RC_CLIENT_GAME_PENDING_EVENT_PROGRESS_TRACKER = (1 << 3)
};
typedef struct rc_client_game_info_t {
rc_client_game_t public_;
rc_client_leaderboard_tracker_info_t* leaderboard_trackers;
rc_client_progress_tracker_t progress_tracker;
rc_client_subset_info_t* subsets;
rc_client_media_hash_t* media_hash;
rc_runtime_t runtime;
uint32_t max_valid_address;
uint8_t waiting_for_reset;
uint8_t pending_events;
rc_buffer_t buffer;
} rc_client_game_info_t;
void rc_client_update_active_achievements(rc_client_game_info_t* game);
void rc_client_update_active_leaderboards(rc_client_game_info_t* game);
/*****************************************************************************\
| Client |
\*****************************************************************************/
enum {
RC_CLIENT_LOAD_STATE_NONE,
RC_CLIENT_LOAD_STATE_IDENTIFYING_GAME,
RC_CLIENT_LOAD_STATE_AWAIT_LOGIN,
RC_CLIENT_LOAD_STATE_FETCHING_GAME_DATA,
RC_CLIENT_LOAD_STATE_STARTING_SESSION,
RC_CLIENT_LOAD_STATE_DONE,
RC_CLIENT_LOAD_STATE_UNKNOWN_GAME
};
enum {
RC_CLIENT_USER_STATE_NONE,
RC_CLIENT_USER_STATE_LOGIN_REQUESTED,
RC_CLIENT_USER_STATE_LOGGED_IN
};
enum {
RC_CLIENT_MASTERY_STATE_NONE,
RC_CLIENT_MASTERY_STATE_PENDING,
RC_CLIENT_MASTERY_STATE_SHOWN
};
enum {
RC_CLIENT_SPECTATOR_MODE_OFF,
RC_CLIENT_SPECTATOR_MODE_ON,
RC_CLIENT_SPECTATOR_MODE_LOCKED
};
enum {
RC_CLIENT_DISCONNECT_HIDDEN = 0,
RC_CLIENT_DISCONNECT_VISIBLE = (1 << 0),
RC_CLIENT_DISCONNECT_SHOW_PENDING = (1 << 1),
RC_CLIENT_DISCONNECT_HIDE_PENDING = (1 << 2)
};
struct rc_client_load_state_t;
typedef struct rc_client_state_t {
rc_mutex_t mutex;
rc_buffer_t buffer;
rc_client_scheduled_callback_data_t* scheduled_callbacks;
#ifdef RC_CLIENT_SUPPORTS_EXTERNAL
rc_client_external_t* external_client;
#endif
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
rc_client_raintegration_t* raintegration;
#endif
uint16_t unpaused_frame_decay;
uint16_t required_unpaused_frames;
uint8_t hardcore;
uint8_t encore_mode;
uint8_t spectator_mode;
uint8_t unofficial_enabled;
uint8_t log_level;
uint8_t user;
uint8_t disconnect;
uint8_t allow_leaderboards_in_softcore;
struct rc_client_load_state_t* load;
struct rc_client_async_handle_t* async_handles[4];
rc_memref_t* processing_memref;
rc_peek_t legacy_peek;
} rc_client_state_t;
struct rc_client_t {
rc_client_game_info_t* game;
rc_client_game_hash_t* hashes;
rc_client_user_t user;
rc_client_callbacks_t callbacks;
rc_client_state_t state;
};
/*****************************************************************************\
| Helpers |
\*****************************************************************************/
#ifdef RC_NO_VARIADIC_MACROS
void RC_CLIENT_LOG_ERR_FORMATTED(const rc_client_t* client, const char* format, ...);
void RC_CLIENT_LOG_WARN_FORMATTED(const rc_client_t* client, const char* format, ...);
void RC_CLIENT_LOG_INFO_FORMATTED(const rc_client_t* client, const char* format, ...);
void RC_CLIENT_LOG_VERBOSE_FORMATTED(const rc_client_t* client, const char* format, ...);
#else
void rc_client_log_message_formatted(const rc_client_t* client, const char* format, ...);
#define RC_CLIENT_LOG_ERR_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_ERROR) rc_client_log_message_formatted(client, format, __VA_ARGS__); }
#define RC_CLIENT_LOG_WARN_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_WARN) rc_client_log_message_formatted(client, format, __VA_ARGS__); }
#define RC_CLIENT_LOG_INFO_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO) rc_client_log_message_formatted(client, format, __VA_ARGS__); }
#define RC_CLIENT_LOG_VERBOSE_FORMATTED(client, format, ...) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_VERBOSE) rc_client_log_message_formatted(client, format, __VA_ARGS__); }
#endif
void rc_client_log_message(const rc_client_t* client, const char* message);
#define RC_CLIENT_LOG_ERR(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_ERROR) rc_client_log_message(client, message); }
#define RC_CLIENT_LOG_WARN(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_WARN) rc_client_log_message(client, message); }
#define RC_CLIENT_LOG_INFO(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_INFO) rc_client_log_message(client, message); }
#define RC_CLIENT_LOG_VERBOSE(client, message) { if (client->state.log_level >= RC_CLIENT_LOG_LEVEL_VERBOSE) rc_client_log_message(client, message); }
/* internals pulled from runtime.c */
void rc_runtime_checksum(const char* memaddr, uint8_t* md5);
int rc_trigger_contains_memref(const rc_trigger_t* trigger, const rc_memref_t* memref);
int rc_value_contains_memref(const rc_value_t* value, const rc_memref_t* memref);
/* end runtime.c internals */
/* helper functions for unit tests */
struct rc_hash_iterator;
struct rc_hash_iterator* rc_client_get_load_state_hash_iterator(rc_client_t* client);
/* end helper functions for unit tests */
enum {
RC_CLIENT_LEGACY_PEEK_AUTO,
RC_CLIENT_LEGACY_PEEK_CONSTRUCTED,
RC_CLIENT_LEGACY_PEEK_LITTLE_ENDIAN_READS
};
void rc_client_set_legacy_peek(rc_client_t* client, int method);
void rc_client_release_leaderboard_tracker(rc_client_game_info_t* game, rc_client_leaderboard_info_t* leaderboard);
RC_END_C_DECLS
#endif /* RC_CLIENT_INTERNAL_H */
+493
View File
@@ -0,0 +1,493 @@
#include "rc_client_raintegration_internal.h"
#include "rc_client_internal.h"
#include "rapi/rc_api_common.h"
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
static void rc_client_raintegration_load_dll(rc_client_t* client,
const wchar_t* search_directory, rc_client_callback_t callback, void* callback_userdata)
{
wchar_t sPath[_MAX_PATH];
const int nPathSize = sizeof(sPath) / sizeof(sPath[0]);
rc_client_raintegration_t* raintegration;
int sPathIndex = 0;
DWORD dwAttrib;
HINSTANCE hDLL;
if (search_directory) {
sPathIndex = swprintf_s(sPath, nPathSize, L"%s\\", search_directory);
if (sPathIndex > nPathSize - 22) {
callback(RC_INVALID_STATE, "search_directory too long", client, callback_userdata);
return;
}
}
#if defined(_M_X64) || defined(__amd64__)
wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration-x64.dll");
dwAttrib = GetFileAttributesW(sPath);
if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration.dll");
dwAttrib = GetFileAttributesW(sPath);
}
#else
wcscpy_s(&sPath[sPathIndex], nPathSize - sPathIndex, L"RA_Integration.dll");
dwAttrib = GetFileAttributesW(sPath);
#endif
if (dwAttrib == INVALID_FILE_ATTRIBUTES) {
callback(RC_MISSING_VALUE, "RA_Integration.dll not found in search directory", client, callback_userdata);
return;
}
hDLL = LoadLibraryW(sPath);
if (hDLL == NULL) {
char error_message[512];
const DWORD last_error = GetLastError();
int offset = snprintf(error_message, sizeof(error_message), "Failed to load RA_Integration.dll (%u)", last_error);
if (last_error != 0) {
LPSTR messageBuffer = NULL;
const DWORD size = FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, last_error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, NULL);
snprintf(&error_message[offset], sizeof(error_message) - offset, ": %.*s", size, messageBuffer);
LocalFree(messageBuffer);
}
callback(RC_ABORTED, error_message, client, callback_userdata);
return;
}
raintegration = (rc_client_raintegration_t*)rc_buffer_alloc(&client->state.buffer, sizeof(rc_client_raintegration_t));
memset(raintegration, 0, sizeof(*raintegration));
raintegration->hDLL = hDLL;
raintegration->get_version = (rc_client_raintegration_get_string_func_t)GetProcAddress(hDLL, "_RA_IntegrationVersion");
raintegration->get_host_url = (rc_client_raintegration_get_string_func_t)GetProcAddress(hDLL, "_RA_HostUrl");
raintegration->init_client = (rc_client_raintegration_init_client_func_t)GetProcAddress(hDLL, "_RA_InitClient");
raintegration->init_client_offline = (rc_client_raintegration_init_client_func_t)GetProcAddress(hDLL, "_RA_InitOffline");
raintegration->shutdown = (rc_client_raintegration_action_func_t)GetProcAddress(hDLL, "_RA_Shutdown");
raintegration->update_main_window_handle = (rc_client_raintegration_hwnd_action_func_t)GetProcAddress(hDLL, "_RA_UpdateHWnd");
raintegration->get_external_client = (rc_client_raintegration_get_external_client_func_t)GetProcAddress(hDLL, "_Rcheevos_GetExternalClient");
raintegration->get_menu = (rc_client_raintegration_get_menu_func_t)GetProcAddress(hDLL, "_Rcheevos_RAIntegrationGetMenu");
raintegration->activate_menu_item = (rc_client_raintegration_activate_menuitem_func_t)GetProcAddress(hDLL, "_Rcheevos_ActivateRAIntegrationMenuItem");
raintegration->set_write_memory_function = (rc_client_raintegration_set_write_memory_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationWriteMemoryFunction");
raintegration->set_event_handler = (rc_client_raintegration_set_event_handler_func_t)GetProcAddress(hDLL, "_Rcheevos_SetRAIntegrationEventHandler");
if (!raintegration->get_version ||
!raintegration->init_client ||
!raintegration->get_external_client) {
FreeLibrary(hDLL);
callback(RC_ABORTED, "One or more required exports was not found in RA_Integration.dll", client, callback_userdata);
}
else {
rc_mutex_lock(&client->state.mutex);
client->state.raintegration = raintegration;
rc_mutex_unlock(&client->state.mutex);
RC_CLIENT_LOG_INFO_FORMATTED(client, "RA_Integration.dll %s loaded", client->state.raintegration->get_version());
}
}
typedef struct rc_client_version_validation_callback_data_t {
rc_client_t* client;
rc_client_callback_t callback;
void* callback_userdata;
HWND main_window_handle;
char* client_name;
char* client_version;
rc_client_async_handle_t async_handle;
} rc_client_version_validation_callback_data_t;
int rc_client_version_less(const char* left, const char* right)
{
do {
int left_len = 0;
int right_len = 0;
while (*left && *left == '0')
++left;
while (left[left_len] && left[left_len] != '.')
++left_len;
while (*right && *right == '0')
++right;
while (right[right_len] && right[right_len] != '.')
++right_len;
if (left_len != right_len)
return (left_len < right_len);
while (left_len--) {
if (*left != *right)
return (*left < *right);
++left;
++right;
}
if (*left == '.')
++left;
if (*right == '.')
++right;
} while (*left || *right);
return 0;
}
static void rc_client_init_raintegration(rc_client_t* client,
rc_client_version_validation_callback_data_t* version_validation_callback_data)
{
rc_client_raintegration_init_client_func_t init_func = client->state.raintegration->init_client;
if (client->state.raintegration->get_host_url) {
const char* host_url = client->state.raintegration->get_host_url();
if (host_url) {
if (strcmp(host_url, "OFFLINE") != 0) {
rc_client_set_host(client, host_url);
}
else if (client->state.raintegration->init_client_offline) {
init_func = client->state.raintegration->init_client_offline;
RC_CLIENT_LOG_INFO(client, "Initializing in offline mode");
}
}
}
if (!init_func || !init_func(version_validation_callback_data->main_window_handle,
version_validation_callback_data->client_name,
version_validation_callback_data->client_version)) {
const char* error_message = "RA_Integration initialization failed";
rc_client_unload_raintegration(client);
RC_CLIENT_LOG_ERR(client, error_message);
version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata);
}
else {
rc_client_external_t* external_client = (rc_client_external_t*)
rc_buffer_alloc(&client->state.buffer, sizeof(*external_client));
memset(external_client, 0, sizeof(*external_client));
if (!client->state.raintegration->get_external_client(external_client, RC_CLIENT_EXTERNAL_VERSION)) {
const char* error_message = "RA_Integration external client export failed";
rc_client_unload_raintegration(client);
RC_CLIENT_LOG_ERR(client, error_message);
version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata);
}
else {
/* copy state to the external client */
if (external_client->enable_logging)
external_client->enable_logging(client, client->state.log_level, client->callbacks.log_call);
if (external_client->set_event_handler)
external_client->set_event_handler(client, client->callbacks.event_handler);
if (external_client->set_read_memory)
external_client->set_read_memory(client, client->callbacks.read_memory);
if (external_client->set_hardcore_enabled)
external_client->set_hardcore_enabled(rc_client_get_hardcore_enabled(client));
if (external_client->set_unofficial_enabled)
external_client->set_unofficial_enabled(rc_client_get_unofficial_enabled(client));
if (external_client->set_encore_mode_enabled)
external_client->set_encore_mode_enabled(rc_client_get_encore_mode_enabled(client));
if (external_client->set_spectator_mode_enabled)
external_client->set_spectator_mode_enabled(rc_client_get_spectator_mode_enabled(client));
/* attach the external client and call the callback */
client->state.external_client = external_client;
client->state.raintegration->bIsInited = 1;
version_validation_callback_data->callback(RC_OK, NULL,
client, version_validation_callback_data->callback_userdata);
}
}
}
static void rc_client_version_validation_callback(const rc_api_server_response_t* server_response, void* callback_data)
{
rc_client_version_validation_callback_data_t* version_validation_callback_data =
(rc_client_version_validation_callback_data_t*)callback_data;
rc_client_t* client = version_validation_callback_data->client;
if (rc_client_async_handle_aborted(client, &version_validation_callback_data->async_handle)) {
RC_CLIENT_LOG_VERBOSE(client, "Version validation aborted");
}
else {
rc_api_response_t response;
int result;
const char* current_version;
const char* minimum_version = "";
rc_json_field_t fields[] = {
RC_JSON_NEW_FIELD("Success"),
RC_JSON_NEW_FIELD("Error"),
RC_JSON_NEW_FIELD("MinimumVersion"),
};
memset(&response, 0, sizeof(response));
rc_buffer_init(&response.buffer);
result = rc_json_parse_server_response(&response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
if (result == RC_OK) {
if (!rc_json_get_required_string(&minimum_version, &response, &fields[2], "MinimumVersion"))
result = RC_MISSING_VALUE;
}
if (result != RC_OK) {
RC_CLIENT_LOG_ERR_FORMATTED(client, "Failed to fetch latest integration version: %.*s", server_response->body_length, server_response->body);
rc_client_unload_raintegration(client);
version_validation_callback_data->callback(result, rc_error_str(result),
client, version_validation_callback_data->callback_userdata);
}
else {
current_version = client->state.raintegration->get_version();
if (rc_client_version_less(current_version, minimum_version)) {
char error_message[256];
rc_client_unload_raintegration(client);
snprintf(error_message, sizeof(error_message),
"RA_Integration version %s is lower than minimum version %s", current_version, minimum_version);
RC_CLIENT_LOG_WARN(client, error_message);
version_validation_callback_data->callback(RC_ABORTED, error_message, client, version_validation_callback_data->callback_userdata);
}
else {
RC_CLIENT_LOG_INFO_FORMATTED(client, "Validated RA_Integration version %s (minimum %s)", current_version, minimum_version);
rc_client_init_raintegration(client, version_validation_callback_data);
}
}
rc_buffer_destroy(&response.buffer);
}
free(version_validation_callback_data->client_name);
free(version_validation_callback_data->client_version);
free(version_validation_callback_data);
}
rc_client_async_handle_t* rc_client_begin_load_raintegration(rc_client_t* client,
const wchar_t* search_directory, HWND main_window_handle,
const char* client_name, const char* client_version,
rc_client_callback_t callback, void* callback_userdata)
{
rc_client_version_validation_callback_data_t* callback_data;
rc_api_url_builder_t builder;
rc_api_request_t request;
if (!client) {
callback(RC_INVALID_STATE, "client is required", client, callback_userdata);
return NULL;
}
if (!client_name) {
callback(RC_INVALID_STATE, "client_name is required", client, callback_userdata);
return NULL;
}
if (!client_version) {
callback(RC_INVALID_STATE, "client_version is required", client, callback_userdata);
return NULL;
}
if (client->state.user != RC_CLIENT_USER_STATE_NONE) {
callback(RC_INVALID_STATE, "Cannot initialize RAIntegration after login", client, callback_userdata);
return NULL;
}
if (!client->state.raintegration) {
if (!main_window_handle) {
callback(RC_INVALID_STATE, "main_window_handle is required", client, callback_userdata);
return NULL;
}
rc_client_raintegration_load_dll(client, search_directory, callback, callback_userdata);
if (!client->state.raintegration)
return NULL;
}
if (client->state.raintegration->get_host_url) {
const char* host_url = client->state.raintegration->get_host_url();
if (host_url && strcmp(host_url, "https://retroachievements.org") != 0 &&
strcmp(host_url, "OFFLINE") != 0) {
/* if the DLL specifies a custom host, use it */
rc_client_set_host(client, host_url);
}
}
memset(&request, 0, sizeof(request));
rc_api_url_build_dorequest_url(&request);
rc_url_builder_init(&builder, &request.buffer, 48);
rc_url_builder_append_str_param(&builder, "r", "latestintegration");
request.post_data = rc_url_builder_finalize(&builder);
callback_data = calloc(1, sizeof(*callback_data));
if (!callback_data) {
callback(RC_OUT_OF_MEMORY, rc_error_str(RC_OUT_OF_MEMORY), client, callback_userdata);
return NULL;
}
callback_data->client = client;
callback_data->callback = callback;
callback_data->callback_userdata = callback_userdata;
callback_data->client_name = strdup(client_name);
callback_data->client_version = strdup(client_version);
callback_data->main_window_handle = main_window_handle;
client->callbacks.server_call(&request, rc_client_version_validation_callback, callback_data, client);
return &callback_data->async_handle;
}
void rc_client_raintegration_update_main_window_handle(rc_client_t* client, HWND main_window_handle)
{
if (client && client->state.raintegration &&
client->state.raintegration->bIsInited &&
client->state.raintegration->update_main_window_handle)
{
client->state.raintegration->update_main_window_handle(main_window_handle);
}
}
void rc_client_raintegration_set_write_memory_function(rc_client_t* client, rc_client_raintegration_write_memory_func_t handler)
{
if (client && client->state.raintegration && client->state.raintegration->set_write_memory_function)
client->state.raintegration->set_write_memory_function(client, handler);
}
void rc_client_raintegration_set_event_handler(rc_client_t* client,
rc_client_raintegration_event_handler_t handler)
{
if (client && client->state.raintegration && client->state.raintegration->set_event_handler)
client->state.raintegration->set_event_handler(client, handler);
}
const rc_client_raintegration_menu_t* rc_client_raintegration_get_menu(const rc_client_t* client)
{
if (!client || !client->state.raintegration ||
!client->state.raintegration->bIsInited ||
!client->state.raintegration->get_menu)
{
return NULL;
}
return client->state.raintegration->get_menu();
}
void rc_client_raintegration_rebuild_submenu(rc_client_t* client, HMENU hMenu)
{
HMENU hPopupMenu = NULL;
const rc_client_raintegration_menu_t* menu;
if (!client || !client->state.raintegration)
return;
/* destroy the existing menu */
if (client->state.raintegration->hPopupMenu)
DestroyMenu(client->state.raintegration->hPopupMenu);
/* create the popup menu */
hPopupMenu = CreatePopupMenu();
menu = rc_client_raintegration_get_menu(client);
if (menu && menu->num_items)
{
const rc_client_raintegration_menu_item_t* menuitem = menu->items;
const rc_client_raintegration_menu_item_t* stop = menu->items + menu->num_items;
for (; menuitem < stop; ++menuitem)
{
if (menuitem->id == 0)
AppendMenuA(hPopupMenu, MF_SEPARATOR, 0U, NULL);
else
{
UINT flags = MF_STRING;
if (menuitem->checked)
flags |= MF_CHECKED;
if (!menuitem->enabled)
flags |= MF_DISABLED | MF_GRAYED;
AppendMenuA(hPopupMenu, flags, menuitem->id, menuitem->label);
}
}
}
/* add/update the item containing the popup menu */
{
int nIndex = GetMenuItemCount(hMenu);
const char* menuText = "&RetroAchievements";
char buffer[64];
UINT flags = MF_POPUP | MF_STRING;
if (!menu || !menu->num_items)
flags |= MF_DISABLED | MF_GRAYED;
while (--nIndex >= 0)
{
if (GetMenuStringA(hMenu, nIndex, buffer, sizeof(buffer) - 1, MF_BYPOSITION))
{
if (strcmp(buffer, menuText) == 0)
break;
}
}
if (nIndex == -1)
AppendMenuA(hMenu, flags, (UINT_PTR)hPopupMenu, menuText);
else
ModifyMenuA(hMenu, nIndex, flags | MF_BYPOSITION, (UINT_PTR)hPopupMenu, menuText);
}
client->state.raintegration->hPopupMenu = hPopupMenu;
}
void rc_client_raintegration_update_menu_item(const rc_client_t* client, const rc_client_raintegration_menu_item_t* menuitem)
{
if (client && client->state.raintegration && client->state.raintegration->hPopupMenu)
{
UINT flags = MF_STRING;
if (menuitem->checked)
flags |= MF_CHECKED;
CheckMenuItem(client->state.raintegration->hPopupMenu, menuitem->id, flags | MF_BYCOMMAND);
}
}
int rc_client_raintegration_activate_menu_item(const rc_client_t* client, uint32_t nMenuItemId)
{
if (!client || !client->state.raintegration || !client->state.raintegration->activate_menu_item)
return 0;
return client->state.raintegration->activate_menu_item(nMenuItemId);
}
void rc_client_unload_raintegration(rc_client_t* client)
{
HINSTANCE hDLL;
if (!client || !client->state.raintegration)
return;
RC_CLIENT_LOG_INFO(client, "Unloading RA_Integration")
if (client->state.raintegration->shutdown)
client->state.raintegration->shutdown();
rc_mutex_lock(&client->state.mutex);
hDLL = client->state.raintegration->hDLL;
client->state.raintegration = NULL;
client->state.external_client = NULL;
rc_mutex_unlock(&client->state.mutex);
if (hDLL)
FreeLibrary(hDLL);
}
#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */
@@ -0,0 +1,52 @@
#ifndef RC_CLIENT_RAINTEGRATION_INTERNAL_H
#define RC_CLIENT_RAINTEGRATION_INTERNAL_H
#include "rc_client_raintegration.h"
#ifdef RC_CLIENT_SUPPORTS_RAINTEGRATION
#include "rc_client_external.h"
#include "rc_compat.h"
RC_BEGIN_C_DECLS
/* RAIntegration follows the same calling convention as rcheevos */
typedef void (RC_CCONV* rc_client_raintegration_action_func_t)(void);
typedef const char* (RC_CCONV* rc_client_raintegration_get_string_func_t)(void);
typedef int (RC_CCONV* rc_client_raintegration_init_client_func_t)(HWND hMainWnd, const char* sClientName, const char* sClientVersion);
typedef int (RC_CCONV* rc_client_raintegration_get_external_client_func_t)(rc_client_external_t* pClient, int nVersion);
typedef void (RC_CCONV* rc_client_raintegration_hwnd_action_func_t)(HWND hWnd);
typedef const rc_client_raintegration_menu_t* (RC_CCONV* rc_client_raintegration_get_menu_func_t)(void);
typedef int (RC_CCONV* rc_client_raintegration_activate_menuitem_func_t)(uint32_t nMenuItemId);
typedef void (RC_CCONV* rc_client_raintegration_set_write_memory_func_t)(rc_client_t* pClient, rc_client_raintegration_write_memory_func_t handler);
typedef void (RC_CCONV* rc_client_raintegration_set_event_handler_func_t)(rc_client_t* pClient, rc_client_raintegration_event_handler_t handler);
typedef struct rc_client_raintegration_t
{
HINSTANCE hDLL;
HMENU hPopupMenu;
uint8_t bIsInited;
rc_client_raintegration_get_string_func_t get_version;
rc_client_raintegration_get_string_func_t get_host_url;
rc_client_raintegration_init_client_func_t init_client;
rc_client_raintegration_init_client_func_t init_client_offline;
rc_client_raintegration_action_func_t shutdown;
rc_client_raintegration_hwnd_action_func_t update_main_window_handle;
rc_client_raintegration_set_write_memory_func_t set_write_memory_function;
rc_client_raintegration_set_event_handler_func_t set_event_handler;
rc_client_raintegration_get_menu_func_t get_menu;
rc_client_raintegration_activate_menuitem_func_t activate_menu_item;
rc_client_raintegration_get_external_client_func_t get_external_client;
} rc_client_raintegration_t;
RC_END_C_DECLS
#endif /* RC_CLIENT_SUPPORTS_RAINTEGRATION */
#endif /* RC_CLIENT_RAINTEGRATION_INTERNAL_H */
+164
View File
@@ -0,0 +1,164 @@
#include "rc_compat.h"
#include <ctype.h>
#include <stdarg.h>
#ifdef RC_C89_HELPERS
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);
if (buffer)
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);
#ifdef __STDC_WANT_SECURE_LIB__
result = vsprintf_s(buffer, size, format, args);
#else
/* assume buffer is large enough and ignore size */
(void)size;
result = vsprintf(buffer, format, args);
#endif
va_end(args);
return result;
}
#endif
#ifndef __STDC_WANT_SECURE_LIB__
struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer)
{
struct tm* tm = gmtime(timer);
memcpy(buf, tm, sizeof(*tm));
return buf;
}
#endif
#ifndef RC_NO_THREADS
#if defined(_WIN32)
/* https://gist.github.com/roxlu/1c1af99f92bafff9d8d9 */
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
void rc_mutex_init(rc_mutex_t* mutex)
{
/* default security, not owned by calling thread, unnamed */
mutex->handle = CreateMutex(NULL, FALSE, NULL);
}
void rc_mutex_destroy(rc_mutex_t* mutex)
{
CloseHandle(mutex->handle);
}
void rc_mutex_lock(rc_mutex_t* mutex)
{
WaitForSingleObject(mutex->handle, 0xFFFFFFFF);
}
void rc_mutex_unlock(rc_mutex_t* mutex)
{
ReleaseMutex(mutex->handle);
}
#elif defined(GEKKO)
/* https://github.com/libretro/RetroArch/pull/16116 */
void rc_mutex_init(rc_mutex_t* mutex)
{
LWP_MutexInit(mutex, NULL);
}
void rc_mutex_destroy(rc_mutex_t* mutex)
{
LWP_MutexDestroy(mutex);
}
void rc_mutex_lock(rc_mutex_t* mutex)
{
LWP_MutexLock(mutex);
}
void rc_mutex_unlock(rc_mutex_t* mutex)
{
LWP_MutexUnlock(mutex);
}
#else
void rc_mutex_init(rc_mutex_t* mutex)
{
pthread_mutex_init(mutex, NULL);
}
void rc_mutex_destroy(rc_mutex_t* mutex)
{
pthread_mutex_destroy(mutex);
}
void rc_mutex_lock(rc_mutex_t* mutex)
{
pthread_mutex_lock(mutex);
}
void rc_mutex_unlock(rc_mutex_t* mutex)
{
pthread_mutex_unlock(mutex);
}
#endif
#endif /* RC_NO_THREADS */
+97
View File
@@ -0,0 +1,97 @@
#ifndef RC_COMPAT_H
#define RC_COMPAT_H
#include "rc_export.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
RC_BEGIN_C_DECLS
#if defined(MINGW) || defined(__MINGW32__) || defined(__MINGW64__)
/* MinGW redefinitions */
#define RC_NO_VARIADIC_MACROS 1
#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 */
#define RC_C89_HELPERS 1
#define RC_NO_VARIADIC_MACROS 1
#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 */
#ifndef __STDC_WANT_SECURE_LIB__
/* _CRT_SECURE_NO_WARNINGS redefinitions */
#define strcpy_s(dest, sz, src) strcpy(dest, src)
#define sscanf_s sscanf
/* NOTE: Microsoft secure gmtime_s parameter order differs from C11 standard */
#include <time.h>
extern struct tm* rc_gmtime_s(struct tm* buf, const time_t* timer);
#define gmtime_s rc_gmtime_s
#endif
#ifdef RC_NO_THREADS
typedef int rc_mutex_t;
#define rc_mutex_init(mutex)
#define rc_mutex_destroy(mutex)
#define rc_mutex_lock(mutex)
#define rc_mutex_unlock(mutex)
#else
#ifdef _WIN32
typedef struct rc_mutex_t {
void* handle; /* HANDLE is defined as "void*" */
} rc_mutex_t;
#else
#include <pthread.h>
typedef pthread_mutex_t rc_mutex_t;
#endif
void rc_mutex_init(rc_mutex_t* mutex);
void rc_mutex_destroy(rc_mutex_t* mutex);
void rc_mutex_lock(rc_mutex_t* mutex);
void rc_mutex_unlock(rc_mutex_t* mutex);
#endif
RC_END_C_DECLS
#endif /* RC_COMPAT_H */
+831
View File
@@ -0,0 +1,831 @@
/* This file provides a series of functions for integrating RetroAchievements with libretro.
* These functions will be called by a libretro frontend to validate certain expected behaviors
* and simplify mapping core data to the RAIntegration DLL.
*
* Originally designed to be shared between RALibretro and RetroArch, but will simplify
* integrating with any other frontends.
*/
#include "rc_libretro.h"
#include "rc_consoles.h"
#include "rc_compat.h"
#include <ctype.h>
#include <string.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_path_compare_extension(const char* path, const char* ext);
extern int rc_hash_error(const char* message);
static rc_libretro_message_callback rc_libretro_verbose_message_callback = NULL;
/* a value that starts with a comma is a CSV.
* if it starts with an exclamation point, it's everything but the provided value.
* if it starts with an exclamntion point followed by a comma, it's everything but the CSV values.
* values are case-insensitive */
typedef struct rc_disallowed_core_settings_t
{
const char* library_name;
const rc_disallowed_setting_t* disallowed_settings;
} rc_disallowed_core_settings_t;
static const rc_disallowed_setting_t _rc_disallowed_bsnes_settings[] = {
{ "bsnes_region", "pal" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_cap32_settings[] = {
{ "cap32_autorun", "disabled" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_dolphin_settings[] = {
{ "dolphin_cheats_enabled", "enabled" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_dosbox_pure_settings[] = {
{ "dosbox_pure_strict_mode", "false" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_duckstation_settings[] = {
{ "duckstation_CDROM.LoadImagePatches", "true" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_ecwolf_settings[] = {
{ "ecwolf-invulnerability", "enabled" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_fbneo_settings[] = {
{ "fbneo-allow-patched-romsets", "enabled" },
{ "fbneo-cheat-*", "!,Disabled,0 - Disabled" },
{ "fbneo-dipswitch-*", "Universe BIOS*" },
{ "fbneo-neogeo-mode", "UNIBIOS" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_fceumm_settings[] = {
{ "fceumm_region", ",PAL,Dendy" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_gpgx_settings[] = {
{ "genesis_plus_gx_lock_on", ",action replay (pro),game genie" },
{ "genesis_plus_gx_region_detect", "pal" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_gpgx_wide_settings[] = {
{ "genesis_plus_gx_wide_lock_on", ",action replay (pro),game genie" },
{ "genesis_plus_gx_wide_region_detect", "pal" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_mesen_settings[] = {
{ "mesen_region", ",PAL,Dendy" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_mesen_s_settings[] = {
{ "mesen-s_region", "PAL" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_neocd_settings[] = {
{ "neocd_bios", "uni-bios*" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_pcsx_rearmed_settings[] = {
{ "pcsx_rearmed_region", "pal" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_picodrive_settings[] = {
{ "picodrive_region", ",Europe,Japan PAL" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_ppsspp_settings[] = {
{ "ppsspp_cheats", "enabled" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_quasi88_settings[] = {
{ "q88_cpu_clock", ",1,2" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_smsplus_settings[] = {
{ "smsplus_region", "pal" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_snes9x_settings[] = {
{ "snes9x_gfx_clip", "disabled" },
{ "snes9x_gfx_transp", "disabled" },
{ "snes9x_layer_*", "disabled" },
{ "snes9x_region", "pal" },
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_vice_settings[] = {
{ "vice_autostart", "disabled" }, /* autostart dictates initial load and reset from menu */
{ "vice_reset", "!autostart" }, /* reset dictates behavior when pressing reset button (END) */
{ NULL, NULL }
};
static const rc_disallowed_setting_t _rc_disallowed_virtual_jaguar_settings[] = {
{ "virtualjaguar_pal", "enabled" },
{ NULL, NULL }
};
static const rc_disallowed_core_settings_t rc_disallowed_core_settings[] = {
{ "bsnes-mercury", _rc_disallowed_bsnes_settings },
{ "cap32", _rc_disallowed_cap32_settings },
{ "dolphin-emu", _rc_disallowed_dolphin_settings },
{ "DOSBox-pure", _rc_disallowed_dosbox_pure_settings },
{ "DuckStation", _rc_disallowed_duckstation_settings },
{ "ecwolf", _rc_disallowed_ecwolf_settings },
{ "FCEUmm", _rc_disallowed_fceumm_settings },
{ "FinalBurn Neo", _rc_disallowed_fbneo_settings },
{ "Genesis Plus GX", _rc_disallowed_gpgx_settings },
{ "Genesis Plus GX Wide", _rc_disallowed_gpgx_wide_settings },
{ "Mesen", _rc_disallowed_mesen_settings },
{ "Mesen-S", _rc_disallowed_mesen_s_settings },
{ "NeoCD", _rc_disallowed_neocd_settings },
{ "PPSSPP", _rc_disallowed_ppsspp_settings },
{ "PCSX-ReARMed", _rc_disallowed_pcsx_rearmed_settings },
{ "PicoDrive", _rc_disallowed_picodrive_settings },
{ "QUASI88", _rc_disallowed_quasi88_settings },
{ "SMS Plus GX", _rc_disallowed_smsplus_settings },
{ "Snes9x", _rc_disallowed_snes9x_settings },
{ "VICE x64", _rc_disallowed_vice_settings },
{ "Virtual Jaguar", _rc_disallowed_virtual_jaguar_settings },
{ NULL, NULL }
};
static int rc_libretro_string_equal_nocase_wildcard(const char* test, const char* value) {
char c1, c2;
while ((c1 = *test++)) {
if (tolower(c1) != tolower(c2 = *value++))
return (c2 == '*');
}
return (*value == '\0');
}
static int rc_libretro_match_value(const char* val, const char* match) {
/* if value starts with a comma, it's a CSV list of potential matches */
if (*match == ',') {
do {
const char* ptr = ++match;
size_t size;
while (*match && *match != ',')
++match;
size = match - ptr;
if (val[size] == '\0') {
if (memcmp(ptr, val, size) == 0) {
return 1;
}
else {
char buffer[128];
memcpy(buffer, ptr, size);
buffer[size] = '\0';
if (rc_libretro_string_equal_nocase_wildcard(buffer, val))
return 1;
}
}
} while (*match == ',');
return 0;
}
/* a leading exclamation point means the provided value(s) are not forbidden (are allowed) */
if (*match == '!')
return !rc_libretro_match_value(val, &match[1]);
/* just a single value, attempt to match it */
return rc_libretro_string_equal_nocase_wildcard(val, match);
}
int rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value) {
const char* key;
size_t key_len;
for (; disallowed_settings->setting; ++disallowed_settings) {
key = disallowed_settings->setting;
key_len = strlen(key);
if (key[key_len - 1] == '*') {
if (memcmp(setting, key, key_len - 1) == 0) {
if (rc_libretro_match_value(value, disallowed_settings->value))
return 0;
}
}
else {
if (memcmp(setting, key, key_len + 1) == 0) {
if (rc_libretro_match_value(value, disallowed_settings->value))
return 0;
}
}
}
return 1;
}
const rc_disallowed_setting_t* rc_libretro_get_disallowed_settings(const char* library_name) {
const rc_disallowed_core_settings_t* core_filter = rc_disallowed_core_settings;
size_t library_name_length;
if (!library_name || !library_name[0])
return NULL;
library_name_length = strlen(library_name) + 1;
while (core_filter->library_name) {
if (memcmp(core_filter->library_name, library_name, library_name_length) == 0)
return core_filter->disallowed_settings;
++core_filter;
}
return NULL;
}
typedef struct rc_disallowed_core_systems_t
{
const char* library_name;
const uint32_t disallowed_consoles[4];
} rc_disallowed_core_systems_t;
static const rc_disallowed_core_systems_t rc_disallowed_core_systems[] = {
/* https://github.com/libretro/Mesen-S/issues/8 */
{ "Mesen-S", { RC_CONSOLE_GAMEBOY, RC_CONSOLE_GAMEBOY_COLOR, 0 }},
{ NULL, { 0 } }
};
int rc_libretro_is_system_allowed(const char* library_name, uint32_t console_id) {
const rc_disallowed_core_systems_t* core_filter = rc_disallowed_core_systems;
size_t library_name_length;
size_t i;
if (!library_name || !library_name[0])
return 1;
library_name_length = strlen(library_name) + 1;
while (core_filter->library_name) {
if (memcmp(core_filter->library_name, library_name, library_name_length) == 0) {
for (i = 0; i < sizeof(core_filter->disallowed_consoles) / sizeof(core_filter->disallowed_consoles[0]); ++i) {
if (core_filter->disallowed_consoles[i] == console_id)
return 0;
}
break;
}
++core_filter;
}
return 1;
}
uint8_t* rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, uint32_t address, uint32_t* avail) {
uint32_t i;
for (i = 0; i < regions->count; ++i) {
const size_t size = regions->size[i];
if (address < size) {
if (regions->data[i] == NULL)
break;
if (avail)
*avail = (uint32_t)(size - address);
return &regions->data[i][address];
}
address -= (uint32_t)size;
}
if (avail)
*avail = 0;
return NULL;
}
uint8_t* rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, uint32_t address) {
return rc_libretro_memory_find_avail(regions, address, NULL);
}
uint32_t rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, uint32_t address,
uint8_t* buffer, uint32_t num_bytes) {
uint32_t bytes_read = 0;
uint32_t avail;
uint32_t i;
for (i = 0; i < regions->count; ++i) {
const size_t size = regions->size[i];
if (address >= size) {
/* address is not in this block, adjust and look at next block */
address -= (unsigned)size;
continue;
}
if (regions->data[i] == NULL) /* no memory associated to this block. abort */
break;
avail = (unsigned)(size - address);
if (avail >= num_bytes) {
/* requested memory is fully within this block, copy and return it */
memcpy(buffer, &regions->data[i][address], num_bytes);
bytes_read += num_bytes;
return bytes_read;
}
/* copy whatever is available in this block, and adjust for the next block */
memcpy(buffer, &regions->data[i][address], avail);
buffer += avail;
bytes_read += avail;
num_bytes -= avail;
address = 0;
}
return bytes_read;
}
void rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback) {
rc_libretro_verbose_message_callback = callback;
}
static void rc_libretro_verbose(const char* message) {
if (rc_libretro_verbose_message_callback)
rc_libretro_verbose_message_callback(message);
}
static const char* rc_memory_type_str(int type) {
switch (type)
{
case RC_MEMORY_TYPE_SAVE_RAM:
return "SRAM";
case RC_MEMORY_TYPE_VIDEO_RAM:
return "VRAM";
case RC_MEMORY_TYPE_UNUSED:
return "UNUSED";
default:
break;
}
return "SYSTEM RAM";
}
static void rc_libretro_memory_register_region(rc_libretro_memory_regions_t* regions, int type,
uint8_t* data, size_t size, const char* description) {
if (size == 0)
return;
if (regions->count == (sizeof(regions->size) / sizeof(regions->size[0]))) {
rc_libretro_verbose("Too many memory memory regions to register");
return;
}
if (!data && regions->count > 0 && !regions->data[regions->count - 1]) {
/* extend null region */
regions->size[regions->count - 1] += size;
}
else if (data && regions->count > 0 &&
data == (regions->data[regions->count - 1] + regions->size[regions->count - 1])) {
/* extend non-null region */
regions->size[regions->count - 1] += size;
}
else {
/* create new region */
regions->data[regions->count] = data;
regions->size[regions->count] = size;
++regions->count;
}
regions->total_size += size;
if (rc_libretro_verbose_message_callback) {
char message[128];
snprintf(message, sizeof(message), "Registered 0x%04X bytes of %s at $%06X (%s)", (unsigned)size,
rc_memory_type_str(type), (unsigned)(regions->total_size - size), description);
rc_libretro_verbose_message_callback(message);
}
}
static void rc_libretro_memory_init_without_regions(rc_libretro_memory_regions_t* regions,
rc_libretro_get_core_memory_info_func get_core_memory_info) {
/* no regions specified, assume system RAM followed by save RAM */
char description[64];
rc_libretro_core_memory_info_t info;
snprintf(description, sizeof(description), "offset 0x%06x", 0);
get_core_memory_info(RETRO_MEMORY_SYSTEM_RAM, &info);
if (info.size)
rc_libretro_memory_register_region(regions, RC_MEMORY_TYPE_SYSTEM_RAM, info.data, info.size, description);
get_core_memory_info(RETRO_MEMORY_SAVE_RAM, &info);
if (info.size)
rc_libretro_memory_register_region(regions, RC_MEMORY_TYPE_SAVE_RAM, info.data, info.size, description);
}
static const struct retro_memory_descriptor* rc_libretro_memory_get_descriptor(const struct retro_memory_map* mmap, uint32_t real_address, size_t* offset)
{
const struct retro_memory_descriptor* desc = mmap->descriptors;
const struct retro_memory_descriptor* end = desc + mmap->num_descriptors;
for (; desc < end; desc++) {
if (desc->select == 0) {
/* if select is 0, attempt to explcitly match the address */
if (real_address >= desc->start && real_address < desc->start + desc->len) {
*offset = real_address - desc->start;
return desc;
}
}
else {
/* otherwise, attempt to match the address by matching the select bits */
/* address is in the block if (addr & select) == (start & select) */
if (((desc->start ^ real_address) & desc->select) == 0) {
/* get the relative offset of the address from the start of the memory block */
uint32_t reduced_address = real_address - (unsigned)desc->start;
/* remove any bits from the reduced_address that correspond to the bits in the disconnect
* mask and collapse the remaining bits. this code was copied from the mmap_reduce function
* in RetroArch. i'm not exactly sure how it works, but it does. */
uint32_t disconnect_mask = (unsigned)desc->disconnect;
while (disconnect_mask) {
const uint32_t tmp = (disconnect_mask - 1) & ~disconnect_mask;
reduced_address = (reduced_address & tmp) | ((reduced_address >> 1) & ~tmp);
disconnect_mask = (disconnect_mask & (disconnect_mask - 1)) >> 1;
}
/* calculate the offset within the descriptor */
*offset = reduced_address;
/* sanity check - make sure the descriptor is large enough to hold the target address */
if (reduced_address < desc->len)
return desc;
}
}
}
*offset = 0;
return NULL;
}
static void rc_libretro_memory_init_from_memory_map(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
const rc_memory_regions_t* console_regions) {
char description[64];
uint32_t i;
uint8_t* region_start;
uint8_t* desc_start;
size_t desc_size;
size_t offset;
for (i = 0; i < console_regions->num_regions; ++i) {
const rc_memory_region_t* console_region = &console_regions->region[i];
size_t console_region_size = console_region->end_address - console_region->start_address + 1;
uint32_t real_address = console_region->real_address;
uint32_t disconnect_size = 0;
while (console_region_size > 0) {
const struct retro_memory_descriptor* desc = rc_libretro_memory_get_descriptor(mmap, real_address, &offset);
if (!desc) {
if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
snprintf(description, sizeof(description), "Could not map region starting at $%06X",
(unsigned)(real_address - console_region->real_address + console_region->start_address));
rc_libretro_verbose(description);
}
if (disconnect_size && console_region_size > disconnect_size) {
rc_libretro_memory_register_region(regions, console_region->type, NULL, disconnect_size, "null filler");
console_region_size -= disconnect_size;
real_address += disconnect_size;
disconnect_size = 0;
continue;
}
rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler");
break;
}
snprintf(description, sizeof(description), "descriptor %u, offset 0x%06X%s",
(unsigned)(desc - mmap->descriptors) + 1, (int)offset, desc->ptr ? "" : " [no pointer]");
if (desc->ptr) {
desc_start = (uint8_t*)desc->ptr + desc->offset;
region_start = desc_start + offset;
}
else {
region_start = NULL;
}
desc_size = desc->len - offset;
if (desc->disconnect && desc_size > desc->disconnect) {
/* if we need to extract a disconnect bit, the largest block we can read is up to
* the next time that bit flips */
/* https://stackoverflow.com/questions/12247186/find-the-lowest-set-bit */
disconnect_size = (desc->disconnect & -((int)desc->disconnect));
desc_size = disconnect_size - (real_address & (disconnect_size - 1));
}
if (console_region_size > desc_size) {
if (desc_size == 0) {
if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
snprintf(description, sizeof(description), "Could not map region starting at $%06X",
(unsigned)(real_address - console_region->real_address + console_region->start_address));
rc_libretro_verbose(description);
}
rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size, "null filler");
console_region_size = 0;
}
else {
rc_libretro_memory_register_region(regions, console_region->type, region_start, desc_size, description);
console_region_size -= desc_size;
real_address += (unsigned)desc_size;
}
}
else {
rc_libretro_memory_register_region(regions, console_region->type, region_start, console_region_size, description);
console_region_size = 0;
}
}
}
}
static uint32_t rc_libretro_memory_console_region_to_ram_type(uint8_t region_type) {
switch (region_type)
{
case RC_MEMORY_TYPE_SAVE_RAM:
return RETRO_MEMORY_SAVE_RAM;
case RC_MEMORY_TYPE_VIDEO_RAM:
return RETRO_MEMORY_VIDEO_RAM;
default:
break;
}
return RETRO_MEMORY_SYSTEM_RAM;
}
static void rc_libretro_memory_init_from_unmapped_memory(rc_libretro_memory_regions_t* regions,
rc_libretro_get_core_memory_info_func get_core_memory_info, const rc_memory_regions_t* console_regions) {
char description[64];
uint32_t i, j;
rc_libretro_core_memory_info_t info;
size_t offset;
for (i = 0; i < console_regions->num_regions; ++i) {
const rc_memory_region_t* console_region = &console_regions->region[i];
const size_t console_region_size = console_region->end_address - console_region->start_address + 1;
const uint32_t type = rc_libretro_memory_console_region_to_ram_type(console_region->type);
uint32_t base_address = 0;
for (j = 0; j <= i; ++j) {
const rc_memory_region_t* console_region2 = &console_regions->region[j];
if (rc_libretro_memory_console_region_to_ram_type(console_region2->type) == type) {
base_address = console_region2->start_address;
break;
}
}
offset = console_region->start_address - base_address;
get_core_memory_info(type, &info);
if (offset < info.size) {
info.size -= offset;
if (info.data) {
snprintf(description, sizeof(description), "offset 0x%06X", (int)offset);
info.data += offset;
}
else {
snprintf(description, sizeof(description), "null filler");
}
}
else {
if (rc_libretro_verbose_message_callback && console_region->type != RC_MEMORY_TYPE_UNUSED) {
snprintf(description, sizeof(description), "Could not map region starting at $%06X", (unsigned)console_region->start_address);
rc_libretro_verbose(description);
}
info.data = NULL;
info.size = 0;
}
if (console_region_size > info.size) {
/* want more than what is available, take what we can and null fill the rest */
rc_libretro_memory_register_region(regions, console_region->type, info.data, info.size, description);
rc_libretro_memory_register_region(regions, console_region->type, NULL, console_region_size - info.size, "null filler");
}
else {
/* only take as much as we need */
rc_libretro_memory_register_region(regions, console_region->type, info.data, console_region_size, description);
}
}
}
int rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
rc_libretro_get_core_memory_info_func get_core_memory_info, uint32_t console_id) {
const rc_memory_regions_t* console_regions = rc_console_memory_regions(console_id);
rc_libretro_memory_regions_t new_regions;
int has_valid_region = 0;
uint32_t i;
if (!regions)
return 0;
memset(&new_regions, 0, sizeof(new_regions));
if (console_regions == NULL || console_regions->num_regions == 0)
rc_libretro_memory_init_without_regions(&new_regions, get_core_memory_info);
else if (mmap && mmap->num_descriptors != 0)
rc_libretro_memory_init_from_memory_map(&new_regions, mmap, console_regions);
else
rc_libretro_memory_init_from_unmapped_memory(&new_regions, get_core_memory_info, console_regions);
/* determine if any valid regions were found */
for (i = 0; i < new_regions.count; i++) {
if (new_regions.data[i]) {
has_valid_region = 1;
break;
}
}
memcpy(regions, &new_regions, sizeof(*regions));
return has_valid_region;
}
void rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions) {
memset(regions, 0, sizeof(*regions));
}
void rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
const char* m3u_path, rc_libretro_get_image_path_func get_image_path) {
char image_path[1024];
char* m3u_contents;
char* ptr;
int64_t file_len;
void* file_handle;
int index = 0;
memset(hash_set, 0, sizeof(*hash_set));
if (!rc_path_compare_extension(m3u_path, "m3u"))
return;
file_handle = rc_file_open(m3u_path);
if (!file_handle) {
rc_hash_error("Could not open playlist");
return;
}
rc_file_seek(file_handle, 0, SEEK_END);
file_len = rc_file_tell(file_handle);
rc_file_seek(file_handle, 0, SEEK_SET);
m3u_contents = (char*)malloc((size_t)file_len + 1);
if (m3u_contents) {
rc_file_read(file_handle, m3u_contents, (int)file_len);
m3u_contents[file_len] = '\0';
rc_file_close(file_handle);
ptr = m3u_contents;
do
{
/* ignore whitespace */
while (isspace((int)*ptr))
++ptr;
if (*ptr == '#') {
/* ignore comment unless it's the special SAVEDISK extension */
if (memcmp(ptr, "#SAVEDISK:", 10) == 0) {
/* get the path to the save disk from the frontend, assign it a bogus hash so
* it doesn't get hashed later */
if (get_image_path(index, image_path, sizeof(image_path))) {
const char save_disk_hash[33] = "[SAVE DISK]";
rc_libretro_hash_set_add(hash_set, image_path, -1, save_disk_hash);
++index;
}
}
}
else {
/* non-empty line, tally a file */
++index;
}
/* find the end of the line */
while (*ptr && *ptr != '\n')
++ptr;
} while (*ptr);
free(m3u_contents);
}
if (hash_set->entries_count > 0) {
/* at least one save disk was found. make sure the core supports the #SAVEDISK: extension by
* asking for the last expected disk. if it's not found, assume no #SAVEDISK: support */
if (!get_image_path(index - 1, image_path, sizeof(image_path)))
hash_set->entries_count = 0;
}
}
void rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set) {
if (hash_set->entries)
free(hash_set->entries);
memset(hash_set, 0, sizeof(*hash_set));
}
static uint32_t rc_libretro_djb2(const char* input)
{
uint32_t result = 5381;
char c;
while ((c = *input++) != '\0')
result = ((result << 5) + result) + c; /* result = result * 33 + c */
return result;
}
void rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set,
const char* path, uint32_t game_id, const char hash[33]) {
const uint32_t path_djb2 = (path != NULL) ? rc_libretro_djb2(path) : 0;
struct rc_libretro_hash_entry_t* entry = NULL;
struct rc_libretro_hash_entry_t* scan;
struct rc_libretro_hash_entry_t* stop = hash_set->entries + hash_set->entries_count;;
if (path_djb2) {
/* attempt to match the path */
for (scan = hash_set->entries; scan < stop; ++scan) {
if (scan->path_djb2 == path_djb2) {
entry = scan;
break;
}
}
}
if (!entry)
{
/* entry not found, allocate a new one */
if (hash_set->entries_size == 0) {
hash_set->entries_size = 4;
hash_set->entries = (struct rc_libretro_hash_entry_t*)
malloc(hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t));
}
else if (hash_set->entries_count == hash_set->entries_size) {
hash_set->entries_size += 4;
hash_set->entries = (struct rc_libretro_hash_entry_t*)realloc(hash_set->entries,
hash_set->entries_size * sizeof(struct rc_libretro_hash_entry_t));
}
if (hash_set->entries == NULL) /* unexpected, but better than crashing */
return;
entry = hash_set->entries + hash_set->entries_count++;
}
/* update the entry */
entry->path_djb2 = path_djb2;
entry->game_id = game_id;
memcpy(entry->hash, hash, sizeof(entry->hash));
}
const char* rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path)
{
const uint32_t path_djb2 = rc_libretro_djb2(path);
struct rc_libretro_hash_entry_t* scan = hash_set->entries;
struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
for (; scan < stop; ++scan) {
if (scan->path_djb2 == path_djb2)
return scan->hash;
}
return NULL;
}
int rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash)
{
struct rc_libretro_hash_entry_t* scan = hash_set->entries;
struct rc_libretro_hash_entry_t* stop = scan + hash_set->entries_count;
for (; scan < stop; ++scan) {
if (memcmp(scan->hash, hash, sizeof(scan->hash)) == 0)
return scan->game_id;
}
return 0;
}
+93
View File
@@ -0,0 +1,93 @@
#ifndef RC_LIBRETRO_H
#define RC_LIBRETRO_H
#include "rc_export.h"
/* this file comes from the libretro repository, which is not an explicit submodule.
* the integration must set up paths appropriately to find it. */
#include <libretro.h>
#include <stddef.h>
#include <stdint.h>
RC_BEGIN_C_DECLS
/*****************************************************************************\
| Disallowed Settings |
\*****************************************************************************/
typedef struct rc_disallowed_setting_t
{
const char* setting;
const char* value;
} rc_disallowed_setting_t;
RC_EXPORT const rc_disallowed_setting_t* RC_CCONV rc_libretro_get_disallowed_settings(const char* library_name);
RC_EXPORT int RC_CCONV rc_libretro_is_setting_allowed(const rc_disallowed_setting_t* disallowed_settings, const char* setting, const char* value);
RC_EXPORT int RC_CCONV rc_libretro_is_system_allowed(const char* library_name, uint32_t console_id);
/*****************************************************************************\
| Memory Mapping |
\*****************************************************************************/
/* specifies a function to call for verbose logging */
typedef void (RC_CCONV *rc_libretro_message_callback)(const char*);
RC_EXPORT void RC_CCONV rc_libretro_init_verbose_message_callback(rc_libretro_message_callback callback);
#define RC_LIBRETRO_MAX_MEMORY_REGIONS 32
typedef struct rc_libretro_memory_regions_t
{
uint8_t* data[RC_LIBRETRO_MAX_MEMORY_REGIONS];
size_t size[RC_LIBRETRO_MAX_MEMORY_REGIONS];
size_t total_size;
uint32_t count;
} rc_libretro_memory_regions_t;
typedef struct rc_libretro_core_memory_info_t
{
uint8_t* data;
size_t size;
} rc_libretro_core_memory_info_t;
typedef void (RC_CCONV *rc_libretro_get_core_memory_info_func)(uint32_t id, rc_libretro_core_memory_info_t* info);
RC_EXPORT int RC_CCONV rc_libretro_memory_init(rc_libretro_memory_regions_t* regions, const struct retro_memory_map* mmap,
rc_libretro_get_core_memory_info_func get_core_memory_info, uint32_t console_id);
RC_EXPORT void RC_CCONV rc_libretro_memory_destroy(rc_libretro_memory_regions_t* regions);
RC_EXPORT uint8_t* RC_CCONV rc_libretro_memory_find(const rc_libretro_memory_regions_t* regions, uint32_t address);
RC_EXPORT uint8_t* RC_CCONV rc_libretro_memory_find_avail(const rc_libretro_memory_regions_t* regions, uint32_t address, uint32_t* avail);
RC_EXPORT uint32_t RC_CCONV rc_libretro_memory_read(const rc_libretro_memory_regions_t* regions, uint32_t address, uint8_t* buffer, uint32_t num_bytes);
/*****************************************************************************\
| Disk Identification |
\*****************************************************************************/
typedef struct rc_libretro_hash_entry_t
{
uint32_t path_djb2;
uint32_t game_id;
char hash[33];
} rc_libretro_hash_entry_t;
typedef struct rc_libretro_hash_set_t
{
struct rc_libretro_hash_entry_t* entries;
uint16_t entries_count;
uint16_t entries_size;
} rc_libretro_hash_set_t;
typedef int (RC_CCONV *rc_libretro_get_image_path_func)(uint32_t index, char* buffer, size_t buffer_size);
RC_EXPORT void RC_CCONV rc_libretro_hash_set_init(struct rc_libretro_hash_set_t* hash_set,
const char* m3u_path, rc_libretro_get_image_path_func get_image_path);
RC_EXPORT void RC_CCONV rc_libretro_hash_set_destroy(struct rc_libretro_hash_set_t* hash_set);
RC_EXPORT void RC_CCONV rc_libretro_hash_set_add(struct rc_libretro_hash_set_t* hash_set,
const char* path, uint32_t game_id, const char hash[33]);
RC_EXPORT const char* RC_CCONV rc_libretro_hash_set_get_hash(const struct rc_libretro_hash_set_t* hash_set, const char* path);
RC_EXPORT int RC_CCONV rc_libretro_hash_set_get_game_id(const struct rc_libretro_hash_set_t* hash_set, const char* hash);
RC_END_C_DECLS
#endif /* RC_LIBRETRO_H */
+188
View File
@@ -0,0 +1,188 @@
#include "rc_util.h"
#include "rc_compat.h"
#include "rc_error.h"
#include <stdlib.h>
#include <string.h>
#undef DEBUG_BUFFERS
/* --- rc_buffer --- */
void rc_buffer_init(rc_buffer_t* buffer)
{
buffer->chunk.write = buffer->chunk.start = &buffer->data[0];
buffer->chunk.end = &buffer->data[sizeof(buffer->data)];
buffer->chunk.next = NULL;
/* leave buffer->data uninitialized */
}
void rc_buffer_destroy(rc_buffer_t* buffer)
{
rc_buffer_chunk_t* chunk;
#ifdef DEBUG_BUFFERS
int count = 0;
int wasted = 0;
int total = 0;
#endif
/* first chunk is not allocated. skip it. */
chunk = buffer->chunk.next;
/* deallocate any additional buffers */
while (chunk)
{
rc_buffer_chunk_t* next = chunk->next;
#ifdef DEBUG_BUFFERS
total += (int)(chunk->end - chunk->start);
wasted += (int)(chunk->end - chunk->write);
++count;
#endif
free(chunk);
chunk = next;
}
#ifdef DEBUG_BUFFERS
printf("-- %d allocated buffers (%d/%d used, %d wasted, %0.2f%% efficiency)\n", count,
total - wasted, total, wasted, (float)(100.0 - (wasted * 100.0) / total));
#endif
}
uint8_t* rc_buffer_reserve(rc_buffer_t* buffer, size_t amount)
{
rc_buffer_chunk_t* chunk = &buffer->chunk;
size_t remaining;
while (chunk)
{
remaining = chunk->end - chunk->write;
if (remaining >= amount)
return chunk->write;
if (!chunk->next)
{
/* allocate a chunk of memory that is a multiple of 256-bytes. the first 32 bytes will be associated
* to the chunk header, and the remaining will be used for data.
*/
const size_t chunk_header_size = sizeof(rc_buffer_chunk_t);
const size_t alloc_size = (chunk_header_size + amount + 0xFF) & ~0xFF;
chunk->next = (rc_buffer_chunk_t*)malloc(alloc_size);
if (!chunk->next)
break;
chunk->next->start = (uint8_t*)chunk->next + chunk_header_size;
chunk->next->write = chunk->next->start;
chunk->next->end = (uint8_t*)chunk->next + alloc_size;
chunk->next->next = NULL;
}
chunk = chunk->next;
}
return NULL;
}
void rc_buffer_consume(rc_buffer_t* buffer, const uint8_t* start, uint8_t* end)
{
rc_buffer_chunk_t* chunk = &buffer->chunk;
do
{
if (chunk->write == start)
{
size_t offset = (end - chunk->start);
offset = (offset + 7) & ~7;
chunk->write = &chunk->start[offset];
if (chunk->write > chunk->end)
chunk->write = chunk->end;
break;
}
chunk = chunk->next;
} while (chunk);
}
void* rc_buffer_alloc(rc_buffer_t* buffer, size_t amount)
{
uint8_t* ptr = rc_buffer_reserve(buffer, amount);
rc_buffer_consume(buffer, ptr, ptr + amount);
return (void*)ptr;
}
char* rc_buffer_strncpy(rc_buffer_t* buffer, const char* src, size_t len)
{
uint8_t* dst = rc_buffer_reserve(buffer, len + 1);
memcpy(dst, src, len);
dst[len] = '\0';
rc_buffer_consume(buffer, dst, dst + len + 2);
return (char*)dst;
}
char* rc_buffer_strcpy(rc_buffer_t* buffer, const char* src)
{
return rc_buffer_strncpy(buffer, src, strlen(src));
}
/* --- other --- */
void rc_format_md5(char checksum[33], const uint8_t digest[16])
{
snprintf(checksum, 33, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
digest[0], digest[1], digest[2], digest[3], digest[4], digest[5], digest[6], digest[7],
digest[8], digest[9], digest[10], digest[11], digest[12], digest[13], digest[14], digest[15]
);
}
uint32_t rc_djb2(const char* input)
{
uint32_t result = 5381;
char c;
while ((c = *input++) != '\0')
result = ((result << 5) + result) + c; /* result = result * 33 + c */
return result;
}
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";
case RC_API_FAILURE: return "API call failed";
case RC_LOGIN_REQUIRED: return "Login required";
case RC_NO_GAME_LOADED: return "No game loaded";
case RC_HARDCORE_DISABLED: return "Hardcore disabled";
case RC_ABORTED: return "Aborted";
case RC_NO_RESPONSE: return "No response";
case RC_ACCESS_DENIED: return "Access denied";
case RC_INVALID_CREDENTIALS: return "Invalid credentials";
case RC_EXPIRED_TOKEN: return "Expired token";
default: return "Unknown error";
}
}
+11
View File
@@ -0,0 +1,11 @@
#include "rc_version.h"
uint32_t rc_version(void)
{
return RCHEEVOS_VERSION;
}
const char* rc_version_string(void)
{
return RCHEEVOS_VERSION_STRING;
}
+32
View File
@@ -0,0 +1,32 @@
#ifndef RC_VERSION_H
#define RC_VERSION_H
#include "rc_export.h"
#include <stdint.h>
RC_BEGIN_C_DECLS
#define RCHEEVOS_VERSION_MAJOR 11
#define RCHEEVOS_VERSION_MINOR 1
#define RCHEEVOS_VERSION_PATCH 0
#define RCHEEVOS_MAKE_VERSION(major, minor, patch) (major * 1000000 + minor * 1000 + patch)
#define RCHEEVOS_VERSION RCHEEVOS_MAKE_VERSION(RCHEEVOS_VERSION_MAJOR, RCHEEVOS_VERSION_MINOR, RCHEEVOS_VERSION_PATCH)
#define RCHEEVOS_MAKE_STRING(num) #num
#define RCHEEVOS_MAKE_VERSION_STRING(major, minor, patch) RCHEEVOS_MAKE_STRING(major) "." RCHEEVOS_MAKE_STRING(minor) "." RCHEEVOS_MAKE_STRING(patch)
#define RCHEEVOS_MAKE_VERSION_STRING_SHORT(major, minor) RCHEEVOS_MAKE_STRING(major) "." RCHEEVOS_MAKE_STRING(minor)
#if RCHEEVOS_VERSION_PATCH > 0
#define RCHEEVOS_VERSION_STRING RCHEEVOS_MAKE_VERSION_STRING(RCHEEVOS_VERSION_MAJOR, RCHEEVOS_VERSION_MINOR, RCHEEVOS_VERSION_PATCH)
#else
#define RCHEEVOS_VERSION_STRING RCHEEVOS_MAKE_VERSION_STRING_SHORT(RCHEEVOS_VERSION_MAJOR, RCHEEVOS_VERSION_MINOR)
#endif
RC_EXPORT uint32_t rc_version(void);
RC_EXPORT const char* rc_version_string(void);
RC_END_C_DECLS
#endif /* RC_VERSION_H */
+118
View File
@@ -0,0 +1,118 @@
#include "rc_internal.h"
#include <stdlib.h>
#include <string.h>
void* rc_alloc_scratch(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset)
{
void* data;
/* 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 int32_t aligned_offset = (*offset + alignment - 1) & ~(alignment - 1);
*offset += (aligned_offset - *offset);
*offset += size;
}
/* find a scratch buffer to hold the temporary data */
data = rc_buffer_alloc(&scratch->buffer, size);
if (!data) {
*offset = RC_OUT_OF_MEMORY;
return NULL;
}
return data;
}
void* rc_alloc(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t 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 < sizeof(scratch->objs)) {
/* 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) {
int32_t 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, size_t length) {
int32_t 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, (uint32_t)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.strings = NULL;
rc_buffer_init(&parse->scratch.buffer);
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_buffer_destroy(&parse->scratch.buffer);
}
+555
View File
@@ -0,0 +1,555 @@
#include "rc_internal.h"
#include <stdlib.h>
#include <assert.h>
static int rc_test_condition_compare(uint32_t value1, uint32_t value2, uint8_t oper) {
switch (oper) {
case RC_OPERATOR_EQ: return value1 == value2;
case RC_OPERATOR_NE: return value1 != value2;
case RC_OPERATOR_LT: return value1 < value2;
case RC_OPERATOR_LE: return value1 <= value2;
case RC_OPERATOR_GT: return value1 > value2;
case RC_OPERATOR_GE: return value1 >= value2;
default: return 1;
}
}
static char rc_condition_determine_comparator(const rc_condition_t* self) {
switch (self->oper) {
case RC_OPERATOR_EQ:
case RC_OPERATOR_NE:
case RC_OPERATOR_LT:
case RC_OPERATOR_LE:
case RC_OPERATOR_GT:
case RC_OPERATOR_GE:
break;
default:
/* not a comparison. should not be getting compared. but if it is, legacy behavior was to return 1 */
return RC_PROCESSING_COMPARE_ALWAYS_TRUE;
}
if ((self->operand1.type == RC_OPERAND_ADDRESS || self->operand1.type == RC_OPERAND_DELTA) &&
!self->operand1.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand1)) {
/* left side is an integer memory reference */
int needs_translate = (self->operand1.size != self->operand1.value.memref->value.size);
if (self->operand2.type == RC_OPERAND_CONST) {
/* right side is a constant */
if (self->operand1.type == RC_OPERAND_ADDRESS)
return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_CONST;
return needs_translate ? RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED : RC_PROCESSING_COMPARE_DELTA_TO_CONST;
}
else if ((self->operand2.type == RC_OPERAND_ADDRESS || self->operand2.type == RC_OPERAND_DELTA) &&
!self->operand2.value.memref->value.is_indirect && !rc_operand_is_float(&self->operand2)) {
/* right side is an integer memory reference */
const int is_same_memref = (self->operand1.value.memref == self->operand2.value.memref);
needs_translate |= (self->operand2.size != self->operand2.value.memref->value.size);
if (self->operand1.type == RC_OPERAND_ADDRESS) {
if (self->operand2.type == RC_OPERAND_ADDRESS) {
if (is_same_memref && !needs_translate) {
/* comparing a memref to itself, will evaluate to a constant */
return rc_test_condition_compare(0, 0, self->oper) ? RC_PROCESSING_COMPARE_ALWAYS_TRUE : RC_PROCESSING_COMPARE_ALWAYS_FALSE;
}
return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF;
}
assert(self->operand2.type == RC_OPERAND_DELTA);
if (is_same_memref) {
/* delta comparison is optimized to compare with itself (for detecting change) */
return needs_translate ? RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED : RC_PROCESSING_COMPARE_MEMREF_TO_DELTA;
}
}
else {
assert(self->operand1.type == RC_OPERAND_DELTA);
if (self->operand2.type == RC_OPERAND_ADDRESS) {
if (is_same_memref) {
/* delta comparison is optimized to compare with itself (for detecting change) */
return needs_translate ? RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED : RC_PROCESSING_COMPARE_DELTA_TO_MEMREF;
}
}
}
}
}
if (self->operand1.type == RC_OPERAND_CONST && self->operand2.type == RC_OPERAND_CONST) {
/* comparing constants will always generate a constant result */
return rc_test_condition_compare(self->operand1.value.num, self->operand2.value.num, self->oper) ?
RC_PROCESSING_COMPARE_ALWAYS_TRUE : RC_PROCESSING_COMPARE_ALWAYS_FALSE;
}
return RC_PROCESSING_COMPARE_DEFAULT;
}
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, uint8_t 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;
self->optimized_comparator = RC_PROCESSING_COMPARE_DEFAULT;
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;
}
if (parse->buffer != 0)
self->optimized_comparator = rc_condition_determine_comparator(self);
*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;
}
}
static int rc_test_condition_compare_memref_to_const(rc_condition_t* self) {
const uint32_t value1 = self->operand1.value.memref->value.value;
const uint32_t value2 = self->operand2.value.num;
assert(self->operand1.size == self->operand1.value.memref->value.size);
return rc_test_condition_compare(value1, value2, self->oper);
}
static int rc_test_condition_compare_delta_to_const(rc_condition_t* self) {
const rc_memref_value_t* memref1 = &self->operand1.value.memref->value;
const uint32_t value1 = (memref1->changed) ? memref1->prior : memref1->value;
const uint32_t value2 = self->operand2.value.num;
assert(self->operand1.size == self->operand1.value.memref->value.size);
return rc_test_condition_compare(value1, value2, self->oper);
}
static int rc_test_condition_compare_memref_to_memref(rc_condition_t* self) {
const uint32_t value1 = self->operand1.value.memref->value.value;
const uint32_t value2 = self->operand2.value.memref->value.value;
assert(self->operand1.size == self->operand1.value.memref->value.size);
assert(self->operand2.size == self->operand2.value.memref->value.size);
return rc_test_condition_compare(value1, value2, self->oper);
}
static int rc_test_condition_compare_memref_to_delta(rc_condition_t* self) {
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
assert(self->operand1.value.memref == self->operand2.value.memref);
assert(self->operand1.size == self->operand1.value.memref->value.size);
assert(self->operand2.size == self->operand2.value.memref->value.size);
if (memref->changed)
return rc_test_condition_compare(memref->value, memref->prior, self->oper);
switch (self->oper) {
case RC_OPERATOR_EQ:
case RC_OPERATOR_GE:
case RC_OPERATOR_LE:
return 1;
default:
return 0;
}
}
static int rc_test_condition_compare_delta_to_memref(rc_condition_t* self) {
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
assert(self->operand1.value.memref == self->operand2.value.memref);
assert(self->operand1.size == self->operand1.value.memref->value.size);
assert(self->operand2.size == self->operand2.value.memref->value.size);
if (memref->changed)
return rc_test_condition_compare(memref->prior, memref->value, self->oper);
switch (self->oper) {
case RC_OPERATOR_EQ:
case RC_OPERATOR_GE:
case RC_OPERATOR_LE:
return 1;
default:
return 0;
}
}
static int rc_test_condition_compare_memref_to_const_transformed(rc_condition_t* self) {
rc_typed_value_t value1;
const uint32_t value2 = self->operand2.value.num;
value1.type = RC_VALUE_TYPE_UNSIGNED;
value1.value.u32 = self->operand1.value.memref->value.value;
rc_transform_memref_value(&value1, self->operand1.size);
return rc_test_condition_compare(value1.value.u32, value2, self->oper);
}
static int rc_test_condition_compare_delta_to_const_transformed(rc_condition_t* self) {
rc_typed_value_t value1;
const rc_memref_value_t* memref1 = &self->operand1.value.memref->value;
const uint32_t value2 = self->operand2.value.num;
value1.type = RC_VALUE_TYPE_UNSIGNED;
value1.value.u32 = (memref1->changed) ? memref1->prior : memref1->value;
rc_transform_memref_value(&value1, self->operand1.size);
return rc_test_condition_compare(value1.value.u32, value2, self->oper);
}
static int rc_test_condition_compare_memref_to_memref_transformed(rc_condition_t* self) {
rc_typed_value_t value1, value2;
value1.type = RC_VALUE_TYPE_UNSIGNED;
value1.value.u32 = self->operand1.value.memref->value.value;
rc_transform_memref_value(&value1, self->operand1.size);
value2.type = RC_VALUE_TYPE_UNSIGNED;
value2.value.u32 = self->operand2.value.memref->value.value;
rc_transform_memref_value(&value2, self->operand2.size);
return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper);
}
static int rc_test_condition_compare_memref_to_delta_transformed(rc_condition_t* self) {
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
assert(self->operand1.value.memref == self->operand2.value.memref);
if (memref->changed) {
rc_typed_value_t value1, value2;
value1.type = RC_VALUE_TYPE_UNSIGNED;
value1.value.u32 = memref->value;
rc_transform_memref_value(&value1, self->operand1.size);
value2.type = RC_VALUE_TYPE_UNSIGNED;
value2.value.u32 = memref->prior;
rc_transform_memref_value(&value2, self->operand2.size);
return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper);
}
switch (self->oper) {
case RC_OPERATOR_EQ:
case RC_OPERATOR_GE:
case RC_OPERATOR_LE:
return 1;
default:
return 0;
}
}
static int rc_test_condition_compare_delta_to_memref_transformed(rc_condition_t* self) {
const rc_memref_value_t* memref = &self->operand1.value.memref->value;
assert(self->operand1.value.memref == self->operand2.value.memref);
if (memref->changed) {
rc_typed_value_t value1, value2;
value1.type = RC_VALUE_TYPE_UNSIGNED;
value1.value.u32 = memref->prior;
rc_transform_memref_value(&value1, self->operand1.size);
value2.type = RC_VALUE_TYPE_UNSIGNED;
value2.value.u32 = memref->value;
rc_transform_memref_value(&value2, self->operand2.size);
return rc_test_condition_compare(value1.value.u32, value2.value.u32, self->oper);
}
switch (self->oper) {
case RC_OPERATOR_EQ:
case RC_OPERATOR_GE:
case RC_OPERATOR_LE:
return 1;
default:
return 0;
}
}
int rc_test_condition(rc_condition_t* self, rc_eval_state_t* eval_state) {
rc_typed_value_t value1, value2;
if (eval_state->add_value.type != RC_VALUE_TYPE_NONE) {
/* if there's an accumulator, we can't use the optimized comparators */
rc_evaluate_operand(&value1, &self->operand1, eval_state);
rc_typed_value_add(&value1, &eval_state->add_value);
} else {
/* use an optimized comparator whenever possible */
switch (self->optimized_comparator) {
case RC_PROCESSING_COMPARE_MEMREF_TO_CONST:
return rc_test_condition_compare_memref_to_const(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA:
return rc_test_condition_compare_memref_to_delta(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF:
return rc_test_condition_compare_memref_to_memref(self);
case RC_PROCESSING_COMPARE_DELTA_TO_CONST:
return rc_test_condition_compare_delta_to_const(self);
case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF:
return rc_test_condition_compare_delta_to_memref(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED:
return rc_test_condition_compare_memref_to_const_transformed(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED:
return rc_test_condition_compare_memref_to_delta_transformed(self);
case RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED:
return rc_test_condition_compare_memref_to_memref_transformed(self);
case RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED:
return rc_test_condition_compare_delta_to_const_transformed(self);
case RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED:
return rc_test_condition_compare_delta_to_memref_transformed(self);
case RC_PROCESSING_COMPARE_ALWAYS_TRUE:
return 1;
case RC_PROCESSING_COMPARE_ALWAYS_FALSE:
return 0;
default:
rc_evaluate_operand(&value1, &self->operand1, eval_state);
break;
}
}
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;
}
}
+437
View File
@@ -0,0 +1,437 @@
#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;
uint32_t 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;
uint32_t 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_negate(&value);
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
+280
View File
@@ -0,0 +1,280 @@
#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');
}
if (!strncmp(format_str, "IXED", 4) && format_str[4] >= '1' && format_str[4] <= '3' && format_str[5] == '\0') {
return RC_FORMAT_FIXED1 + (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;
}
if (!strcmp(format_str, "HOUSANDS")) {
return RC_FORMAT_THOUSANDS;
}
if (!strcmp(format_str, "ENS")) {
return RC_FORMAT_TENS;
}
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 'U':
if (!strcmp(format_str, "NSIGNED")) {
return RC_FORMAT_UNSIGNED_VALUE;
}
break;
case 'O':
if (!strcmp(format_str, "THER")) {
return RC_FORMAT_SCORE;
}
break;
case 'H':
if (!strcmp(format_str, "UNDREDS")) {
return RC_FORMAT_HUNDREDS;
}
break;
}
return RC_FORMAT_VALUE;
}
static int rc_format_value_minutes(char* buffer, size_t size, uint32_t minutes) {
uint32_t hours;
hours = minutes / 60;
minutes -= hours * 60;
return snprintf(buffer, size, "%uh%02u", hours, minutes);
}
static int rc_format_value_seconds(char* buffer, size_t size, uint32_t seconds) {
uint32_t 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, size_t size, uint32_t centiseconds) {
uint32_t 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;
}
static int rc_format_value_fixed(char* buffer, size_t size, const char* format, int32_t value, int32_t factor)
{
if (value >= 0)
return snprintf(buffer, size, format, value / factor, value % factor);
return snprintf(buffer, size, format, value / factor, (-value) % factor);
}
static int rc_format_value_padded(char* buffer, size_t size, const char* format, int32_t value)
{
if (value == 0)
return snprintf(buffer, size, "0");
return snprintf(buffer, size, format, value);
}
int rc_format_typed_value(char* buffer, size_t 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;
case RC_FORMAT_FIXED1:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
chars = rc_format_value_fixed(buffer, size, "%d.%u", converted_value.value.i32, 10);
break;
case RC_FORMAT_FIXED2:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
chars = rc_format_value_fixed(buffer, size, "%d.%02u", converted_value.value.i32, 100);
break;
case RC_FORMAT_FIXED3:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
chars = rc_format_value_fixed(buffer, size, "%d.%03u", converted_value.value.i32, 1000);
break;
case RC_FORMAT_TENS:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
chars = rc_format_value_padded(buffer, size, "%d0", converted_value.value.i32);
break;
case RC_FORMAT_HUNDREDS:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
chars = rc_format_value_padded(buffer, size, "%d00", converted_value.value.i32);
break;
case RC_FORMAT_THOUSANDS:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_SIGNED);
chars = rc_format_value_padded(buffer, size, "%d000", converted_value.value.i32);
break;
case RC_FORMAT_UNSIGNED_VALUE:
rc_typed_value_convert(&converted_value, RC_VALUE_TYPE_UNSIGNED);
chars = snprintf(buffer, size, "%u", converted_value.value.u32);
break;
}
return chars;
}
int rc_format_value(char* buffer, int size, int32_t 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);
}
+278
View File
@@ -0,0 +1,278 @@
#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, int32_t* 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) {
if (!self)
return;
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);
}
+494
View File
@@ -0,0 +1,494 @@
#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, uint32_t address, uint8_t size, uint8_t 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, uint8_t* size, uint32_t* 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 'b': case 'B': *size = RC_MEMSIZE_FLOAT_BE; 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 = (uint32_t)value;
*memaddr = end;
return RC_OK;
}
static float rc_build_float(uint32_t mantissa_bits, int32_t exponent, int sign) {
/* 32-bit float has a 23-bit mantissa and 8-bit exponent */
const uint32_t implied_bit = 1 << 23;
const uint32_t 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 uint32_t mantissa = (value->value.u32 & 0x7FFFFF);
const int32_t exponent = (int32_t)((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_float_be(rc_typed_value_t* value) {
/* decodes an IEEE 754 float in big endian format */
const uint32_t mantissa = ((value->value.u32 & 0xFF000000) >> 24) |
((value->value.u32 & 0x00FF0000) >> 8) |
((value->value.u32 & 0x00007F00) << 8);
const int32_t exponent = (int32_t)(((value->value.u32 & 0x0000007F) << 1) |
((value->value.u32 & 0x00008000) >> 15)) - 127;
const int sign = (value->value.u32 & 0x00000080);
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 uint32_t mantissa = ((value->value.u32 & 0xFF000000) >> 24) |
((value->value.u32 & 0x00FF0000) >> 8) |
((value->value.u32 & 0x00007F00) << 8);
const int32_t exponent = (int32_t)(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 uint32_t mantissa = value->value.u32 & 0x007FFFFF;
const int32_t exponent = (int32_t)(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 uint8_t 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, uint8_t 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_FLOAT_BE:
rc_transform_memref_float_be(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 uint32_t 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_FLOAT_BE */
0xffffffff /* RC_MEMSIZE_VARIABLE */
};
uint32_t rc_memref_mask(uint8_t 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 uint8_t 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_FLOAT_BE */
RC_MEMSIZE_32_BITS /* RC_MEMSIZE_VARIABLE */
};
uint8_t rc_memref_shared_size(uint8_t 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];
}
uint32_t rc_peek_value(uint32_t address, uint8_t 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:
{
uint32_t 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, uint32_t 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 uint32_t rc_get_memref_value_value(const 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;
}
}
uint32_t 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 uint32_t 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);
}
+475
View File
@@ -0,0 +1,475 @@
#include "rc_internal.h"
#include <stdlib.h>
#include <ctype.h>
#include <math.h>
#ifndef RC_DISABLE_LUA
RC_BEGIN_C_DECLS
#include <lua.h>
#include <lauxlib.h>
RC_END_C_DECLS
#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);
}
#else
(void)parse;
#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, uint8_t is_indirect) {
const char* aux = *memaddr;
uint32_t address;
uint8_t 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, 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, uint8_t 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 */
/* fallthrough */ /* to default */
default:
ret = rc_parse_operand_memory(self, &aux, parse, is_indirect);
if (ret < 0)
return ret;
break;
}
/* fallthrough */ /* to case '1' 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) {
uint32_t address = (uint32_t)luaL_checkinteger(L, 1);
uint32_t num_bytes = (uint32_t)luaL_checkinteger(L, 2);
rc_luapeek_t* luapeek = (rc_luapeek_t*)lua_touserdata(L, 3);
uint32_t 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_FLOAT_BE:
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);
}
uint32_t rc_transform_operand_value(uint32_t 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 = (uint32_t)lua_toboolean(eval_state->L, -1);
}
else {
result->value.u32 = (uint32_t)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);
}
+197
View File
@@ -0,0 +1,197 @@
#ifndef RC_INTERNAL_H
#define RC_INTERNAL_H
#include "rc_runtime_types.h"
#include "rc_util.h"
RC_BEGIN_C_DECLS
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)))
/* force alignment to 4 bytes on 32-bit systems, or 8 bytes on 64-bit systems */
#define RC_ALIGN(n) (((n) + (sizeof(void*)-1)) & ~(sizeof(void*)-1))
typedef struct {
rc_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 {
uint32_t u32;
int32_t i32;
float f32;
} value;
char type;
}
rc_typed_value_t;
#define RC_MEASURED_UNKNOWN 0xFFFFFFFF
typedef struct {
rc_typed_value_t add_value;/* AddSource/SubSource */
int32_t add_hits; /* AddHits */
uint32_t add_address; /* AddAddress */
rc_peek_t peek;
void* peek_userdata;
lua_State* L;
rc_typed_value_t measured_value; /* Measured */
uint8_t was_reset; /* ResetIf triggered */
uint8_t has_hits; /* one of more hit counts is non-zero */
uint8_t primed; /* true if all non-Trigger conditions are true */
uint8_t measured_from_hits; /* true if the measured_value came from a condition's hit count */
uint8_t was_cond_reset; /* ResetNextIf triggered */
}
rc_eval_state_t;
typedef struct {
int32_t offset;
lua_State* L;
int funcs_ndx;
void* buffer;
rc_scratch_t scratch;
rc_memref_t** first_memref;
rc_value_t** variables;
uint32_t measured_target;
int lines_read;
uint8_t has_required_hits;
uint8_t 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, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset);
void* rc_alloc_scratch(void* pointer, int32_t* offset, uint32_t size, uint32_t alignment, rc_scratch_t* scratch, uint32_t scratch_object_pointer_offset);
char* rc_alloc_str(rc_parse_state_t* parse, const char* text, size_t length);
rc_memref_t* rc_alloc_memref(rc_parse_state_t* parse, uint32_t address, uint8_t size, uint8_t is_indirect);
int rc_parse_memref(const char** memaddr, uint8_t* size, uint32_t* 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, uint32_t value);
uint32_t rc_get_memref_value(rc_memref_t* memref, int operand_type, rc_eval_state_t* eval_state);
uint8_t rc_memref_shared_size(uint8_t size);
uint32_t rc_memref_mask(uint8_t size);
void rc_transform_memref_value(rc_typed_value_t* value, uint8_t size);
uint32_t rc_peek_value(uint32_t address, uint8_t size, rc_peek_t peek, void* ud);
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);
enum {
RC_PROCESSING_COMPARE_DEFAULT = 0,
RC_PROCESSING_COMPARE_MEMREF_TO_CONST,
RC_PROCESSING_COMPARE_MEMREF_TO_DELTA,
RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF,
RC_PROCESSING_COMPARE_DELTA_TO_MEMREF,
RC_PROCESSING_COMPARE_DELTA_TO_CONST,
RC_PROCESSING_COMPARE_MEMREF_TO_CONST_TRANSFORMED,
RC_PROCESSING_COMPARE_MEMREF_TO_DELTA_TRANSFORMED,
RC_PROCESSING_COMPARE_MEMREF_TO_MEMREF_TRANSFORMED,
RC_PROCESSING_COMPARE_DELTA_TO_MEMREF_TRANSFORMED,
RC_PROCESSING_COMPARE_DELTA_TO_CONST_TRANSFORMED,
RC_PROCESSING_COMPARE_ALWAYS_TRUE,
RC_PROCESSING_COMPARE_ALWAYS_FALSE
};
rc_condition_t* rc_parse_condition(const char** memaddr, rc_parse_state_t* parse, uint8_t 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, uint8_t 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);
int rc_value_from_hits(rc_value_t* self);
rc_value_t* rc_alloc_helper_variable(const char* memaddr, size_t 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);
void rc_typed_value_negate(rc_typed_value_t* value);
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, size_t 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);
RC_END_C_DECLS
#endif /* RC_INTERNAL_H */
+854
View File
@@ -0,0 +1,854 @@
#include "rc_validate.h"
#include "rc_consoles.h"
#include "rc_internal.h"
#include "../rc_compat.h"
#include <stddef.h>
#include <stdlib.h>
static int rc_validate_memref(const rc_memref_t* memref, char result[], const size_t result_size, uint32_t console_id, uint32_t max_address)
{
if (memref->address > max_address) {
snprintf(result, result_size, "Address %04X out of range (max %04X)", memref->address, max_address);
return 0;
}
switch (console_id) {
case RC_CONSOLE_NINTENDO:
if (memref->address >= 0x0800 && memref->address <= 0x1FFF) {
snprintf(result, result_size, "Mirror RAM may not be exposed by emulator (address %04X)", memref->address);
return 0;
}
break;
case RC_CONSOLE_GAMEBOY:
case RC_CONSOLE_GAMEBOY_COLOR:
if (memref->address >= 0xE000 && memref->address <= 0xFDFF) {
snprintf(result, result_size, "Echo RAM may not be exposed by emulator (address %04X)", memref->address);
return 0;
}
break;
case RC_CONSOLE_PLAYSTATION:
if (memref->address <= 0xFFFF) {
snprintf(result, result_size, "Kernel RAM may not be initialized without real BIOS (address %04X)", memref->address);
return 0;
}
break;
}
return 1;
}
int rc_validate_memrefs(const rc_memref_t* memref, char result[], const size_t result_size, uint32_t max_address)
{
while (memref) {
if (!rc_validate_memref(memref, result, result_size, 0, max_address))
return 0;
memref = memref->next;
}
return 1;
}
static uint32_t rc_console_max_address(uint32_t console_id)
{
const rc_memory_regions_t* memory_regions;
memory_regions = rc_console_memory_regions(console_id);
if (memory_regions && memory_regions->num_regions > 0)
return memory_regions->region[memory_regions->num_regions - 1].end_address;
return 0xFFFFFFFF;
}
int rc_validate_memrefs_for_console(const rc_memref_t* memref, char result[], const size_t result_size, uint32_t console_id)
{
const uint32_t max_address = rc_console_max_address(console_id);
while (memref) {
if (!rc_validate_memref(memref, result, result_size, console_id, max_address))
return 0;
memref = memref->next;
}
return 1;
}
static uint32_t 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 uint32_t rc_scale_value(uint32_t value, uint8_t 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 (uint32_t)scaled;
}
case RC_OPERATOR_DIV:
{
const uint32_t 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(uint32_t min_val, uint32_t max_val, char oper, uint32_t 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_internal(const rc_condset_t* condset, char result[], const size_t result_size, uint32_t console_id, uint32_t max_address)
{
const rc_condition_t* cond;
char buffer[128];
uint32_t 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) {
uint32_t 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 && !rc_validate_memref(cond->operand1.value.memref, buffer, sizeof(buffer), console_id, max_address)) {
snprintf(result, result_size, "Condition %d: %s", index, buffer);
return 0;
}
if (is_memref2 && !rc_validate_memref(cond->operand2.value.memref, buffer, sizeof(buffer), console_id, max_address)) {
snprintf(result, result_size, "Condition %d: %s", index, buffer);
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;
case RC_CONDITION_RESET_IF:
if (cond->required_hits == 1) {
snprintf(result, result_size, "Condition %d: Hit target of 1 is redundant on ResetIf", index);
return 0;
}
/* fallthrough */ /* to default */
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);
uint32_t 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_condset(const rc_condset_t* condset, char result[], const size_t result_size, uint32_t max_address)
{
return rc_validate_condset_internal(condset, result, result_size, 0, max_address);
}
int rc_validate_condset_for_console(const rc_condset_t* condset, char result[], const size_t result_size, uint32_t console_id)
{
const uint32_t max_address = rc_console_max_address(console_id);
return rc_validate_condset_internal(condset, result, result_size, console_id, max_address);
}
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, uint32_t value1, int comparison2, uint32_t 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;
uint32_t 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 (prefix != compare_prefix && strcmp(compare_prefix, "Core") == 0)
{
/* if the alt condition is more restrictive than the core condition, ignore it */
if (rc_validate_comparison_overlap(comparison2, value2, comparison1, value1) != RC_OVERLAP_REDUNDANT)
continue;
}
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)
{
/* ignore MeasuredIf redundancies between groups */
if (conditions != compare_conditions)
continue;
if (compare_condition->type == RC_CONDITION_MEASURED_IF && condition->type != RC_CONDITION_MEASURED_IF)
{
/* only ever report the redundancy on the non-MeasuredIf condition. The MeasuredIf provides
* additional functionality. */
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;
}
static int rc_validate_trigger_internal(const rc_trigger_t* trigger, char result[], const size_t result_size, uint32_t console_id, uint32_t max_address)
{
const rc_condset_t* alt;
int index;
if (!trigger->alternative)
{
if (!rc_validate_condset_internal(trigger->requirement, result, result_size, console_id, 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_internal(trigger->requirement, result + 5, result_size - 5, console_id, 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_internal(alt, result + prefix_length, result_size - prefix_length, console_id, 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;
}
int rc_validate_trigger(const rc_trigger_t* trigger, char result[], const size_t result_size, uint32_t max_address)
{
return rc_validate_trigger_internal(trigger, result, result_size, 0, max_address);
}
int rc_validate_trigger_for_console(const rc_trigger_t* trigger, char result[], const size_t result_size, uint32_t console_id)
{
const uint32_t max_address = rc_console_max_address(console_id);
return rc_validate_trigger_internal(trigger, result, result_size, console_id, max_address);
}
+22
View File
@@ -0,0 +1,22 @@
#ifndef RC_VALIDATE_H
#define RC_VALIDATE_H
#include "rc_runtime_types.h"
#include <stddef.h>
RC_BEGIN_C_DECLS
int rc_validate_memrefs(const rc_memref_t* memref, char result[], const size_t result_size, uint32_t max_address);
int rc_validate_condset(const rc_condset_t* condset, char result[], const size_t result_size, uint32_t max_address);
int rc_validate_trigger(const rc_trigger_t* trigger, char result[], const size_t result_size, uint32_t max_address);
int rc_validate_memrefs_for_console(const rc_memref_t* memref, char result[], const size_t result_size, uint32_t console_id);
int rc_validate_condset_for_console(const rc_condset_t* condset, char result[], const size_t result_size, uint32_t console_id);
int rc_validate_trigger_for_console(const rc_trigger_t* trigger, char result[], const size_t result_size, uint32_t console_id);
RC_END_C_DECLS
#endif /* RC_VALIDATE_H */
+829
View File
@@ -0,0 +1,829 @@
#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;
uint32_t address;
uint8_t 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;
uint8_t 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},
{"Fixed1", 6, RC_FORMAT_FIXED1},
{"Fixed2", 6, RC_FORMAT_FIXED2},
{"Fixed3", 6, RC_FORMAT_FIXED3},
{"Unsigned", 8, RC_FORMAT_UNSIGNED_VALUE}
};
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;
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 in scratch memory */
size = count * sizeof(rc_richpresence_lookup_item_t*);
items = (rc_richpresence_lookup_item_t**)rc_buffer_alloc(&parse->scratch.buffer, size);
/* if allocation fails, we can still use the unbalanced tree, so just bail out */
if (items == NULL)
return;
/* 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);
}
static void rc_insert_richpresence_lookup_item(rc_richpresence_lookup_t* lookup,
uint32_t first, uint32_t last, const char* label, size_t 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;
uint32_t 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 = (uint8_t)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, size_t 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, size_t 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, size_t 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);
}
+892
View File
@@ -0,0 +1,892 @@
#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
rc_runtime_t* rc_runtime_alloc(void) {
rc_runtime_t* self = malloc(sizeof(rc_runtime_t));
if (self) {
rc_runtime_init(self);
self->owns_self = 1;
}
return self;
}
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) {
uint32_t 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;
if (self->owns_self) {
free(self);
}
}
void rc_runtime_checksum(const char* memaddr, uint8_t* 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, uint32_t 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, uint32_t id) {
uint32_t 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, uint32_t 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;
uint8_t md5[16];
int32_t size;
uint32_t 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, uint32_t id)
{
uint32_t 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, uint32_t 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 == RC_MEASURED_UNKNOWN) ? 0 : 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, uint32_t id, char* buffer, size_t buffer_size)
{
const rc_trigger_t* trigger = rc_runtime_get_achievement(runtime, id);
uint32_t 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 == RC_MEASURED_UNKNOWN) ? 0 : trigger->measured_value;
if (value > trigger->measured_target)
value = trigger->measured_target;
if (trigger->measured_as_percent) {
const uint32_t percent = (uint32_t)(((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, uint32_t 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, uint32_t id) {
uint32_t 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, uint32_t id, const char* memaddr, lua_State* L, int funcs_idx) {
void* lboard_buffer;
uint8_t md5[16];
rc_lboard_t* lboard;
rc_parse_state_t parse;
rc_runtime_lboard_t* runtime_lboard;
int size;
uint32_t 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, uint32_t id)
{
uint32_t 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, int32_t 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;
uint8_t 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, size_t 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;
uint32_t old_measured_value;
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_measured_value = trigger->measured_value;
old_state = trigger->state;
new_state = rc_evaluate_trigger(trigger, peek, ud, L);
/* 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 measured value changed and the achievement hasn't triggered, send a notification */
if (trigger->measured_value != old_measured_value && old_measured_value != RC_MEASURED_UNKNOWN &&
trigger->measured_target != 0 && trigger->measured_value <= trigger->measured_target &&
new_state != RC_TRIGGER_STATE_TRIGGERED &&
new_state != RC_TRIGGER_STATE_INACTIVE && new_state != RC_TRIGGER_STATE_WAITING) {
runtime_event.type = RC_RUNTIME_EVENT_ACHIEVEMENT_PROGRESS_UPDATED;
runtime_event.id = self->triggers[i].id;
if (trigger->measured_as_percent) {
/* if reporting measured value as a percentage, only send the notification if the percentage changes */
const int32_t old_percent = (int32_t)(((unsigned long long)old_measured_value * 100) / trigger->measured_target);
const int32_t new_percent = (int32_t)(((unsigned long long)trigger->measured_value * 100) / trigger->measured_target);
if (old_percent != new_percent) {
runtime_event.value = new_percent;
event_handler(&runtime_event);
}
}
else {
runtime_event.value = trigger->measured_value;
event_handler(&runtime_event);
}
runtime_event.value = 0; /* achievement loop expects this to stay at 0 */
}
/* 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;
uint32_t 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_reset_richpresence(self->richpresence->richpresence);
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;
}
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;
}
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) {
uint32_t 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, uint32_t 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);
}
}
}
}
+881
View File
@@ -0,0 +1,881 @@
#include "rc_runtime.h"
#include "rc_internal.h"
#include "rc_util.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 {
const rc_runtime_t* runtime;
uint32_t offset;
uint8_t* buffer;
uint32_t 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, uint32_t 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 uint32_t rc_runtime_progress_read_uint(rc_runtime_progress_t* progress)
{
uint32_t 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, uint8_t* 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, uint8_t* md5)
{
int result = 0;
if (progress->buffer)
result = (memcmp(&progress->buffer[progress->offset], md5, 16) == 0);
progress->offset += 16;
return result;
}
static void rc_runtime_progress_start_chunk(rc_runtime_progress_t* progress, uint32_t 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)
{
uint32_t length;
uint32_t offset;
progress->offset = (progress->offset + 3) & ~0x03; /* align to 4 byte boundary */
if (progress->buffer) {
/* ignore chunk size field when calculating chunk size */
length = (uint32_t)(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, const 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;
uint32_t 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)
{
uint32_t entries;
uint32_t address, flags, value, prior;
uint8_t 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;
uint32_t 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;
uint32_t 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 uint32_t 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)
{
uint32_t 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)
{
uint32_t 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)
{
uint32_t djb2 = rc_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)
{
uint32_t 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;
uint32_t djb2;
};
struct rc_pending_value_t local_pending_variables[32];
struct rc_pending_value_t* pending_variables;
rc_value_t* variable;
uint32_t count, serialized_count;
int result;
uint32_t 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_djb2(variable->name);
++count;
}
result = RC_OK;
for (; serialized_count > 0 && result == RC_OK; --serialized_count) {
uint32_t 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)
{
uint32_t 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)
{
uint32_t id = rc_runtime_progress_read_uint(progress);
uint32_t 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)
{
uint32_t i;
uint32_t 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)
{
uint32_t id = rc_runtime_progress_read_uint(progress);
uint32_t 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)) {
uint32_t 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;
uint8_t 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, 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;
if (!buffer)
return RC_INVALID_STATE;
rc_runtime_progress_init(&progress, runtime, L);
progress.buffer = (uint8_t*)buffer;
return rc_runtime_progress_serialize_internal(&progress);
}
int rc_runtime_deserialize_progress(rc_runtime_t* runtime, const uint8_t* serialized, lua_State* L)
{
rc_runtime_progress_t progress;
md5_state_t state;
uint8_t md5[16];
uint32_t chunk_id;
uint32_t chunk_size;
uint32_t next_chunk_offset;
uint32_t i;
int seen_rich_presence = 0;
int result = RC_OK;
if (!serialized) {
rc_runtime_reset(runtime);
return RC_INVALID_STATE;
}
rc_runtime_progress_init(&progress, runtime, L);
progress.buffer = (uint8_t*)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;
}
+294
View File
@@ -0,0 +1,294 @@
#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_target = parse->measured_target;
self->measured_value = parse->measured_target ? RC_MEASURED_UNKNOWN : 0;
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, uint32_t 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 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) {
/* if the state is WAITING and the trigger is ready to fire, ignore it and reset the hit counts */
if (self->state == RC_TRIGGER_STATE_WAITING) {
rc_reset_trigger(self);
self->has_hits = 0;
return RC_TRIGGER_STATE_WAITING;
}
/* 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) {
if (!self)
return;
rc_reset_trigger_hitcounts(self);
self->state = RC_TRIGGER_STATE_WAITING;
if (self->measured_target)
self->measured_value = RC_MEASURED_UNKNOWN;
self->has_hits = 0;
}

Some files were not shown because too many files have changed in this diff Show More