Coder Social home page Coder Social logo

retroachievements / raintegration Goto Github PK

View Code? Open in Web Editor NEW
92.0 92.0 22.0 7.51 MB

The DLL responsible to integrate emulators with RetroAchievements.org

Home Page: https://retroachievements.org

License: MIT License

C++ 99.38% C 0.47% Batchfile 0.13% Python 0.02%
cpp retroachievements retroachievements-api retrogaming

raintegration's People

Contributors

casualpokeplayer avatar gamedragon2k avatar gdeoo avatar immensegames avatar jamiras avatar loopyd avatar meleu avatar pgrimaud avatar redwizard42 avatar richman1c avatar riku55 avatar rzumer avatar scottfromderby avatar shoegazessb avatar syrianballas avatar televandalist avatar vancleeff avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

raintegration's Issues

make leaderboard cancel on reset

From @meleu on April 1, 2018 13:46

Currently Leaderboards don't cancel on Reset.

A cheater can, for example, start a "hi-score on hard difficulty" leaderboard, reset the game, change the difficulty to easy and play.

I consider this a bug.

Copied from original issue: RetroAchievements/RASuite#71

Restructuring of Active Achievements Groups, New Achievement Groups (WIP, Garbage, Demoted...)

From @kdecks on April 8, 2018 3:22

This issues is closely related to RAWeb #105 (Merging ~ Bonus ~ Entries into same game.

@GameDragon2k has been making plans to add some new groups. I'm adding this another other issues for both of us based on some recent discussions.

I'm not sure if this needs to be in RASuite or RAWeb, it seems that it would require some worth from both sides.

The plan is to add new Active Achievement Groups
image
to reflect the needs of the community and to disentangle the various uses of unofficial.

  1. WIP (Work-In-Progress)
    • This is a hybrid of local and online and would be the new unofficial.
    • The purpose is to create a more active shared workspace for collaborative work.
    • Achievements would be able to be pushed to Core from here (and to any other group).
    • New achievements can be created here without starting in local. Once saved they are saved to the network in the same way that code notes are.
    • Achievements here would be would be displayed from the game page WIP progress flag (In RAWeb #57). This would help players see the progress of a set that's being worked on.
  2. Garbage
    • Achievements that are in every Group except local when deleted will be moved here.
  3. Demoted
    • When an achievement is demoted it is moved here. (This should be what unofficial
  4. Bonus
  5. Multi
    • This group would be isolated, and treated like it's own set? Perhaps this one could be left out and into it's own set. I'm uncertain at the time of this writing.

Other

  1. Change achievements points such that WIP, Garbage and Demoted cannot be earned by the player, in terms of the site, but can still be earned during the local sessions for testing.
  2. Add a delete achievement to every group except local and Garbage.
  3. Instead of: Local ⇒ Promote to Unofficial ⇒ Promote to Core, Achievements should be able to be moved to any Achievement Group, and still duplicated to local.
    • Including demotions inside the emulator

Copied from original issue: RetroAchievements/RASuite#74

GUI Simplification Project and other refactorings

Putting this in here as a heads up, stuff will be done one at a time. Functionality/feature-wise nothing is intended to be different, but the code will be vastly transformed when this is done. If you have any questions or advice please come forward.

GUI Simplification Project

Libraries

N.B.: The emulators will not depend on any of this stuff

  • RapidJSON v1.1.0
    • What to expect
      • std::i/ofstream will be used instead of FILE*
      • rapidjson::I/OStreamWrapper instead instead of rapidjson::FileStream.
      • Many files will change

Helpers and Improvements

  • Safer string conversion, and other helper conversion functions
    • What to expect:
      • Many of these functions will templates, to be used with any valid type
        • There will be instant/compile time checks to prevent misuse, if it wasn't a valid type you will get an intellisense error.
      • Conversion will be done through a stream to ensure every character is converted correctly
      • Implementation of a few concepts, i.e., EqualityComparable, LessThanComparable, Comparable, also to check if something is a character or a string. (these can be used in a static_assert)
      • A very easy signed/unsigned conversion for any valid type, etc
      • An enum to underlying_type conversion function (needed if using enum class)
  • Colors as named enums
    • This is prevent having to memorize the RGB code
  • Enum to enum classes (currently working on this)
  • Make enum classes comparable
  • Replace all constant global C arrays with std::array
  • Replace macros with constexpr variables and/or function templates where possible

Most of what's below in Memory Management is interrelated, meaning some of a task may have been completed by another. The purpose of separating them into separate tasks to ensure all of what needs to be done will be done completely in an efficient manner.

Memory Management

What's down here will follow the RAII paradigm (resource acquisition is initialization/pointers own resources)

  • Replace COM interfaces with ATL smart pointers
    • Example: instead of IFileDialog* you'll see CComPtr<IFileDialog>, prevents leaks
  • Replace raw pointers with STL objects where appropriate
    • What to expect (just some examples):
      • Non-constexpr primitive pointers will replaced such as...
        • FILE* with i/ofstream (may have been partially done in another task)
        • String pointers with std::basic_string (may have been done partially)
          - std::basic_string::data returns a mutable C string in C++17
  • Make raw pointers owned at all times; more specificly ra objects
  • Make custom smart pointers for windows/GDI handles
    - Handles in the Windows API (not COM) can't be owned as-is with the default implementation of std::unique_ptr, it would take too long to explain it but if you need to know ask away.

Generic Interfaces

Basically these two will work with any Window (if top-level) or dialog (RA Dialogs aren't top-level). The window class is the initial registration point for window/dialog/control/etc.

  • Base dialog
  • Base window class

Everything above is preparation to do what's below. I'm fairly confident that there will not be any need for other people to test the stuff above but it wouldn't hurt. Will mostly likely require help testing changes to the modeless dialogs however.

Modal Dialogs

  • Login
  • Checksum
  • Game title
  • Achievement reporter

Modeless Dialogs

  • RichPresence dialog
  • Game library dialog
  • Achievement
  • Achievement editor
  • MemoryViewControl
  • Memory Dialog
  • Achievement set dialog
  • Memory bookmark dialog

Controls

  • Custom controls will be in their own classes or subclassed from a dialog (one example is ProcessCustomDraw in the Achievement Dialog)

Search memory for string data

From @Jamiras on March 21, 2018 15:12

As a developer, I'd like to be able to search memory for string patterns. As I may not know the exact byte->tile mapping used by the game, I'd like to be able to enter a string and have the toolkit look for patterns in the memory that could imply the tile mapping.

For example, searching for "25000" would match "C2 C5 C0 C0 C0" or "06 09 04 04 04" as the second byte is 3 bytes higher than the first, and the last three are the same and two bytes lower than the first.

Copied from original issue: RetroAchievements/RASuite#62

Delay achievement processing on ROM load

As a developer, I don't want to have to code my achievements to handle uninitialized memory while the ROM is being loaded into RAM. There isn't a single valid reason why any achievement should trigger in the first few seconds of starting a game.

Integrated leaderboard editor

From @Jamiras on March 21, 2018 15:16

As a developer, I would like the ability to define a leaderboard using the toolkit. Currently I have to manually generate the expressions and load them into the web page, then restart the game to pull them from the webpage in order to try the leaderboard. As the leaderboard is fully active, submissions are made when the leaderboard submit condition is met. It would be preferable to be able to edit and test leaderboard locally before uploading them to the web page.

Copied from original issue: RetroAchievements/RASuite#63

Memory address tooltips in achievement editor

As a developer, I'd like to know what data is in a memory address by looking at an achievement definition without having to go to the address in the memory viewer.

Suggest showing a tooltip containing the code notes for the address (if present) when hovering over the address.

ResetIf/PauseIf with multiple conditions: AndNext flag

From @Jamiras on March 21, 2018 15:1

As a developer, I want to be able to reset or pause an achievement when multiple conditions are true. Currently, if I apply the ResetIf/PauseIf logic to multiple conditions, the achievement is paused or reset if any are true.

The current UI doesn't really provide an easy way to do this. My best suggestion is a AndNext flag that ANDs the state of the current condition to the result of the next condition:

For example:

AndNext level = 3
ResetIf Character = George

This would only reset if Character = George and level = 3

Similarly:

AndNext level = 3
Character = George HitCount 6

would only increment the HitCount if Character = George and level = 3 were both true.

Copied from original issue: RetroAchievements/RASuite#59

Issue in NES ROM (Not File) Checksum calculation

It seems there is an issue with the way RAIntergration calculates the NES ROM (Not File) Checksum in some Games, it doesn't happen that often and it seems only Games with CHR Bank 16 are affected by this as the other NES ROM's Checksums are all correctly calculated.

From what i know is NES the only System on RetroAchievements that is using ROM Checksums, not File Checksums. All the linked MD5s for the NES Games may be correct for RetroAchievements but the ROM Checksums ain't the same when comparing them in other Tools.

NES ROM Documentation

This Screenshot shows the RA linked MD5s of the NES ROM (Not File) and the actual Checksums in ROM Hasher. The Game has CHR Bank 16.

RA Checksum

And in uCON64.

RA Checksum

And here a other Game with CHR Bank 0.

RA Checksum

Possible Memory Leak

std::unique_ptr<unsigned char> memory;

It seems that what's here is supposed to be a dynamic array but is specified as a pointer to a single object.

Possible Remedies:

std::unique_ptr<unsigned char[]> memory; or
auto memory{std::make_unique<unsigned char[]>(BIG_BLOCK_SIZE)};

with the second one you'll get rid of the reset below it.
edit: TestInitializeFromMemorySixteenBitLargeMemory below has the same issue.
We could run the std::unique_ptr ruleset later to catch all of them, those are just the ones I noticed.
It's important because std::default_delete will call delete instead of delete[] which may cause a leak.

Continuous Filter for Memory Inspector

The concept is to create a toggle switch button, alongside the filter! button that would filter every frame and check your comparison until it's turned off again.

Normally when I'm filtering, I will find myself pressing the filter button sometimes dozens of times before I change the conditions of my filter. This switch would be is a big time saver for achievement devs and make for more accurate results by clearing out addresses that don't fit in the filter for one frame and could otherwise easily be missed.

Already talked to @Jamiras about it briefly, he noted that would probably be necessary to turn off the filter history while this is taking place.

Support for 32-bit comparisons

As a developer, I want to create achievements that compare against 32-bit values.

Currently, if you try to enter a value greater than 0x7FFFFFFF (‭2147483647‬) in the achievement editor it is constrained down to 0x7FFFFFFF.

Freely deletable notes? Seriously?

I wanted to get started on developing achievements for a game, and there was some clutter I wanted to get rid of in the Memory Inspector notes. What wasn't made perfectly clear is that, when a note is deleted, it deletes that note for ALL developers as it syncs this change to the server immediately. I created a new RetroAchievements account to test this, and sure enough, those notes are gone. It is eluded to in the documentation, but it is glazed over, and certainly isn't made as urgently clear as it should be.

One, single button, makes this change. All I have to do it click the "delete" button, and BOOM, note gone.

There are NO warnings. Zero indication that I may be screwing over other developers by doing this. Yes, I could save my own bookmarks, but the current UX doesn't make this immediately clear or easy to do. Using notes as though they are bookmarks is much more convenient. I can still see a JSON with usernames and addresses in XYZ-Notes2.txt, but the text content of the "notes" field is empty in all of them.

I realize this is an unpaid, open-source hobby project, but quite frankly, this is unacceptable.

At the absolute minimum, users should be shown a warning that makes it very clear that deleting and saving notes will do the same for other developers. But this won't stop trolls from going through a bunch of games and deleting notes, or even creating fake notes to waste people's time. This is the internet. Stuff like this will absolutely happen.

As for long term fixes, the default server behavior shouldn't be "overwrite" as it currently is. There are many ways to go about this, but one of them is to do the following:

  • Always keep a local copy of locally-created notes. These will override any notes downloaded from the server. This also prevents notes from being lost in the event they are deleted from the RA server.
  • On the server-side, save notes on a per-account basis, not a per-game basis. This handles accidental deletion of notes for literally everyone creating achievements.

In this setup, I believe the default behavior should be for RAIntegration to NOT download notes by default. Instead, a "download online notes" button should be implemented, or something to that effect. Then, when this button is clicked:

  1. Query the server to retrieve a list of users that have notes for a given game.
  2. Download separate lists of notes for those users.
  3. Combine the two lists, using the address as the unique key
  4. When a duplicate is found, ask the user which of the two they would like to keep.
  5. Combine this new, combined list with the local list of notes, if any. Then, either use the local list of notes to override any found in this new list (as suggested above), or ask the user once again which of the two they want to use.

I'm sure this could be fleshed out more. But there really needs to be a discussion about this.

As a side note, does retroachievements.org keep server-side backups of notes? If so, I'd like to request that a certain game be restored to a previous state...

Dump of concepts

From @kdecks on April 8, 2018 5:49

Anyone who wants to can dump incomplete ideas or ideas that have not yet been fleshed out, here, I'll be doing so. (Just as with anything just because I posted it does not mean I OWN the ideas, good ideas are living things that grow on their own!) Also these names are not important and could be called anything that's effectively descriptive.

Flags:
LockIf (single condition if met, will prevent further achievement processing for that group)
UnlockIf (single condition if met, will unlock the unlocked group above)
Achievement Pointers (an achievement or group that if true will add hits to another achievement, this is done somewhat with addhits and addsource)
ResetFirst (reset before pause) OR LatePause
ResetGroup
ResetTask (see task group below)

ALT group alts:
AND groups (All conditions one each group must be true at once)
Task Groups (Similar to AND groups. Once the conditions of a Task are met achievement processing stops, it cannot be paused or reset and remains TRUE. Once the Tasks and the Core group are true the achievement is activated. Can only be reset by a ResetTask)

Other:
Any kind of logic gates
Data sheet (a table of values are stored and retrieved by the achievement editor during the current session)
Offline data sheet (like above, but allows for values to be stored between sessions.) (Would probably need encryption or a hash check to prevent cheating.)

Copied from original issue: RetroAchievements/RASuite#76

PauseIf HitCount

From @Jamiras on March 21, 2018 14:34

As a developer, I'd like to create a PauseIf condition that only pauses the achievement if it happens a certain number of times.

Just like regular conditions, when the PauseIf condition is true, it will increment the counter. When the counter reaches the target, then the condition group (core or alt) will be paused. When the condition is false, the counter is not reset. To reset the counter, a seperate ResetIf condition must be present.

This would allow for achievements like "Complete the level without jumping more than three times". Each jump would increment the counter. When it hits four, the achievement is disabled until the user restarts the level and a ResetIf resets the counter.

PauseIf HitCount could also be used as a makeshift timer. If you know the framerate of your system you can create an achievement like "Complete the level in under 30 seconds" that has a PauseIf level=X HitCount (30*frames).

Existing achievements should be audited. Anything with a PauseIf HitCount(>0) will have to be modified. A PauseIf with a HitCount (even 1) will not unpause itself. As the HitCount clause currently has no effect on the PauseIf conditions, they should be changed to HitCount(0).

Copied from original issue: RetroAchievements/RASuite#55

Non-ASCII characters are not sent correctly in code notes

This is a regression, as this was working as of January. Characters outside of standard extended ASCII, such as ō, are either replaced by standard ASCII "equivalents" (in this case by o), or by interrogation marks (at least for Chinese characters, e.g. ).

The database was updated recently to support UTF-8 characters in code notes. They are currently not displayed correctly on the website, but this will be fixed in a future update (likely 2.0.0). In the meantime, support should be restored on the side of RAIntegration as well.

A secondary issue is that characters outside of standard ASCII, i.e. extended characters such as ö, now return an error despite being sent and processed correctly by the server. This can be moved to a separate issue once confirmed if the solution is different from that of the main issue.

Access Violations

So to resolve conflicts I had to merge RetroAchievements master with my master. Tried to test a few things but exceptions keep on getting thrown. I'll add to this as I find them. I'll try to see what's wrong with them myself. On the master branch of course.

I'll put my findings in another comment per problem I find.

Problem 1 - Making Local Achievements

Steps to reproduce:

  1. Load any game (I loaded Contra)
  2. Click "Achievement Sets"
  3. Click the "Local Achievements" radio button
  4. Click "Add Achievement"
  5. Double click the achievement.
  6. Access violation

Call Stack
image

Error
image
image

ChangesTo operator

From @Jamiras on March 21, 2018 14:39

As a developer, I would like to track the number of times a value changes to a specific value. To do this currently, I usually write an achievement like "value = X HitCount N and PauseIf delta(value) != X", but this pauses the entire condition group while the "delta(value) != X" clause is true, preventing it from being reset.

What I'd really like is "value ChangesTo X HitCount N" where ChangesTo evaluates as "= X and delta != X". Then I could use it like any other condition and not have to worry about the achievement being paused.

Copied from original issue: RetroAchievements/RASuite#56

Support for AND clauses in leaderboard cancel conditions

From @Jamiras on March 21, 2018 14:55

I was looking at the leaderboard code and noticed the following:
https://github.com/RetroAchievements/RASuite/blame/master/RA_Integration/RA_Leaderboard.cpp#L462
The cancel condition is passed TRUE when it's evaluated, which indicates Test should return true if any condition is true. This effectively makes the logic be OR for all conditions in the Cancel field (despite still using '_' as a separator). It looks like it's been that way since it was originally added, which is further supported by the fact that it's the only timeTest is passed TRUE.

There are currently 171 leaderboards across 22 games that have multiple conditions in their Cancel field, most notably Super Mario Bros and Super Mario Kart.

This behavior seems idiosyncratic (and I don't believe is properly implemented in RetroArch).

I don't know if there's any easy way to fix it. Ideally we'd want to make "_" behave like an and and introduce a new symbol for or (probably "S" like achievements).

But since it's dll behavior, we can't update the existing leaderboards until a new dll is published. Maybe have an interim release that defines both "_" and "S" as OR, fix all the achievements, then redefine "_" back to AND in a later release?

Copied from original issue: RetroAchievements/RASuite#58

Passwords are not URL-escaped

I wasn't able to log in to RA through any of the emulator apps, so I changed my password to a simple one. I was able to log in with this simple password. After changing my password back to a more complex one, I wasn't able to log in again. This made me think it was an issue with not escaping special characters on the login request. I skimmed the source code, and saw that passwords are sent as-is without any checks to see if the characters are valid URL characters. Sure enough, after manually escaping my password and pasting the escaped version into RAIntegration's password field, I was able to log in.

For clarification, please consider the following example:

Let's say this string is my password (it's not my real password!): s3?AJhs:u+S=D<%-]@{O

If I use this string as-is in RAIntegration, my login will fail, even though I would be able to log in to RetroAchievements.org with this very password.

However, if I enter an escaped version into RAIntegration, my login will succeed without issue. So my above password would become:

s3%3FAJhs%3Au%2BS%3DD%3C%25-%5D%40%7BO

So instead of sending this request:

https://retroachievements.org/login_app.php?u=ubergeek77&p=s3?AJhs:u+S=D<%-]@{O

it sends this request:

https://retroachievements.org/login_app.php?u=ubergeek77&p=s3%3FAJhs%3Au%2BS%3DD%3C%25-%5D%40%7BO

and the server can properly read the password and send the appropriate response.

RAIntegration should really be doing this manually, as sending any URL request before properly sanitizing it is bad practice.

Leaderboard OR support

Please disregard and close this if it's a duplicate.

What is Requested:

A means for leaderboard syntax to support OR logic; especially for SUBMIT, but also for START and CANCEL as they use the same syntax.

Reason for Request:

The problem lies especially with SUBMIT: Most games do not have a single, stable address/value for GAME OVER (failure) and GAME OVER (victory), but typically a developer will want to have a submit during failure or victory.

OR logic missing is the number one obstacle I've had while making leaderboards (and I've made more than most developers). Adding a method would save dozens of hours for many devs and often make impossible, possible.

Extra Notes:

  • For some games this is not an issue. Depending on the memory.
  • Some games there is a (typically labor intensive) work-around: Finding an address that has one unique state for failure and another unique state for victory. Next I'd find out every other possible states of this address and excluding them. Testing it is a bitch too, because the values cant ever be used any other time in the game or you'll submit out of turn.
  • Some games provide no solution at all in the memory and making separate leaderboards for victory and failure is a weak work-around.

Ability to develop achievements offline

As a developer, I'd like to be able to create achievements without an active internet connection. Currently, to access any of the tools, I have to log in first.

  • Minimally, allow updating and testing of Local achievements (no internet required)
  • Ideally, allow adding/updating code notes (these would have to be queued and submitted once reconnected).
  • Modification of non-Local achievements would not be expected.
  • Earning of achievements while offline should be a separate issue if desired, and has its own set of concerns around possible cheating.

Suggest adding a new "Work Offline" menu option to enable the behavior. The user could just click past the "error connecting to retroachievements.org" dialog, or it could be modified to quickly enable the feature. In either case, once enabled, a dialog should be shown letting the user know they're in offline mode and that achievements cannot be earned until they reconnect.

unreachable code

@Jamiras
While I was working on the implementation of PauseIf/ResetIf hitcounts for RetroArch I noticed that part of the code is possibly unreachable (highlighting "possibly" because I'm not used to all peculiarities of C++).

The code is in one of those Test() methods, which you can see below.

I think the code between lines 440 and 502 is unreacheable because it's inside a for loop and right above line 440 there is a switch-case clause that always goes to a continue and the default case is a break.

bool ConditionGroup::Test(bool& bDirtyConditions, bool& bResetAll, const std::vector<bool>& vPauseConditions, bool bProcessingPauseIfs)
{
unsigned int nAddBuffer = 0;
unsigned int nAddHits = 0;
bool bSetValid = true; // must start true so AND logic works
for (size_t i = 0; i < m_Conditions.size(); ++i)
{
if (vPauseConditions[i] != bProcessingPauseIfs)
continue;
Condition* pNextCond = &m_Conditions[i];
switch (pNextCond->GetConditionType())
{
case Condition::AddSource:
nAddBuffer += pNextCond->CompSource().GetValue();
continue;
case Condition::SubSource:
nAddBuffer -= pNextCond->CompSource().GetValue();
continue;
case Condition::AddHits:
if (pNextCond->Compare())
{
if (pNextCond->RequiredHits() == 0 || pNextCond->CurrentHits() < pNextCond->RequiredHits())
{
pNextCond->IncrHits();
bDirtyConditions = true;
}
}
nAddHits += pNextCond->CurrentHits();
continue;
default:
break;
}
// always evaluate the condition to ensure delta values get tracked correctly
bool bConditionValid = pNextCond->Compare(nAddBuffer);
// if the condition has a target hit count that has already been met, it's automatically true, even if not currently true.
if (pNextCond->RequiredHits() != 0 && (pNextCond->CurrentHits() + nAddHits >= pNextCond->RequiredHits()))
{
bConditionValid = true;
}
else if (bConditionValid)
{
pNextCond->IncrHits();
bDirtyConditions = true;
if (pNextCond->RequiredHits() == 0)
{
// not a hit-based requirement: ignore any additional logic!
}
else if (pNextCond->CurrentHits() + nAddHits < pNextCond->RequiredHits())
{
// HitCount target has not yet been met, condition is not yet valid
bConditionValid = false;
}
}
nAddBuffer = 0;
nAddHits = 0;
switch (pNextCond->GetConditionType())
{
case Condition::PauseIf:
// as soon as we find a PauseIf that evaluates to true, stop processing the rest of the group
if (bConditionValid)
return true;
// 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.
bSetValid = false;
if (pNextCond->RequiredHits() == 0)
{
// PauseIf didn't evaluate true, and doesn't have a HitCount, reset the HitCount to indicate the condition didn't match
if (pNextCond->ResetHits())
bDirtyConditions = true;
}
else
{
// PauseIf has a HitCount that hasn't been met, ignore it for now.
}
break;
case Condition::ResetIf:
if (bConditionValid)
{
bResetAll = true; // let caller know to reset all hit counts
bSetValid = false; // cannot be valid if we've hit a reset condition
}
break;
default:
bSetValid &= bConditionValid;
break;
}
}
return bSetValid;
}

Achievement cannot be created with MemAddr larger than 2K

As a developer, I want to create an achievement with many conditions. The UI doesn't limit me, but things start going back when the serialized string exceeds ~1900 bytes.

There's two major issues contributing to this.

  1. AchievementSet::SaveToFile uses a 2K buffer to write each line of the XXX-User.txt file. With the additional space taken by the achievement title, description, and other attributes that leaves around 1900 bytes for the MemAddr string.
  2. AchievementSet::LoadFromFile uses a 4K buffer read each line of the XXX-User.txt file, so even if you used another tool (RATools/Notepad) to generate a more complete achievement, it still couldn't be loaded.

These methods should be modified to support larger achievements now that the database does.

Rework "News" overlay

The existing News overlay renders the raw HTML that appears in the News section of the home page.

image

There's enough information in the JSON received from the server to do a decent job of mimicing the server behavior.

In addition to "fixing" the display behavior, the News overlay should be featured in the emulator until the user selects a game.

Alternately, we should stop fetching the News data on startup as most users probably never use it.

Active Hits in Cheevo Descriptions

It would be useful to have a active Hits counter being displayed in the Cheevo Descriptions.
The implementation would work by using ID Requirements in the Description, for example:

  • Displayed in RetroArch:
    Collect 10 (5) Apples.
    Nothing changed beside the (5) shows the current number of Hits/collected Apples.

  • Code could look like following:
    Collect 10 [H=2] Apples.
    [H=] being the function to read Hits, 2 the ID in the Toolkit (Cheevo Requirement).

Having such function build into the Cheevo Descriptions would allow anyone to quickly open the Cheevo Menu and see all active Hits by browsing through the list of Cheevos, which Cheevo is alive (not reseted yet) or which Item has been collected/which one is missing, for example:

Cheevo List:

  1. Deathless
    Finish Stage 1 without falling into a hole (1).

  2. Dragon Parts
    Collect Dragon Sword (1), Dragon Shield (1) and Dragon Armor (0).

The Hits in Description could also be displayed on the Game Page / Cheevo Page, updated with the Rich Presence or just being hidden. Having such information on Web would also allow us to use the RP Space on the frontpage for other ingame informations.

Naming and Layout/Style Proposal

From @SyrianBallaS on April 3, 2018 19:25

Preface

OK, so I noticed the .editorconfig file got accepted. That will definitely help a little bit, but ultimately not solve the root issue of inconsistent style. It is understandable that inconsistencies are undesirable and wish to propose a solution. Some of these come from the reviews in pull request #49, others from myself, and, isocpp. For some insight, I professionaly program in ASP.NET Core so some stylings may differ from isocpp.

On another note (somewhat related), I propose using a package.config/project.json file for binaries to prevent changes to library files (plus it will save space in the repo). Had one a long time ago but deleted the repo that was in it because it was too far gone (incorrect).

The lead engineer (I'm assuming @ScottFromDerby) will have the final say.

This is just a proposal, what's actually going to be used should be formalized in the wiki or a CONTRIBUTING file. This is just to give some ideas.

Naming

For a lot of these, you can easily change them using ctrl+alt+f.

Data Members

I would prefer data members be in snake-case with a leading underscore (i.e., some_var_) as encoding type information is redundant. However, if the majority strongly desire Hungarian Notation that's fine too.

Methods

  • For this one I'm not too sure on what we should do but I'll propose something any way.

Event Handlers

  • Given my background, I propose we use Pascal case for these.
  • An event handler for us is like the Message Handlers in my fork.

Other Member functions, internal functions(helpers), and global functions

  • These should be in snake-case

Classes/Structs

  • These should be in snake-case, we could make the first letter capital.
  • In my fork I wrapped all the new stuff in a namespace (ra) but put a macro _RA to make it more consistent. It's defined like this: #define _RA ::ra::

Enums

  • Should be changed to "enum class", this prevents conflicts and disables implicit conversion to int.
  • snake-case
  • I made a helper function etoi (made an alias to_integral) to convert any enum to its underlying_type.
  • I did put two concepts (type_traits) is_equality_comparable and is_lessthan_comparable to validate something is comparable it looks for the equality and lessthan operator. It will be important for some enums. If those validations pass in static_assert you can use using namespace std::rel_ops (suggest at function scope) to have the rest of the operators done for us. Some other operator overloads might be necessary as well but it's trivial.

Layout

More Formatting (not covered by .editorconfig)

To check/uncheck, go to Tools->Options->Text Editor->C/C++->Formatting->(*something). *something will depend on what we're trying to do. Some of these might be checked/unchecked already.

To save time I'll just put a screen shot

image
image

I'd suggest for everything besides multiline functions and long loops (the initialization part) put a brace on the same line with one space before it.

Limit Macros

Unacceptable Uses

  • Symbolic Constants (ironic)
    • Sometimes a macro is the only solution; yet, if used for a global constant there is another way.
    • That other way is use the keyword constexpr(wasn't fully implemented (still isn't, no constexpr if) by Microsoft until C++17 but did exist in C++14).
      • Example: instead #define SOME_STRING "a string", consider inline constexpr const char* some_string{"a string"}
    • constexpr if`is short for "constant expression" and can only be used on types that can be resolved at compile time.
      • Example: You can use constexpr on std::array but not std::vector, std::string, std::map, etc.
    • For the strings we should const char* since arrays aren't null terminated. In the Microsoft's version of the GSL they have something called zcstring which is a wrapper for const char* which is bounded.
  • Aliasing
    • For functions your can just use std::function with std::bind. (creates a function object instead of a pointer, which automatically deallocates).
    • Another way is to use a function pointer alias.
    • Example:
      int some_fn(char c, std::string str);
      using some_fn_t = decltype(&some_fn);
      const auto& another_fn{static_cast(some_fn_t)(some_fn)};
      It didn't like the angle brackets.
      • Now another_fn is an alias to some_fn; Did some extra steps because that's for overloads. Overloaded template functions needs parameter forwarding but we'll worry about that later.
    • For member functions, the same as the above but you have to provide an object. If it's in the same class using this, should be good enough.
    • A better way to alias a type without declaring it is to use using. Instead of typedef int some_type ; consider using some_type = int;.

Acceptable uses

  • Required by a dependency, such as HANDLE_MSG.
  • Pretty much anything that is not used in execution (seldom few exceptions like above).

Copied from original issue: RetroAchievements/RASuite#73

[feature request] Hardcore Mode as default

I guess there's no need to repeat here what already discussed in discord, right?

Many active members and the leadership agree that the hardcore mode should be enabled by default on the first run of the RAEmus. If users want save/load state, they need to disable hardcore mode.

Just to put very clear, this request is only about making hardcore mode be the default mode on the first run. Nothing more, nothing less.

EDIT:
@Jamiras mentioned on Discord something about showing a message when the user tries to save/load a state while in hardcore mode. Alright, that would be cool too! 👍

[feature request] ability to use Memory Inspector while offline

It would be useful if we could use the Memory Inspector even when offline. The code notes fields could be grayed out when not connected.

Not sure if it's an edge case, but for me it would be extremely useful (and in a discord conversation with @JuliaWolska (aka Salsa) she said it would be useful for her too).

My case: every month I stay for 14 days working at sea. And while there I have internet access in my spare time but it occurs through a restrictive proxy, which blocks retroachievements.org. Therefore, totally unable to access the RetroAchievements menu -> Memory Inspector in the emulators.

I could work on more games if I was able to use Memory Inspector offline.

Avoid querying the API for unlinked titles

An ID of 0 is currently sent to the backend when running an unlinked title. I'm not sure at which point this is triggered, but because of that a user's recently played titles page will contain an empty title with an ID of 0. This doesn't happen prior to the linking dialog being triggered, so it should be possible to avoid subsequent API calls if no title is selected for linking.

Corresponding issue on RAWeb: RetroAchievements/RAWeb#202

HitCount for leaderboard value

From @Jamiras on April 1, 2018 13:30

As a developer, I'd like to create a leaderboard whose value is a HitCount condition, not a memory address.

For example, Mario's coin counter resets at 100, so there's no way to create a leaderboard for most coins collected in a playthrough. I'd like the value to be hitcount(coins != delta(coins))

Copied from original issue: RetroAchievements/RASuite#70

Achievement folders

From @kdecks on April 8, 2018 5:7

This is probably a hybrid RASuite/RAWeb issue.

This concept is for visual grouping:

Instead of
image
We get

+ Penguin Craze
    - Penguin Craze I
    - Penguin Craze II
    - Penguin Craze III
    - Penguin Craze IV
    - Penguin Craze V
    - Penguin Craze VI

Where the + designates collapsible folders and - designates contents hidden after being collapsed.
Each one of the Penguin Craze I through VI would all be normal achievements.

Properties:

The grouping is to allow devs to more clearly show set structure and help visually simplify game pages. The folder is not an achievement, but would display a percentage of the achievements completed within it. The folder would be able to be expanded and contracted on the game page and inside the emulator. Only one level of folders would be allowed, no sub folders. A default folder icon would be used for visual consistency.

Copied from original issue: RetroAchievements/RASuite#75

Challenge achievement indicator

As a player, I want an indicator when a challenge achievement is active so I know when not to do things that might disable it, and know when I have done something that does disable it.

This primarily applies to things like no-damage achievements. An icon would be displayed when starting the boss fight, and disappears when damage is taken or the achievement triggers.

Invalid argument exceptions thrown from achievement editor

This is while testing my enum_to_enumclasses branch to make sure the dirty flags and bitwise operator overloads were working as intended.

Basically all you have to do is enter a non-integral into the points section and it will throw. Probably during WM_CLOSE and/or IDOK we should check that "Points" field is populated with an integral.
Kind of like this, it's just an example. There could be better way, just to give an idea.

case WM_CLOSE:
...
auto len{GetWindowTextLength(point_field_handle)};
if(len == 0)
 return -1;

auto buf{std::make_unique<TCHAR[]>(len)};
if(GetWindowText(point_field_handle, buf.get(), len) == 0)
  return -1;

std::string str{Narrow(tstr.get())};
for(auto& ch : str)
{
   if(!std::isdigit(static_cast<int>(ch)))
   {
       MessageBox(..., _T("The point field must only contain an integral value."), ...);
       return -1; // To stop processing it has to be something besides 0, "-1" is arbitrary.
    }
}
...

So here's a few other non-critical things I noticed.

  1. Only dropdown fields and button presses seem to trigger Dlg_AchievementEditor::LoadAchievement
    • Not really sure if this is possible, but maybe we should add a watcher for edit fields.
  2. Putting in bizzare input is not always checked
    - In Mem/Val, if you put something like !df3*&342 it will change back to zero. If you put in something like 321a~21 it will keep it but when you close the editor and open it back up, it'll show 0x321a.
    - In "Hits" if you put "321@32" it will stay there and the sets dialog will even let you save it!
    - Fortunately, it puts in "321" but all fields should be validated before closing the editor window.
    - If you enter "32$#14#$12" in "memory", when you close and open it back up it will show 0x000032
  3. Dlg_AchievementEditor::LoadAchievement sometimes gets called twice in unofficial (did not commit anything).

RAGoals

@Jamiras, @SyrianBallaS, @GameDragon2k, @leiradel, @ScottFromDerby, @luchaos

Hey guys, I've been talking with @ScottFromDerby and @leiradel about the bright and promising moment we, at RetroAchievements scene, are experiencing. We have a bunch of very skilled guys contributing with the project and this is freaking awesome.

However, we are a bit scattered. Each one of us is making great contributions but each with their own focus. I have a strong conviction that if we make a roadmap and share the same goals, we will achieve better and faster results and also avoid wasting of time on unnecessary work.

That being said, let me list here some goals we are aiming for RAIntegration in a hope that we can direct the efforts to them.

decouple the achievement triggering/testing code from RAIntegration

The main reason for it is that currently it's a pain to maintain two codebases for the same thing (here and on RetroArch).

RetroArch users are a big slice of our user base (I dare to say they are more than 50%), and any new feature implemented in RAIntegration must consider compatibility with RetroArch.

The good news is that @leiradel is already working on this front.

UPDATE: here's the repo where this is being worked: https://github.com/RetroAchievements/rcheevos

ability to develop achievements with Lua

This will be really cool. Achievement developers will be freed from many of the existing limitations of the current toolkit.

Good news here too: @Jamiras and @leiradel are working on this front.

We still need to make some adjustments on the server side to make it a real thing, but I already started to discuss it with @luchaos. I hope we can solve it soon.

add RAIntegration support on the Windows version of RetroArch

Currently we have RALibretro, but as you may know it lacks some basic features (e.g.: input remapping) and implementing them from scratch is like reinventing the wheel.

The good news here is that @leiradel told me that the leadership of RetroArch is in a good agreement about adding RAIntegration support on the Windows version of RetroArch.

The new RetroArch WIMP GUI should be a good interface to use while developing achievements. It's powered by Qt, and it's exactly the subject of the next topic.

port RAIntegration from Win32 API to Qt

This is a long-term goal. I recognize that this requires a good amount of effort, but I want to highlight this as a goal in a hope to prevent the current development from moving toward a more intensive use of Windows-only solutions.

We know that some libretro guys are interested on this Qt port, and maybe they can help here at some point.

Being able to develop achievements on Linux is like a dream for me and I'm sure that it would also bring many knowledgeable people to the scene (cough @RobLoach cough).

IMHO making the cheevo development toolkit be cross-platform is a huge contribution to its longevity.

Indirect memory addresses: pointers, etc

As a developer, I want to be able able to reference memory that may not always be at the same address.

To simplify the explanation, I use the following conventions:

  • $ is a memory reference
    • $1234 means the memory at address 0x1234.
  • 0x is a constant
  • both are hex encoded
  • () indicates order of operations:
    • $(0x5678 + $1234) means: take the memory at address 0x1234, add 0x5678, and get the memory at the resulting address.

There are four scenarios to consider:

  • Direct pointer: A direct pointer is a memory address that points at another memory address of interest.
    • $($1234)
    • $1234 contains 0x5678, condition should look at $5678.
  • Indirect pointer: An indirect pointer is a memory address that points at a chunk of memory where the memory of interest is always N bytes from the pointed-to memory. N is typically the offset of a field within a structure, but it could also be a constant index into an array.
    • $($1234 + 12)
    • $1234 contains 0x5678, condition should look at $(0x5678 + 12), i.e.$5684
  • Array index: The memory address specifies the number of bytes to offset a known address by.
    • $(0x5678 + $1234), alternately notated as $(0x5678[$1234])
    • $1234 contains 6, condition should look at $(0x5678 + 6), i.e. $567E
  • Scaled array index: The memory address specifies a number of units to offset a known address by. A unit is some number of bytes that may represent a simple data type (2 for 16-bit values, 4 for 32-bit values) or a complex data type (i.e. 48 for a character sheet in an RPG)
    • $(0x5678 + $1234 * 16), alternately notated as $(0x5678[$1234*16])
    • $1234 contains 6, condition should look at $(0x5678 + 6 * 16), i.e. $56D8

In all of these scenarios, a known memory location ($1234) can be used to determine a dynamic address that the condition depends on. All of the scenarios also fit into the formula: "read memory at address, multiply by scalar, add constant, and read memory at result":

  • Direct pointer: $($1234 * 1 + 0x0000)
  • Indirect pointer: $($1234 * 1 + 0x000C)
  • Array index: $($1234 * 1 + 0x5678)
  • Scaled array index: $($1234 * 16 + 0x5678)

As such, the proposal below attempts to fit that formula into the existing framework with the smallest amount of overhead.

A new flag AddAddress will be used to indicate the result of the current row will be added to the address field of the next row. The comparison column will be changed to a multiplication symbol (fixed).

Direct pointer: Condition $($1234) is 3

AddAddress Mem 16-bit 0x1234 * Value 1
           Mem 8-bit 0x0000 = 3

Indirect pointer: Condition: $($1234 + 12) is 3

AddAddress Mem 16-bit 0x1234 * Value 1
           Mem 8-bit 0x000C = 3

Array index: Condition $(0x5678 + $1234) is 3

AddAddress Mem 8-bit 0x1234 * Value 1
           Mem 8-bit 0x5678 = 3

Scaled array index: Condition $(0x5678 + $1234 * 16) is 0

AddAddress Mem 8-bit 0x1234 * Value 16
           Mem 32-bit 0x5678 = 0

Because the AddAddress line looks like a condition line (except for the '*'), it won't require a fancy new UI, and should be fairly easy for developers to use (assuming they understand how pointers work in the first place). You can also see illustrated above how easy it would be to specify the size of the pointer (or index) using the existing "16-bit/8-bit" dropdown. The direct pointer example uses a 16-bit pointer. The two array index examples use an 8-bit index. The scaled array index example shows the other side of the coin where a 32-bit value is pulled from the calculated memory address.

This structure also leaves the flag field blank on the condition line, allowing for the use of ResetIf or PauseIf constructs dependent on the indirect memory. The Hits column would be ignored for the AddAddress lines, but still usable normally on the final condition line.

You can also combine multiple AddAddress lines to something more complex. Here's a scaled array pointed at by a direct pointer:

AddAddress Mem 16-bit 0x1234 * Value 1
AddAddress Mem 8-bit 0x5678 * Value 16
           Mem 16-bit 0x0004 = 3

Which is $($1234 + $0x5678 * 16 + 4) == 3.
The first line is the pointer to the data structure array.
The second line gets the Nth item of the array, where each item is 16 bytes long.
The third line gets the 16-bit value 4 bytes into the structure.

Linking or caching issues in certain conditions

Launching RANes from a search (through the Windows 10 Start Menu for example) causes the following issues to occur:

  • Credentials cannot be retrieved and sessions cannot be restored
  • Achievement lists cannot be retrieved (silent failure)
  • Most items in the menu indicate that no ROM is loaded, despite an ID being shown in the Achievement Sets window
  • An ID of 0 is sent to the server, and an empty entry is shown in the recently played games list on the logged in user's profile

Logging on and linking a hash to a database entry work correctly.

When launched from Windows Explorer by navigating to the directory of the executable, none of these issues are present.

Offline Mode for developing and earning cheevos

A Offline Mode would be nice for those of us who have a unstable internet connection or just want to work/play offline and then sync their progress manually to the server, similar to PSN/XBL.

Developing Offline

Making the entire developing progress offline compatible and allow to save a set/cheevos in one package-file, which can easily be transfered from one device to another device.

Earning Offline

A switch in the Emulators (like Hardcore) which turns on the 'Offline Mode'. Offline Mode would function similar to Softcore but it will require to Sync the earnings to users profile, all progress will be saved locally as well. Also being able to load the one package-file which contains every data for developing and earning cheevos incl. cheevo badges anytime a user want, with saving progress of which cheevo has been earned.

This way users can share their custom sets in form of a single file with other users without uploading their content to the Site.

ResetIf HitCount

From @Jamiras on March 21, 2018 14:26

As a developer, I'd like to create a ResetIf condition that only resets the achievement if it happens a certain number of times.

We already have several examples of "Defeat boss without taking damage", but it's impossible to do something like "Defeat boss without jumping more than three times". To do that, you'd need a counter on the ResetIf condition.

Just like regular conditions, when the ResetIf condition is true, it will increment the counter. When the counter reaches the target, then the achievement will be reset.

Because it's tied to a HitCount, any other ResetIf in the achievement may reset its HitCount, but that's desirable as the entire achievement is supposed to reset if any ResetIf is true.

Existing achievements should be audited. Anything with a ResetIf HitCount(1) will be compatible with the change as the counter will increment to 1 and immediate reset the achievement. Anything with a ResetIf HitCount(>1) will have to be modified. As the HitCount clause currently has no effect on the ResetIf conditions, they should be changed to HitCount(0).

Copied from original issue: RetroAchievements/RASuite#54

Condition flag for progress

From @Jamiras on March 21, 2018 14:51

As a developer, I'd like to mark a HitCount condition as a progress indicator for providing feedback to the user.

Many achievements fall into the category of do something N times, but the user doesn't necessary know how may times he's done it. This feature would enable specific achievements to report progress using the leaderboard UI.

Conceptually, as each achievement is processed, any active achievements with a Progress condition and a non-zero HitCount would calculate their progress percentage. Whichever was closest to completion would show up in the corner like a leaderboard.

For example, if there are achievements for killing 100 enemies and 1000 enemies, both would have a HitCount of 50. 50/100 = 50% and 50/1000=5%, so only the 50/100 would be displayed.

I'm not sure the best way to indicate what the progress is measuring. The best idea I've had is to show a leaderboard startup popup "Challenge available - kill 100 enemies" when the HitCount changes to 1, but if the user is working towards multiple goals at the same time, one might jump ahead of the other and suddenly the progress changes from "50/100" to "5/8" and the user may not remember what he's supposed to be doing 8 times.

By using the Flag field, this logic can be applied to a single condition or a series of AddSource or AddHits conditions. It also eliminates the need to change the UI.

Copied from original issue: RetroAchievements/RASuite#57

Completed achievements as a condition

As a developer, I'd like to create an achievement that requires one or more other achievements to be completed first.

For example, in Mario Kart, I might have achievements for getting gold in Mushroom Cup, Flower Cup, Star Cup, and Special Cup. Then I'd have a meta-achievement for getting the gold in the other four cups. Since the other achievements can be earned independently, the meta-achievement can be earned across multiple sessions.

The implementation would be to change the requirement to be "Achievement 12345 = 1". Which would serialize to something like "@12345=1". Note the new "@" prefix to differentiate it from Delta, Memory and Value types. Something to consider is how to represent this for local development where achievements all have ID=0.

Permission validation in toolkit

From @Jamiras on March 21, 2018 15:20

As a non-developer, I don't want the buttons for promoting achievements to be active as pushing them will only generate an error. Alternately, a permissions error could be displayed without sending anything to the server.

There is currently no way to determine the user's account type in the toolkit. Changes would have to be made to return it from one of the APIs (probably the login API).

There are other things that could be better handled in the toolkit knowing the user's account type, like ROM linking.

Copied from original issue: RetroAchievements/RASuite#64

Licensing

From @leiradel on February 25, 2018 14:24

Although one might infer that the RASuite code is licensed under the GPL, since it's both compiled into and dynamically linked to other GPL'ed projects, it's best to be clear about it.

As the RALibretro developer, I'd like to make sure under what terms I can use RA code.

Copied from original issue: RetroAchievements/RASuite#45

Requirements Gathering (really need input)

I'm just going to work on other stuff and leave this alone for awhile.

Edit: Since there aren't really any features being added/removed there won't be an SRS, but will still like some feedback.

OK, so it came to my attention that there are several aspects of the project that I either overlooked or had no idea what was required of them. This issue will comprise a series a of questions based on #19 Issue. This may seem overblown for an open-source project but my engineering process here has come into question. So resolve that, I will try do things the "right" way, in accordance to process best-practices.

Maybe in the future
I currently do not have access to set-up a GitHub project on this repository so something reflecting this will be in my fork for organization purposes.

Once the requirements have been gathered I will formalize it into a SRS (Software Requirements Specification) and post it here for review (I don't access to request reviews, add labels, etc.). It will also be in my fork in a "docs" folder but will not be included in pull requests.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.