- add convenience
cg.ReplayMap
,cg.ReplayPath
,cg.ReplayString
, andcg.ReplayID
methods which create loaded loadables - loading a nonexistent replay will now raise
NoInfoAvailable
like it used to (this had regressed recently) - raise
ValueError
on replays with empty replay data
- add
fuzzy_mods
utils method, which allows you to pass a required mod and a list of optional mods, and a list of each possible mod combination is returned - require snaps to also be in the hitwindow of a hitobj to be counted when using
only_on_hitobjs=True
- Previously, snaps could be counted when they were "on" the closest hitobj, even though that hitobj might be many seconds in the past or future
- move to osrparse for osr parsing instead of circleparse
- require
slider>=0.4.0
instead ofslider~=0.4.0
- add
only_on_hitobjs
parameter tocg.snaps
(defaults toTrue
) which, if set, only returns snaps that occur on hitobjects - all hitobjects returned by
Circleguard
methods (fromcg.snaps
andcg.hits
, for instance) are now converted to their hard_rock form if the replay had HR enabled - add convenience method
cg.beatmap(replay)
which returns the beatmap associated with the given replay - expose hitobject classes in
__init__.py
- fix maps with no replays for a mod combo throwing
NoInfoAvailableException
- required
slider ~= 0.3.1
instead ofslider == 0.3.1
- remove
Mod.__ne__
as it's the inverse ofMod.__eq__
by default if ne doesn't exist
- return ndarray instead of list in
Circleguard#frametimes
- add
Circleguard#frametime_graph
to generate a frametime graph from a replay. This requires that you havematplotlib
installed - add
__str__
and__repr__
toHit
- fix ur and hits erroring on maps that have catmull sliders
steal_check
,relax_check
,correction_check
,timewarp_check
have been removed as deprecated, andrun
has been removed. Usesimilarity
,ur
,snaps
,frametime
, or a combination thereof instead- the
single
parameter has been removed fromsimilarity
,ur
,snaps
,frametime
. These functions now only accept a replay (or pair of replays, insimilarity
's case) instead of aLoadableContainer
. Instead of passing aLoadableConatainer
to these functions, you should iterate over the container and call the function with single replays - new
cv
parameter forur
,frametime
,frametimes
, which allows you to choose if you want the return value to be converted or unconverted - new
within
parameter forhits
, which returns only hits within a certain distance from the edge of the hitobject - new functions
cg.Map
,cg.User
,cg.MapUser
which create the correspondingReplayContainer
and loads its info. This is shorthand for writing (for example):
u = User(...)
cg.load_info(u)
- new
mods_unknown
parameter tosimilarity
,frametime
,frametimes
, which allows replays with unknown mods to still be processed, with the behavior specified bymods_unknown
. This is useful e.g. for finding the similarity of twoReplayID
s, asReplayIDs
do not provide mods due to api limitations Detect
has been removed entirely. If you were usingDetect.SIM_LIMIT
orDetect.CORR_LIMIT
, seeCircleguard.SIM_LIMIT
andCircleguard.CORR_LIMIT
as a replacement. Any other members have been permanently removedResult
and all subclasses have been removed.Circleguard
methods now return the important result (such as a number or list) directly instead of wrapping it behind aResult
ResultType
has been removed- circleguard-specific exceptions have been replaced by base python exceptions where possible
- the
version
attribute ofReplay
s has been renamed togame_version
and is now a new class,GameVersion
, which subclasses int and provides additional functionality x
andy
attributes have been added toHit
- the
hitobject
attribute ofHit
is now a circleguardHitobject
object instead of a sliderHitobject
object - new
Hit#distance
function which calculates the distance of the hit to either the edge or center of the hitobject - new
Hit#within
function which returns true if the hit was within a certain distance of the edge of the hitobject - for all intents and purposes,
LoadableContainer
has been removed. It is still available under the same name, but nothing inherits from it any more and it provides different functionality (acting as a true container and providing convenience operations on a list ofLoadable
s) Check
has been removedReplay
s have a newmap_info
attribute, which provides information about where their map can be found, either online or locally- new
Replay#beatmap
method, which loads the beatmap tied to the replay. This allows replay subclasses to have complete control over how they load their beatmap and which beatmap gets loaded - new
Replay#has_data()
method, which should be preferred instead of checkingreplay.replay_data is not None
ReplayMap
andReplayPath
equality now checks replay data explicitly if both replays being compared are loaded- new
order
utils method which takes two replays and returns a 2-tuple where the earlier replay is first and the later replay is second. This is intended to be used to replacesteal_result.earlier_replay
andsteal_result.later_replay
. Example usage:
(earlier_replay, later_replay) = order(r1, r2)
- new
replay_pairs
method which takes two lists of replays and returns a list of pairs of replays that should be compared against each other to cover all cases of replay stealing in the two lists. This is intended to be used in place of passing aReplayContainer
tocg.similarity
, which no longer accepts iterables. Example usage:
m = cg.Map(221777, span="1-2")
for (replay1, replay2) in replay_pairs(m):
print(cg.similarity(replay1, replay2))
- KeylessCircleguard now has better error messages if you misuse it
- tutorial has been rewritten
- implement
__hash__
for Replay subclasses
- add
KeylessCircleguard
class, which does not require a key to be instantiated and can do everythingCircleguard
can, with the requirement that the passed loadables are already loaded - add
Circleguard#hits
method, which returns a list of the hits in the replay (where the user hit a hitobject) - add
keydowns
attribute toReplay
classes, which is a list of the keys pressed for each frame that were not pressed in the previous frame - implement
__hash__
forSnap
- add new
ReplayString
class, which allows instantiation from a byte string that contains the contents of an osr file (#159) - rename
ReplayPath.hash
toReplayPath.beatmap_hash
- improve ur calculation, which should now usually be exactly correct and sometimes slightly (1-3 ur) off
- rename
*_Check
methods to better describe their use as a statistic calculation - add a
single
argument to*_Check
methods which will immediately evaluate the investigation and return the first result ifTrue
- add user id, map id, and mods index to newly created caches
- pin scipy to 1.4.1 (pyinstaller does not yet have a hook for scipy 1.5.0, which just came out yesterday)
- expose full frametime list in
TimewarpResult
- fix
Mod
instances not being comparable toModCombination
instances (egMod("DT") == Mod.DT
wasFalse
and is nowTrue
) - fix mod initialization from strings not adding
DT
(orSD
) when parsingNC
(PF
) - add
Mod
/ModCombination
tests
- fix
TimewarpResult
being missing from__all__
- add basic timewarp detection (just calculates the median of the frametimes)
- fix
ReplayCache
andReplayDir
double counting replays sometimes (for real this time)
- fix
ReplayCache
andReplayDir
double counting replays sometimes
- add several new
Loadable
convenience classes.ReplayCache
for accessing random elements from a circlecore database,ReplayDir
for folders of.osr
files, andReplayID
for when you only know the replay id.
- correctly account for skips in replays
- don't reorder frames with the same time
- add a new replay stealing detect,
Detect.STEAL_CORR
, using signal processing cross-correlation methods - rename
Detect.STEAL
toDetect.STEAL_SIM
, withDetect.STEAL
remaining as deprecated - split
StealResult
intoStealResultSim
andStealResultCorr
- correctly order replay frames and remove invalid frames. This changes similarity values slightly
- add default cutoffs to
Detect
asSIM_LIMIT
andUR_LIMIT
. These are values we feel are enough to call a replay cheated. They are not used in our code but are provided as a convenience. - don't remove frames with identical time values when processing replay data. This removal still occurrs, but only when the replays are investigated.
- loading replays from cache is now roughly twice as fast
- fix
ReplayMap
s inside aMap
not having some attributes set after theMap
is info loaded
- add
cg.steal_check
,cg.relax_check
, andcg.correction_check
as convenience methods. These methods callcg.run
internally - don't require
Check
when usingcg.run
.cg.run
now accepts an iterable ofLoadable
s and aDetect
- provide cvUR as
result.ur
and ucvUR asresult.ucv_ur
- add new
Span
class, which can be used to represent a range of numbers with a string - remove
num
argument to loadables. Use theSpan
class, or an appropriate string, instead - remove
ischeat
fromResult
s and remove thresholds for determining a cheated replay. Users should determine their own threholds and check againstResult
attributes - make
Detect
anIntFlag
instead of a full class - add default
load
andload_info
implementations toLoadableContainer
- don't use
Replay.__init__
to process replay data. UseReplay#_process_replay_data
instead - require
cache
inLoadable
instantiation - add
Loader.MAX_MAP_SPAN
andLoader.MAX_USER_SPAN
, representing the most replays you can get from a map and user respectively
- fix error while investigating replays with no replay data
- fix error when loading a replay that has replay info but is not downloadable
- InfoLoadable renamed to LoadableContainer
- cache passed as part of super call
__eq__
required for Loadablenum_replays
method removed (should uselen(loadable_container.all_replays()
instead)- all_replays required by
LoadableContainer
, notLoadable
- default implementation of
__iter__
and__getitem__
in LoadableContainer check.all_replays()
now returns the expected value (loadables1 + loadables2
), new method#all_replays1
which returns only the replays inloadables1
.
- use 15 second timeout for requests
- correctly calculate ur for replays using mouse clicks (as opposed to keyboard clicks).
- rename
ReplayStealingResult
toStealResult
(for consistency withResult
names matching their respectiveDetect
names).ReplayStealingResult
left available as deprecated. - mods can now be instantiated through
Mod
. Instantiation throughModCombination
is highly discouraged. - mods can now be instantiated with a string, as well as an int. The string must be a combination, in any order, of the two length strings that represent mods. For instance,
Mod("HDHR")
is valid, andMod("HDHR") == Mod.HDHR
isTrue
. - use
np.interp
instead of a homebrew interpolation.- changes similarity values slightly, both increasing and decreasing simvals for legit and stolen replays.
- no measurable impact on the effectiveness of circlecore to detect stolen replays.
- increases comparison speed.
- use only unique timestamps when interpolating (also changes similarity values slightly)
- fix
MapUser
inheritence (now properly inherits fromReplayContainer
) - fix incorrect
__add__
method forCheck
- rename
Keys
toKey
(Keys
left available as deprecated) - don't double load
ReplayInfo
when usingMap
- fix mod subtraction not being commutative (eg
Mod.HDHR - Mod.HR
has a different meaning fromMod.HR - Mod.HDHR
) - store beatmap hash in
ReplayPath
- properly check response for
map_id
,user_id
, andusername
functions - enforce ratelimit to all Loader functions
- don't reraise
InvalidKeyException
as aCircleguardException
- add CHANGELOG file to both track unreleased changes and past changes
- various documentation links and wording fixes
- add aim correction detection
- new forward-facing documentation built with sphinx, including a comprehensive introduction on how to use circlecore (https://docs.circleguard.dev/)
- new
User
class which represents a user's top plays - new
MapUser
class which represents all of a user's plays on a map - new
Mod
andModCombination
class which represent mods - restructure
replay.py
and inheritance ofloadables
.Container
is (roughly) replaced withInfoLoadable
, andCheck
is the only entry point forDetect
. - major
Detect
restructure; split into subclasses per cheat type. ADetect
is instantiated with its respective thresholds(steal, ur, etc) instead of thresholds being global settings Map
andUser
are now iterable and indexable, referencingReplay
s in theMap
orUser
- rewrite and update all internal documentation
Circleguard
now takes acache
argument, which can make the database effectively read-only
- switch documentation style from google to numpy (https://numpydoc.readthedocs.io/en/latest/format.html#docstring-standard)
- new method
cg.load_info
that loads the info forReplayContainer
s - global settings almost entirely removed, save for
loglevel
- instance settings almost entirely removed, save for
cache
- rename
replay.py
toloadable.py
- rename
UserInfo
toReplayInfo
Options
class removed- add a
loader#username
function which retrieves a username from a user id. See also ppy/osu-api#281 - add an
lru_cache
toloader#map_id
,#user_id
, and#username
functions loader#get_user_best
now returns a list ofUserInfo
, and accepts amods
argument.cg.run
now only accepts aCheck
- replace int mods with
ModCombination
in most places - add ScoreV2 mod. Fixes not being able to process ScoreV2 replays.
- create a slider
Library
every time#run
is called ifslider_dir
is not passed. FixesPermissionError
s on windows. - remove convenience methods (
user_check
,map_check
, etc). These have been replaced byMap
andUser
(new) - update STYLE document
- all Circleguard instances now use the same logger
filter
argument removed throughout the codebaseRatelimitWeight
andResultType
enum string values capitalizedResultType.AIM_CORRECTION
renamed toCORRECTION
#ur
is now a staticmethod inInvestigator
- new Map class for conveniently specifying a range of replays on a map that can be ran directly with cg.run()
- new span argument to loadables and map_check and user_check which specify exactly which of the top replays to check
- restructure of Replays and Checks. Both now inherit from Loadable, and Check and Map inherit from Container. Containers can hold other Containers, to an arbitrary depth. cg.run() now accepts any Container.
- use Slider to download beatmaps for relax detection
- fix user_check not using the same args as create_user_check
- check.filter() now requires a Loader
- add test cases for different replay types
- REPLAY_STEALING and REMODDING ResultType renamed to STEAL and REMOD respectively
- optimize ur calculation
- fix RelaxResult returning timestamped data in
result.replay
instead of the replay - keys enum is now an IntFlag instead of an Enum
- update test cases for cookiezi's new name (chocomint)
- comparer decides mode on its own and does not need a mode in Comparer#compare
- clean up ColoredFormatter code
- fix error when running local check with both u and map id
- throw NoInfoAvailableException on empty api response
- cg.load now accepts either a Check object or a Replay object. Passing a check will result in all replays stored in the check being loaded.
- cg.load no longer requires a Check to load a replay.
- settings overhaul - settings now cascade properly and at different times than before.
- test suite added (not covering everything, yet)
- fix error when setting an Option class value (infinite recursion)
- add relax cheat detection (and consequently UR calculation)
- allow circleguard to be used without a database
- add mods argument to map_check
- add Detect settings to global/cg/check/replay
- retry requests if JSONDecodeError response is returned by ossapi
- avoids fatal error while replay loading if api returns invalid response
- fix
pip install circleguard
failing if requirements were not installed - minor readme example updates
- remove load progress tracking from Loader
- return custom response for JSONDecodeError when api returns invalid json
- replay_id now parses to an int instead of a tuple
- add https://github.com/osufx/osu-parser files for more complicated parsing
- switch license to GPL3 to comply with osu-parser license
- fix convenience options not having effect when passing falsy values
- load map id and user id for local osrs
- fix fatal error when ratelimit is barely hit and proceeded by light api calls
- require map_id, user_id, and timestamp in Replay
- provide earlier_replay and later_replay in Result class that reference either replay1 or replay2 depending on timestamp order (and remove later_name) (#78)
- fix false positive when the user being checked was on the map leaderboard being checked with map check
- fix false positive with user screen when user was on leaderboard of their top plays
- fix error when trying to load only a single replay from a map
- differentiate loggers between circleguard instances
- add missing cache option to convenience methods
- add map and user options to create_local_check
This release splits Circleguard
into circlecore
(pip module) and circleguard
(gui with pyqt
as the frontend, using circlecore
as the backend).
Changes:
- replays can be loaded from arbitrary locations (db, osu website, mirror website, osr file)
- convenience methods for common use cases added (checking a map or user)
- support for comparing two arbitrary replays (from two different maps, if you so choose)
- logging overhaul, any important action is logged
- major code cleanup
- removal of command line interface
- code standardized for pip upload (
setup.py
,__init__.py
, etc)
- fix non-osr files being loaded as osr
- fix replay being compared against itself with -m -u
- fix fatal error when using both -l and -m flags
- new profile screener that checks a user's top n plays for replay stealing and remodding when -u flag only is passed
- ability to restrict what replays are downloaded (and compared) with the --mods flag
- major internal cleanup with the addition of the user_info class
- fix names starting with an underscore not being displayed on graphs
- don't fatal error when rng seed is not present when we expect it to be
- fix wrong name to int mappings
- add scorev2 (fixes fatal error on attempting to parse a replay with the scorev2 mod)
- filter out None values, not None keys, in kwargs parameters
- now detect steals which either have hr added or hr removed from the replay it was stolen from
- print progress every 10% when comparing replays
- print loading progress every time there is a pause for ratelimits while loading beatmaps
- move api wrapper to separate repo; formalize api calls
- catch and retry Request related exceptions
- potentially fix matplotlib printing "invalid command name" (#43)
- fix error when redownloading outdated replays
- parse rng seed value from last frame of lzma (previously, nonsensical values such as -12345|0|0|10186099 were stored in the replay data)
- move api wrapper to separate repo
- new --verify flag designed for staff use that checks if replays by two users on a given map are copies
- add --version flag that prints program version
- program renamed to circleguard (thanks to InvisibleSymbol for the name)
- print usernames instead of user ids for OnlineReplay comparisons
- use a single replay folder for local comparisons instead of two
- change default threshold to 18
- highlight the later replay instead of the first replay in printout
- remove --single flag (this is now default behavior when -l is set)
- load local replays per circleguard instance (fixes incosistent gui behavior)
- handle "Replay retrieval failed." api response
- fix None replays being compared after handling api error response
- force gui comparisons to not visualize replays (avoid multithreading crashes)
- raise properly sublclassed exceptions instead of base Exception
- only revalidate users that are actually stored in local cache
- properly compress replays that use smoke key (see v1.1.1 wtc-lzma-compression)
- treat z stream as a signed byte instead of unsigned
- original release