From 726eb193f9ac1b47470881970d22b0dbf2dcaa86 Mon Sep 17 00:00:00 2001 From: tybug Date: Fri, 1 Feb 2019 14:36:51 -0500 Subject: [PATCH 01/29] implement --verify flag --- README.md | 2 +- osu-ac/anticheat.py | 20 ++++++++++++++++++-- osu-ac/argparser.py | 2 ++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 61ce86f3..4fb4782a 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ For the former, run the anticheat.py file with some or all of the following flag | -c, --cache | if set, locally caches replays so they don't have to be redownloaded when checking the same map multiple times | | --single | compare all replays under user/ with all other replays under user/. No effect if not set with -l | | -s, --silent | if set, you will not be prompted for a visualization of comparisons under the threshold. Results will still be printed | - +| -v, --verify | Takes 3 positional arguments - map id, user1 id and user2 id. Verifies that the scores are steals of each other | #### Some Examples diff --git a/osu-ac/anticheat.py b/osu-ac/anticheat.py index 0d8ee723..698e1259 100644 --- a/osu-ac/anticheat.py +++ b/osu-ac/anticheat.py @@ -43,8 +43,9 @@ def run(self): """ Starts loading and detecting replays based on the args passed through the command line. """ - - if(self.args.local): + if(self.args.verify): + self._run_verify() + elif(self.args.local): self._run_local() elif(self.args.map_id): self._run_map() @@ -52,6 +53,21 @@ def run(self): print("Please set either --local (-l) or --map (-m)! ") sys.exit(1) + def _run_verify(self): + args = self.args + + map_id = self.args.verify[0] + user1_id = self.args.verify[1] + user2_id = self.args.verify[2] + + user1_info = Loader.user_info(map_id, user1_id) + user2_info = Loader.user_info(map_id, user2_id) + replay1 = OnlineReplay.from_user_info(self.cacher, map_id, user1_info) + replay2 = OnlineReplay.from_user_info(self.cacher, map_id, user2_info) + + comparer = Comparer(args.threshold, args.silent, replay1, replays2=replay2) + comparer.compare(mode="double") + def _run_local(self): args = self.args diff --git a/osu-ac/argparser.py b/osu-ac/argparser.py index 6d21b0be..c7ee6d76 100644 --- a/osu-ac/argparser.py +++ b/osu-ac/argparser.py @@ -26,3 +26,5 @@ argparser.add_argument("-s", "--silent", help="If set, you will not be prompted for a visualization of comparisons under the threshold", action="store_true") + +argparser.add_argument("-v", "--verify", help="Takes 3 positional arguments - map id, user1 id and user2 id. Verifies that the scores are steals of each other", nargs=3) From 7e3f60ecc4e10038be855132ad2c43a57d6841d2 Mon Sep 17 00:00:00 2001 From: tybug Date: Fri, 1 Feb 2019 15:10:14 -0500 Subject: [PATCH 02/29] catch retrieval failed api response, refactor enum checks --- osu-ac/enums.py | 7 ++++--- osu-ac/loader.py | 12 +++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/osu-ac/enums.py b/osu-ac/enums.py index a777aec5..3102f296 100644 --- a/osu-ac/enums.py +++ b/osu-ac/enums.py @@ -2,9 +2,10 @@ # strings taken from osu api error responses class Error(Enum): - NO_REPLAY = "Replay not available." - RATELIMITED = "Requesting too fast! Slow your operation, cap'n!" - UNKOWN = "Unkown error." + NO_REPLAY = "Replay not available." + RATELIMITED = "Requesting too fast! Slow your operation, cap'n!" + RETRIEVAL_FAILED = "Replay retrieval failed." + UNKOWN = "Unkown error." class Mod(Enum): NoMod = 0 diff --git a/osu-ac/loader.py b/osu-ac/loader.py index 514a8670..edec2864 100644 --- a/osu-ac/loader.py +++ b/osu-ac/loader.py @@ -108,7 +108,10 @@ def replay_data(map_id, user_id): error = Loader.check_response(response) if(error == Error.NO_REPLAY): - print("Could not find any replay data for user {} on map {}".format(user_id, map_id)) + print("Could not find any replay data for user {} on map {}, skipping".format(user_id, map_id)) + return None + elif(error == Error.RETRIEVAL_FAILED): + print("Replay retrieval failed for user {} on map {}, skipping".format(user_id, map_id)) return None elif(error == Error.RATELIMITED): Loader.enforce_ratelimit() @@ -133,10 +136,9 @@ def check_response(response): """ if("error" in response): - if(response["error"] == Error.RATELIMITED.value): - return Error.RATELIMITED - elif(response["error"] == Error.NO_REPLAY.value): - return Error.NO_REPLAY + for error in Error: + if(response["error"] == error.value): + return error else: return Error.UNKOWN else: From a866127011d61269b60e0fd5ea0e9aac695f5464 Mon Sep 17 00:00:00 2001 From: tybug Date: Fri, 1 Feb 2019 15:28:20 -0500 Subject: [PATCH 03/29] move replay filtering to comparer. Fixes #29 --- osu-ac/comparer.py | 6 +++--- osu-ac/online_replay.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/osu-ac/comparer.py b/osu-ac/comparer.py index b7b8608f..b518a6d0 100644 --- a/osu-ac/comparer.py +++ b/osu-ac/comparer.py @@ -42,9 +42,9 @@ def __init__(self, threshold, silent, replays1, replays2=None, stddevs=None): self.stddevs = stddevs self.silent = silent - self.replays1 = replays1 - self.replays2 = replays2 - + # filter beatmaps we had no data for - see Loader.replay_data and OnlineReplay.from_map + self.replays1 = [replay for replay in replays1 if replay is not None] + self.replays2 = [replay for replay in replays2 if replay is not None] def compare(self, mode): """ diff --git a/osu-ac/online_replay.py b/osu-ac/online_replay.py index 217a8f3d..15374194 100644 --- a/osu-ac/online_replay.py +++ b/osu-ac/online_replay.py @@ -66,7 +66,6 @@ def from_user_info(cacher, map_id, user_info): """ replays = [OnlineReplay.from_map(cacher, map_id, user_id, replay_info[0], replay_info[1]) for user_id, replay_info in user_info.items()] - replays = [replay for replay in replays if replay is not None] # filter beatmaps we had no data for - see Loader.replay_data and OnlineReplay.from_map return replays @staticmethod From 78107f2dc42fee047136282d8c84cf15dbdb5749 Mon Sep 17 00:00:00 2001 From: tybug Date: Fri, 1 Feb 2019 16:08:03 -0500 Subject: [PATCH 04/29] highlight last replay not first --- osu-ac/comparer.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu-ac/comparer.py b/osu-ac/comparer.py index b518a6d0..2086799d 100644 --- a/osu-ac/comparer.py +++ b/osu-ac/comparer.py @@ -44,7 +44,9 @@ def __init__(self, threshold, silent, replays1, replays2=None, stddevs=None): # filter beatmaps we had no data for - see Loader.replay_data and OnlineReplay.from_map self.replays1 = [replay for replay in replays1 if replay is not None] - self.replays2 = [replay for replay in replays2 if replay is not None] + + if(replays2): + self.replays2 = [replay for replay in replays2 if replay is not None] def compare(self, mode): """ @@ -126,13 +128,13 @@ def _print_result(self, result, replay1, replay2): if(mean > self.threshold): return - # if they were both set online, we don't get dates from - first_score = None + # if they were both set locally, we don't get replay ids to compare + last_score = None if(replay1.replay_id and replay2.replay_id): - first_score = replay1.player_name if(replay1.replay_id < replay2.replay_id) else replay2.player_name + last_score = replay1.player_name if(replay1.replay_id > replay2.replay_id) else replay2.player_name print("{:.1f} similarity, {:.1f} std deviation ({} vs {}{})" - .format(mean, sigma, replay1.player_name, replay2.player_name, " - {} set first".format(first_score) if first_score else "")) + .format(mean, sigma, replay1.player_name, replay2.player_name, " - {} set later".format(last_score) if last_score else "")) if(self.silent): return From 76d5843f78d21ad0c0f40e83a0d9a76c644a6ef2 Mon Sep 17 00:00:00 2001 From: tybug Date: Fri, 1 Feb 2019 16:08:20 -0500 Subject: [PATCH 05/29] print username for online replays instead of id --- osu-ac/anticheat.py | 4 ++-- osu-ac/cacher.py | 5 +++-- osu-ac/loader.py | 12 +++++++----- osu-ac/online_replay.py | 8 ++++---- 4 files changed, 16 insertions(+), 13 deletions(-) diff --git a/osu-ac/anticheat.py b/osu-ac/anticheat.py index 698e1259..27740aa1 100644 --- a/osu-ac/anticheat.py +++ b/osu-ac/anticheat.py @@ -36,8 +36,8 @@ def __init__(self, args): if(args.map_id): self.users_info = Loader.users_info(args.map_id, args.number) if(args.user_id and args.map_id): - user_info = Loader.user_info(args.map_id, args.user_id) - self.replays_check = [OnlineReplay.from_map(self.cacher, args.map_id, args.user_id, user_info[args.user_id][0], user_info[args.user_id][1])] + user_info = Loader.user_info(args.map_id, args.user_id)[args.user_id] # should be guaranteed to only be a single mapping of user_id to a list + self.replays_check = [OnlineReplay.from_map(self.cacher, args.map_id, args.user_id, user_info[0], user_info[1], user_info[2])] def run(self): """ diff --git a/osu-ac/cacher.py b/osu-ac/cacher.py index a58641e9..92642eb0 100644 --- a/osu-ac/cacher.py +++ b/osu-ac/cacher.py @@ -58,12 +58,13 @@ def revalidate(self, map_id, user_to_replay): Args: String map_id: The map to revalidate. - Dictionary user_to_replay: The up tp date mapping of user_id to replay_id to revalidate. + Dictionary user_to_replay: The up to date mapping of user_id to [username, replay_id, enabled_mods] to revalidate. + Only contains information for a single map. """ result = self.cursor.execute("SELECT user_id, replay_id FROM replays WHERE map_id=?", [map_id]).fetchall() for user_id, local_replay_id in result: - online_replay_id = user_to_replay[user_id] + online_replay_id = user_to_replay[user_id][1] if(local_replay_id != online_replay_id): # local (outdated) id does not match online (updated) id print("replay outdated, redownloading...", end="") # this **could** conceivable be the source of a logic error by Loader.replay_data returning None and the cache storing None, diff --git a/osu-ac/loader.py b/osu-ac/loader.py index edec2864..b735f144 100644 --- a/osu-ac/loader.py +++ b/osu-ac/loader.py @@ -49,9 +49,10 @@ def __init__(self): @api def users_info(map_id, num=50): """ - Returns a dict mapping the user_id to a list containing their replay_id and the enabled mods for the top given number of replays. + Returns a dict mapping each user_id to a list containing [username, replay_id, enabled mods] + for the top given number of replays on the given map. - EX: {"1234567": ["295871732", "15"]} # numbers may not be accurate to true mod bits or user ids + EX: {"1234567": ["tybug", "295871732", 15]} # numbers may not be accurate to true mod bits or user ids Args: String map_id: The map id to get a list of users from. @@ -65,14 +66,14 @@ def users_info(map_id, num=50): Loader.enforce_ratelimit() return Loader.users_info(map_id, num=num) - info = {x["user_id"]: [x["score_id"], int(x["enabled_mods"])] for x in response} # map user id to score id and mod bit + info = {x["user_id"]: [x["username"], x["score_id"], int(x["enabled_mods"])] for x in response} # map user id to username, score id and mod bit return info @staticmethod @api def user_info(map_id, user_id): """ - Returns a dict mapping a user_id to a list containing their replay_id and the enabled mods on a given map. + Returns a dict mapping a user_id to a list containing their [username, replay_id, enabled mods] on a given map. Args: String map_id: The map id to get the replay_id from. @@ -83,7 +84,8 @@ def user_info(map_id, user_id): if(Loader.check_response(response)): Loader.enforce_ratelimit() return Loader.user_info(map_id, user_id) - info = {x["user_id"]: [x["score_id"], int(x["enabled_mods"])] for x in response} # map user id to score id and mod bit, should only be one response + info = {x["user_id"]: [x["username"], x["score_id"], int(x["enabled_mods"])] for x in response} # map user id to username, score id and mod bit, + # should only be one response return info @staticmethod diff --git a/osu-ac/online_replay.py b/osu-ac/online_replay.py index 15374194..92096df7 100644 --- a/osu-ac/online_replay.py +++ b/osu-ac/online_replay.py @@ -58,19 +58,19 @@ def from_user_info(cacher, map_id, user_info): Args: Cacher cacher: A cacher object containing a database connection. String map_id: The map_id to download the replays from. - Dictionary user_info: A dict mapping a user_id to a list containing their replay_id and the enabled mods on a given map. + Dictionary user_info: A dict mapping user_ids to a list containing [username, replay_id, enabled mods] on the given map. See Loader.users_info Returns: A list of Replay instances from the given information, with entries with no replay data available excluded. """ - replays = [OnlineReplay.from_map(cacher, map_id, user_id, replay_info[0], replay_info[1]) for user_id, replay_info in user_info.items()] + replays = [OnlineReplay.from_map(cacher, map_id, user_id, replay_info[0], replay_info[1], replay_info[2]) for user_id, replay_info in user_info.items()] return replays @staticmethod @check_cache - def from_map(cacher, map_id, user_id, replay_id, enabled_mods): + def from_map(cacher, map_id, user_id, username, replay_id, enabled_mods): """ Creates a Replay instance from a replay by the given user on the given map. @@ -92,4 +92,4 @@ def from_map(cacher, map_id, user_id, replay_id, enabled_mods): parsed_replay = osrparse.parse_replay(lzma_bytes, pure_lzma=True) replay_data = parsed_replay.play_data cacher.cache(map_id, user_id, lzma_bytes, replay_id) - return OnlineReplay(replay_data, user_id, enabled_mods, replay_id) + return OnlineReplay(replay_data, username, enabled_mods, replay_id) From b6d01bed0d0b11a1c3035428cb2e75c5d4dd53f8 Mon Sep 17 00:00:00 2001 From: tybug Date: Fri, 1 Feb 2019 17:34:18 -0500 Subject: [PATCH 06/29] don't compare when iterator has no elements. Fixes #36 --- osu-ac/comparer.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu-ac/comparer.py b/osu-ac/comparer.py index b518a6d0..6b30202c 100644 --- a/osu-ac/comparer.py +++ b/osu-ac/comparer.py @@ -56,6 +56,10 @@ def compare(self, mode): String mode: One of either "double" or "single", determining how to choose which replays to compare. """ + if(not self.replays1): # if this is empty, bad things + print("No comparisons could be made. Make sure replay data is available for your args") + return + if(mode == "double"): print("comparing first set of replays to second set of replays") iterator = itertools.product(self.replays1, self.replays2) @@ -66,11 +70,11 @@ def compare(self, mode): raise Exception("`mode` must be one of 'double' or 'single'") - # automatically determine threshold based on standard deviations of similarities if stddevs is set if(self.stddevs): results = {} for replay1, replay2 in iterator: + print("loop") if(self.check_names(replay1.player_name, replay2.player_name)): continue result = Comparer._compare_two_replays(replay1, replay2) From 38ae8845a869fcc5c00afdc473c3d441c86c06dc Mon Sep 17 00:00:00 2001 From: tybug Date: Fri, 1 Feb 2019 19:34:50 -0500 Subject: [PATCH 07/29] remove debug statement --- osu-ac/comparer.py | 1 - 1 file changed, 1 deletion(-) diff --git a/osu-ac/comparer.py b/osu-ac/comparer.py index adbf5e64..44d42268 100644 --- a/osu-ac/comparer.py +++ b/osu-ac/comparer.py @@ -76,7 +76,6 @@ def compare(self, mode): if(self.stddevs): results = {} for replay1, replay2 in iterator: - print("loop") if(self.check_names(replay1.player_name, replay2.player_name)): continue result = Comparer._compare_two_replays(replay1, replay2) From 239be6ebbe827c3a28815b1040b3f043cb76de99 Mon Sep 17 00:00:00 2001 From: tybug Date: Fri, 1 Feb 2019 20:46:31 -0500 Subject: [PATCH 08/29] fix --single flag --- osu-ac/anticheat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-ac/anticheat.py b/osu-ac/anticheat.py index 27740aa1..df99fab2 100644 --- a/osu-ac/anticheat.py +++ b/osu-ac/anticheat.py @@ -91,7 +91,7 @@ def _run_local(self): if(args.single): # checks every replay listed in PATH_REPLAYS_USER against every other replay there - comparer = Comparer(threshold, stddevs, args.silent, replays1) + comparer = Comparer(threshold, args.silent, replays1) comparer.compare(mode="single") return else: From 486d1b7b5613933e1eb20496952306389043c76c Mon Sep 17 00:00:00 2001 From: tybug Date: Fri, 1 Feb 2019 21:26:19 -0500 Subject: [PATCH 09/29] make --single default behavior for -l, force --silent on gui --- README.md | 3 +-- osu-ac/anticheat.py | 24 +++++++--------------- osu-ac/argparser.py | 9 +++----- osu-ac/comparer.py | 9 ++++---- osu-ac/config.py | 10 ++------- osu-ac/gui.py | 10 ++++----- osu-ac/replays/{compare => }/cheater.osr | Bin osu-ac/replays/{compare => }/elnabhan.osr | Bin osu-ac/replays/{user => }/woey.osr | Bin 9 files changed, 21 insertions(+), 44 deletions(-) rename osu-ac/replays/{compare => }/cheater.osr (100%) rename osu-ac/replays/{compare => }/elnabhan.osr (100%) rename osu-ac/replays/{user => }/woey.osr (100%) diff --git a/README.md b/README.md index 4fb4782a..3c2cbe3a 100644 --- a/README.md +++ b/README.md @@ -35,12 +35,11 @@ For the former, run the anticheat.py file with some or all of the following flag | -h, --help | displays the messages below | | -m, --map | checks the leaderboard on the given beatmap id against each other | | -u, --user | checks only the given user against the other leaderboard replays. Must be set with -m | -| -l, --local | compare scores under the user/ directory to a beatmap leaderboard (if set with just -m), a score set by a user on a beatmap (if set with -m and -u) or other locally saved replays (default behavior) | +| -l, --local | compare scores under the replays/ directory to a beatmap leaderboard (if set with -m), a score set by a user on a beatmap (if set with -m and -u) or the other scores in the folder (default behavior) | | -t, --threshold | sets the similarity threshold to print comparisons that score under it. Defaults to 20 | | -a, --auto-threshold | sets the number of standard deviations from the average similarity the threshold will automatically be set to. Overrides -t **Note: If more than ![formula](https://latex.codecogs.com/gif.latex?\frac{1}{2}&space;-&space;\frac{1}{2}&space;\mathbf{erf}\frac{a}{\sqrt{2}}) of the input is stolen this may cause false negatives** | | -n, --number | how many replays to get from a beatmap. No effect if not set with -m. Defaults to 50. **Note: the time complexity of the comparisons scales with O(n^2)** | | -c, --cache | if set, locally caches replays so they don't have to be redownloaded when checking the same map multiple times | -| --single | compare all replays under user/ with all other replays under user/. No effect if not set with -l | | -s, --silent | if set, you will not be prompted for a visualization of comparisons under the threshold. Results will still be printed | | -v, --verify | Takes 3 positional arguments - map id, user1 id and user2 id. Verifies that the scores are steals of each other | diff --git a/osu-ac/anticheat.py b/osu-ac/anticheat.py index df99fab2..28604599 100644 --- a/osu-ac/anticheat.py +++ b/osu-ac/anticheat.py @@ -18,7 +18,7 @@ from comparer import Comparer from investigator import Investigator from cacher import Cacher -from config import PATH_REPLAYS_USER, PATH_REPLAYS_CHECK, WHITELIST +from config import PATH_REPLAYS, WHITELIST class Anticheat: @@ -50,8 +50,7 @@ def run(self): elif(self.args.map_id): self._run_map() else: - print("Please set either --local (-l) or --map (-m)! ") - sys.exit(1) + print("Please set either --local (-l), --map (-m), or --verify (-v)! ") def _run_verify(self): args = self.args @@ -65,14 +64,14 @@ def _run_verify(self): replay1 = OnlineReplay.from_user_info(self.cacher, map_id, user1_info) replay2 = OnlineReplay.from_user_info(self.cacher, map_id, user2_info) - comparer = Comparer(args.threshold, args.silent, replay1, replays2=replay2) + comparer = Comparer(args.threshold, args.silent, replay1, replays2=replay2, stddevs=args.stddevs) comparer.compare(mode="double") def _run_local(self): args = self.args - # get all local user replays (used in every --local case) - replays1 = [LocalReplay.from_path(osr_path) for osr_path in PATH_REPLAYS_USER] + # get all local replays (used in every --local case) + replays1 = [LocalReplay.from_path(osr_path) for osr_path in PATH_REPLAYS] threshold = args.threshold stddevs = args.stddevs @@ -88,18 +87,9 @@ def _run_local(self): comparer = Comparer(threshold, args.silent, replays1, replays2=replays2, stddevs=stddevs) comparer.compare(mode="double") return - - if(args.single): - # checks every replay listed in PATH_REPLAYS_USER against every other replay there - comparer = Comparer(threshold, args.silent, replays1) - comparer.compare(mode="single") - return else: - # checks every replay listed in PATH_REPLAYS_USER against every replay listed in PATH_REPLAYS_CHECK - replays2 = [LocalReplay.from_path(osr_path) for osr_path in PATH_REPLAYS_CHECK] - comparer = Comparer(threshold, args.silent, replays1, replays2=replays2, stddevs=stddevs) - comparer.compare(mode="double") - return + comparer = Comparer(threshold, args.silent, replays1, stddevs=stddevs) + comparer.compare(mode="single") def _run_map(self): diff --git a/osu-ac/argparser.py b/osu-ac/argparser.py index c7ee6d76..a2e31098 100644 --- a/osu-ac/argparser.py +++ b/osu-ac/argparser.py @@ -7,9 +7,9 @@ argparser.add_argument("-u", "--user", dest="user_id", help="checks only the given user against the other leaderboard replays. Must be set with -m") -argparser.add_argument("-l", "--local", help=("compare scores under the user/ directory to a beatmap leaderboard (if set with -m), " - "a score set by a user on a beatmap (if set with -m and -u) or other locally " - "saved replays (default behavior)"), action="store_true") +argparser.add_argument("-l", "--local", help=("compare scores under the replays/ directory to a beatmap leaderboard (if set with -m), " + "a score set by a user on a beatmap (if set with -m and -u) or the other scores in the folder " + "(default behavior)"), action="store_true") argparser.add_argument("-t", "--threshold", help="sets the similarity threshold to print results that score under it. Defaults to 20", type=int, default=20) @@ -21,9 +21,6 @@ argparser.add_argument("-c", "--cache", help="If set, locally caches replays so they don't have to be redownloaded when checking the same map multiple times.", action="store_true") -argparser.add_argument("--single", help="Compare all replays under user/ with all other replays under user/. No effect if not set with -l", - action="store_true") - argparser.add_argument("-s", "--silent", help="If set, you will not be prompted for a visualization of comparisons under the threshold", action="store_true") diff --git a/osu-ac/comparer.py b/osu-ac/comparer.py index 44d42268..69925a18 100644 --- a/osu-ac/comparer.py +++ b/osu-ac/comparer.py @@ -63,15 +63,13 @@ def compare(self, mode): return if(mode == "double"): - print("comparing first set of replays to second set of replays") iterator = itertools.product(self.replays1, self.replays2) elif (mode == "single"): - print("comparing first set of replays to itself") iterator = itertools.combinations(self.replays1, 2) else: raise Exception("`mode` must be one of 'double' or 'single'") - + print("Starting to compare replays") # automatically determine threshold based on standard deviations of similarities if stddevs is set if(self.stddevs): results = {} @@ -141,8 +139,9 @@ def _print_result(self, result, replay1, replay2): if(self.silent): return - - answer = input("Would you like to see a visualization of both replays? ") + import sys + print("Would you like to see a visualization of both replays?") + answer = sys.stdin.readline() if (answer and answer[0].lower().strip() == "y"): draw = Draw(replay1, replay2) animation = draw.run() diff --git a/osu-ac/config.py b/osu-ac/config.py index d48919aa..4b2f54e3 100644 --- a/osu-ac/config.py +++ b/osu-ac/config.py @@ -5,16 +5,10 @@ from secret import API_KEY PATH_ROOT = pathlib.Path(__file__).parent -PATH_REPLAYS = PATH_ROOT / "replays" +PATH_REPLAYS_STUB = PATH_ROOT / "replays" -# names of replays to check -PATH_REPLAYS_USER_STUB = join(PATH_REPLAYS, "user") -PATH_REPLAYS_CHECK_STUB = join(PATH_REPLAYS, "compare") # path of replays to check against - -PATH_REPLAYS_USER = [join(PATH_REPLAYS_USER_STUB, f) for f in os.listdir(PATH_REPLAYS_USER_STUB) if isfile(join(PATH_REPLAYS_USER_STUB, f)) and f != ".DS_Store"] # get all replays in path to check against -PATH_REPLAYS_CHECK = [join(PATH_REPLAYS_CHECK_STUB, f) for f in os.listdir(PATH_REPLAYS_CHECK_STUB) if isfile(join(PATH_REPLAYS_CHECK_STUB, f)) and f != ".DS_Store"] - +PATH_REPLAYS = [join(PATH_REPLAYS_STUB, f) for f in os.listdir(PATH_REPLAYS_STUB) if isfile(join(PATH_REPLAYS_STUB, f)) and f != ".DS_Store"] API_BASE = "https://osu.ppy.sh/api/" API_REPLAY = API_BASE + "get_replay?k=" + API_KEY + "&m=0&b={}&u={}" diff --git a/osu-ac/gui.py b/osu-ac/gui.py index d8313cb0..5b1de9f0 100644 --- a/osu-ac/gui.py +++ b/osu-ac/gui.py @@ -19,13 +19,12 @@ def run(): _number = num.get() _cache = cache.get() - _single = single.get() - _silent = silent.get() - + _silent = True # Visualizations do very very bad things when not called from the main thread, so when using gui, we just...force ignore them + _verify = verify.get() def run_anticheat(): anticheat = Anticheat(SimpleNamespace(map_id=_map_id, user_id=_user_id, local=_local, threshold=_threshold, stddevs=_stddevs, - number=_number, cache=_cache, single=_single, silent=_silent)) + number=_number, cache=_cache, silent=_silent, verify=_verify)) anticheat.run() thread = threading.Thread(target=run_anticheat) @@ -56,8 +55,7 @@ def run_anticheat(): cache = tkinter.BooleanVar(value=False) # unimplemented -single = tkinter.BooleanVar(value=False) -silent = tkinter.BooleanVar(value=False) +verify = tkinter.BooleanVar(value=False) # Make visual elements for main frame map_label = ttk.Label(main, text="Map id:") diff --git a/osu-ac/replays/compare/cheater.osr b/osu-ac/replays/cheater.osr similarity index 100% rename from osu-ac/replays/compare/cheater.osr rename to osu-ac/replays/cheater.osr diff --git a/osu-ac/replays/compare/elnabhan.osr b/osu-ac/replays/elnabhan.osr similarity index 100% rename from osu-ac/replays/compare/elnabhan.osr rename to osu-ac/replays/elnabhan.osr diff --git a/osu-ac/replays/user/woey.osr b/osu-ac/replays/woey.osr similarity index 100% rename from osu-ac/replays/user/woey.osr rename to osu-ac/replays/woey.osr From 117575286492893e50d095d6a8a08866ad1180ff Mon Sep 17 00:00:00 2001 From: tybug Date: Fri, 1 Feb 2019 22:46:45 -0500 Subject: [PATCH 10/29] require -n to be at least 2 --- osu-ac/argparser.py | 2 +- osu-ac/loader.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu-ac/argparser.py b/osu-ac/argparser.py index a2e31098..67b63394 100644 --- a/osu-ac/argparser.py +++ b/osu-ac/argparser.py @@ -15,7 +15,7 @@ argparser.add_argument("-a", "--auto", help="Sets the threshold to a number of standard deviations below the average similarity", type=float, dest="stddevs") -argparser.add_argument("-n", "--number", help="how many replays to get from a beatmap. No effect if not set with -m. Must be between 1 and 100 inclusive," +argparser.add_argument("-n", "--number", help="how many replays to get from a beatmap. No effect if not set with -m. Must be between 2 and 100 inclusive," "defaults to 50. NOTE: THE TIME COMPLEXITY OF THE COMPARISONS WILL SCALE WITH O(n^2).", type=int, default=50) argparser.add_argument("-c", "--cache", help="If set, locally caches replays so they don't have to be redownloaded when checking the same map multiple times.", diff --git a/osu-ac/loader.py b/osu-ac/loader.py index b735f144..5409e02b 100644 --- a/osu-ac/loader.py +++ b/osu-ac/loader.py @@ -59,7 +59,7 @@ def users_info(map_id, num=50): Integer num: The number of ids to fetch. Defaults to 50. """ - if(num > 100 or num < 1): + if(num > 100 or num < 2): raise Exception("The number of top plays to fetch must be between 1 and 100 inclusive!") response = requests.get(API_SCORES_ALL.format(map_id, num)).json() if(Loader.check_response(response)): From e99d09df1dfd1d5aff547d41d86d8974945f01b3 Mon Sep 17 00:00:00 2001 From: tybug Date: Sat, 2 Feb 2019 00:22:17 -0500 Subject: [PATCH 11/29] remove debugging code --- osu-ac/comparer.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/osu-ac/comparer.py b/osu-ac/comparer.py index 69925a18..2299a461 100644 --- a/osu-ac/comparer.py +++ b/osu-ac/comparer.py @@ -139,9 +139,8 @@ def _print_result(self, result, replay1, replay2): if(self.silent): return - import sys - print("Would you like to see a visualization of both replays?") - answer = sys.stdin.readline() + + answer = input("Would you like to see a visualization of both replays? ") if (answer and answer[0].lower().strip() == "y"): draw = Draw(replay1, replay2) animation = draw.run() From 6c5a87c476cd4daf7a3db7530a97177602b0ac51 Mon Sep 17 00:00:00 2001 From: tybug Date: Sat, 2 Feb 2019 00:25:59 -0500 Subject: [PATCH 12/29] add --version flag --- osu-ac/anticheat.py | 8 ++++++-- osu-ac/argparser.py | 2 ++ osu-ac/config.py | 2 ++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu-ac/anticheat.py b/osu-ac/anticheat.py index 28604599..57a56069 100644 --- a/osu-ac/anticheat.py +++ b/osu-ac/anticheat.py @@ -18,7 +18,7 @@ from comparer import Comparer from investigator import Investigator from cacher import Cacher -from config import PATH_REPLAYS, WHITELIST +from config import PATH_REPLAYS, WHITELIST, VERSION class Anticheat: @@ -115,5 +115,9 @@ def _run_map(self): return if __name__ == '__main__': - anticheat = Anticheat(argparser.parse_args()) + args = argparser.parse_args() + if(args.version): + print("osu!anticheat {}".format(VERSION)) + sys.exit(0) + anticheat = Anticheat(args) anticheat.run() diff --git a/osu-ac/argparser.py b/osu-ac/argparser.py index 67b63394..5ec8912f 100644 --- a/osu-ac/argparser.py +++ b/osu-ac/argparser.py @@ -25,3 +25,5 @@ action="store_true") argparser.add_argument("-v", "--verify", help="Takes 3 positional arguments - map id, user1 id and user2 id. Verifies that the scores are steals of each other", nargs=3) + +argparser.add_argument("--version", help="Prints the program version", action="store_true") diff --git a/osu-ac/config.py b/osu-ac/config.py index 4b2f54e3..7fa009b6 100644 --- a/osu-ac/config.py +++ b/osu-ac/config.py @@ -19,3 +19,5 @@ WHITELIST = ["124493", "6304246", "2558286", "2562987", "2757689"] PATH_DB = PATH_ROOT / "db" / "cache.db" # /absolute/path/db/cache.db + +VERSION = "1.0d" From 74cb983a1ed13f0de4d72e64edfc840a24ebbc0c Mon Sep 17 00:00:00 2001 From: tybug Date: Sat, 2 Feb 2019 00:40:00 -0500 Subject: [PATCH 13/29] change default threshold to 18 --- osu-ac/argparser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-ac/argparser.py b/osu-ac/argparser.py index 67b63394..461a0eba 100644 --- a/osu-ac/argparser.py +++ b/osu-ac/argparser.py @@ -11,7 +11,7 @@ "a score set by a user on a beatmap (if set with -m and -u) or the other scores in the folder " "(default behavior)"), action="store_true") -argparser.add_argument("-t", "--threshold", help="sets the similarity threshold to print results that score under it. Defaults to 20", type=int, default=20) +argparser.add_argument("-t", "--threshold", help="sets the similarity threshold to print results that score under it. Defaults to 20", type=int, default=18) argparser.add_argument("-a", "--auto", help="Sets the threshold to a number of standard deviations below the average similarity", type=float, dest="stddevs") From e0e671c034bcafe052c469711b2e91a85f07cc7f Mon Sep 17 00:00:00 2001 From: Samuel Klumpers Date: Sat, 2 Feb 2019 12:43:47 +0100 Subject: [PATCH 14/29] reinsert visualization legend --- osu-ac/draw.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu-ac/draw.py b/osu-ac/draw.py index d9a4f49c..46567d88 100644 --- a/osu-ac/draw.py +++ b/osu-ac/draw.py @@ -68,6 +68,8 @@ def run(self): plot1 = plt.plot('x', 'y', "red", animated=True, label=self.replay1.player_name)[0] plot2 = plt.plot('', '', "blue", animated=True, label=self.replay2.player_name)[0] + fig.legend() + def init(): ax.set_xlim(0, 512) ax.set_ylim(0, 384) From a00cbd04463c93bf298959762b014ac014b54da5 Mon Sep 17 00:00:00 2001 From: Samuel Klumpers Date: Sat, 2 Feb 2019 12:45:47 +0100 Subject: [PATCH 15/29] fix top n leaderboard label --- osu-ac/gui.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-ac/gui.py b/osu-ac/gui.py index 5b1de9f0..cb9c792f 100644 --- a/osu-ac/gui.py +++ b/osu-ac/gui.py @@ -96,7 +96,7 @@ def run_anticheat(): top_plays_label1.grid(row=0, column=1) top_plays_entry = ttk.Entry(top_x_plays, width=4, textvariable=num) top_plays_entry.grid(row=0, column=2) -top_plays_label2 = ttk.Label(top_x_plays, text="leaderboard plays?\n(Between 1 and 100 inclusive)") +top_plays_label2 = ttk.Label(top_x_plays, text="leaderboard plays?\n(Between 2 and 100 inclusive)") top_plays_label2.grid(row=0, column=3) auto_threshold = ttk.Frame(options) From 9ecdfd6451b46b6211f5f760686415a85d5609cc Mon Sep 17 00:00:00 2001 From: Samuel Klumpers Date: Sat, 2 Feb 2019 12:46:47 +0100 Subject: [PATCH 16/29] fix fetch <2 or >100 error message --- osu-ac/loader.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-ac/loader.py b/osu-ac/loader.py index 5409e02b..f7f074f1 100644 --- a/osu-ac/loader.py +++ b/osu-ac/loader.py @@ -60,7 +60,7 @@ def users_info(map_id, num=50): """ if(num > 100 or num < 2): - raise Exception("The number of top plays to fetch must be between 1 and 100 inclusive!") + raise Exception("The number of top plays to fetch must be between 2 and 100 inclusive!") response = requests.get(API_SCORES_ALL.format(map_id, num)).json() if(Loader.check_response(response)): Loader.enforce_ratelimit() From ac6fc09194952eba4cd98624b1b0e661b324ea7c Mon Sep 17 00:00:00 2001 From: tybug Date: Sat, 2 Feb 2019 13:37:31 -0500 Subject: [PATCH 17/29] raise properly subclassed exception types --- STYLE.md | 8 ++++---- osu-ac/comparer.py | 4 ++-- osu-ac/exceptions.py | 8 ++++++++ osu-ac/loader.py | 10 +++++----- 4 files changed, 19 insertions(+), 11 deletions(-) create mode 100644 osu-ac/exceptions.py diff --git a/STYLE.md b/STYLE.md index 798e596f..59cdb6bd 100644 --- a/STYLE.md +++ b/STYLE.md @@ -29,7 +29,7 @@ def method(arg1, arg2): [arg type] [arg name]: [description of arg] Returns: - [Description of return value] + [Description of return value] Raises: [error name]: [description of when this error is raised] @@ -56,8 +56,8 @@ Classes documentation is a little different. Classes follow all the same guideli ```python class Comparer: """ - A class for managing a set of replay comparisons. - + A class for managing a set of replay comparisons. + Attributes: List replays1: A list of Replay instances to compare against replays2. List replays2: A list of Replay instances to be compared against. @@ -116,4 +116,4 @@ Follow general git conventions when committing: Pull Requests with messy history may be converted to a squash merge at a mantainer's discretion. -Finally, thou shalt not commit directly to master. \ No newline at end of file +Finally, thou shalt not commit directly to master. diff --git a/osu-ac/comparer.py b/osu-ac/comparer.py index 2299a461..a1a92dec 100644 --- a/osu-ac/comparer.py +++ b/osu-ac/comparer.py @@ -5,7 +5,7 @@ from draw import Draw from replay import Replay from config import WHITELIST - +from exceptions import InvalidArgumentsException class Comparer: """ A class for managing a set of replay comparisons. @@ -67,7 +67,7 @@ def compare(self, mode): elif (mode == "single"): iterator = itertools.combinations(self.replays1, 2) else: - raise Exception("`mode` must be one of 'double' or 'single'") + raise InvalidArgumentsException("`mode` must be one of 'double' or 'single'") print("Starting to compare replays") # automatically determine threshold based on standard deviations of similarities if stddevs is set diff --git a/osu-ac/exceptions.py b/osu-ac/exceptions.py new file mode 100644 index 00000000..123c8599 --- /dev/null +++ b/osu-ac/exceptions.py @@ -0,0 +1,8 @@ +class AnticheatException(Exception): + """Base class for exceptions in the anticheat program.""" + +class InvalidArgumentsException(AnticheatException): + """Indicates an invalid argument was passed to one of the flags.""" + +class APIException(AnticheatException): + """Indicates some error on the API's end that we were not prepared to handle.""" diff --git a/osu-ac/loader.py b/osu-ac/loader.py index f7f074f1..592aaa90 100644 --- a/osu-ac/loader.py +++ b/osu-ac/loader.py @@ -5,7 +5,7 @@ from enums import Error from config import API_SCORES_ALL, API_SCORES_USER, API_REPLAY - +from exceptions import InvalidArgumentsException, APIException, AnticheatException def api(function): """ @@ -43,7 +43,7 @@ def __init__(self): This class should never be instantiated. All methods are static. """ - raise Exception("This class is not meant to be instantiated. Use the static methods instead.") + raise AnticheatException("This class is not meant to be instantiated. Use the static methods instead.") @staticmethod @api @@ -60,7 +60,7 @@ def users_info(map_id, num=50): """ if(num > 100 or num < 2): - raise Exception("The number of top plays to fetch must be between 2 and 100 inclusive!") + raise InvalidArgumentsException("The number of top plays to fetch must be between 2 and 100 inclusive!") response = requests.get(API_SCORES_ALL.format(map_id, num)).json() if(Loader.check_response(response)): Loader.enforce_ratelimit() @@ -102,7 +102,7 @@ def replay_data(map_id, user_id): The lzma bytes (b64 decoded response) returned by the api, or None if the replay was not available. Raises: - Exception if the api response with an error we don't know. + APIException if the api responds with an error we don't know. """ print("Requesting replay by {} on map {}".format(user_id, map_id)) @@ -119,7 +119,7 @@ def replay_data(map_id, user_id): Loader.enforce_ratelimit() return Loader.replay_data(map_id, user_id) elif(error == Error.UNKOWN): - raise Exception("unkown error when requesting replay by {} on map {}. Please lodge an issue with the devs immediately".format(user_id, map_id)) + raise APIException("unkown error when requesting replay by {} on map {}. Please lodge an issue with the devs immediately".format(user_id, map_id)) return base64.b64decode(response["content"]) From f75fe9fa7929a10085e399e99b9901686294ba99 Mon Sep 17 00:00:00 2001 From: tybug Date: Sat, 2 Feb 2019 17:53:30 -0500 Subject: [PATCH 18/29] fix loading from cache --- osu-ac/cacher.py | 6 +++++- osu-ac/online_replay.py | 4 ++-- osu-ac/replay.py | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu-ac/cacher.py b/osu-ac/cacher.py index 92642eb0..05d98ebd 100644 --- a/osu-ac/cacher.py +++ b/osu-ac/cacher.py @@ -86,7 +86,11 @@ def check_cache(self, map_id, user_id): """ result = self.cursor.execute("SELECT replay_data FROM replays WHERE map_id=? AND user_id=?", [map_id, user_id]).fetchone() - return wtc.decompress(result[0]) if result else None + if(result): + print("Loading replay by {} from cache".format(user_id)) + return wtc.decompress(result[0]) + + return None def write(self, statement, args): """ diff --git a/osu-ac/online_replay.py b/osu-ac/online_replay.py index 92096df7..240bdef2 100644 --- a/osu-ac/online_replay.py +++ b/osu-ac/online_replay.py @@ -12,7 +12,7 @@ def check_cache(function): Decorator that checks if the replay by the given user_id on the given map_id is already cached. If so, returns a Replay instance from the cached string instead of requesting it from the api. - Note that cacher, map_id, user_id, and enabled_mods must be the first, second, third, and fifth arguments to the function respectively. + Note that cacher, map_id, user_id, and enabled_mods must be the first, second, third, and sixth arguments to the function respectively. Returns: A Replay instance from the cached replay if it was cached, or the return value of the function if not. @@ -22,7 +22,7 @@ def wrapper(*args, **kwargs): cacher = args[0] map_id = args[1] user_id = args[2] - enabled_mods = args[4] + enabled_mods = args[5] lzma = cacher.check_cache(map_id, user_id) if(lzma): replay_data = osrparse.parse_replay(lzma, pure_lzma=True).play_data diff --git a/osu-ac/replay.py b/osu-ac/replay.py index f4e0ed0a..2b03d702 100644 --- a/osu-ac/replay.py +++ b/osu-ac/replay.py @@ -74,6 +74,7 @@ def bits(n): b = n & (~n+1) yield b n ^= b + bit_values_gen = bits(enabled_mods) self.enabled_mods = frozenset(Mod(mod_val) for mod_val in bit_values_gen) From 30c2c6e74cf6fad8995bb6719c2a2735d44eea07 Mon Sep 17 00:00:00 2001 From: tybug Date: Sat, 2 Feb 2019 17:53:48 -0500 Subject: [PATCH 19/29] use specific versions instead of master branches for pip --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 5ce86cce..87cba2c4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy>=1.16.0 matplotlib>=3.0.2 requests>=2.21.0 -https://github.com/osu-anticheat/osu-replay-parser/archive/master.zip -https://github.com/osu-anticheat/wtc-lzma-compressor/archive/master.zip +https://github.com/osu-anticheat/osu-replay-parser/archive/v3.1.0.zip +https://github.com/osu-anticheat/wtc-lzma-compressor/archive/v1.1.1.zip From 72b9f0589c50f9e2f60bf146db2c46e823486ec8 Mon Sep 17 00:00:00 2001 From: tybug Date: Sat, 2 Feb 2019 20:26:53 -0500 Subject: [PATCH 20/29] pass replay_id when creating Replay from cache --- osu-ac/online_replay.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu-ac/online_replay.py b/osu-ac/online_replay.py index 240bdef2..8c01db63 100644 --- a/osu-ac/online_replay.py +++ b/osu-ac/online_replay.py @@ -12,7 +12,7 @@ def check_cache(function): Decorator that checks if the replay by the given user_id on the given map_id is already cached. If so, returns a Replay instance from the cached string instead of requesting it from the api. - Note that cacher, map_id, user_id, and enabled_mods must be the first, second, third, and sixth arguments to the function respectively. + Note that cacher, map_id, user_id, replay_id, and enabled_mods must be the first, second, third, fifth, and sixth arguments to the function respectively. Returns: A Replay instance from the cached replay if it was cached, or the return value of the function if not. @@ -22,11 +22,12 @@ def wrapper(*args, **kwargs): cacher = args[0] map_id = args[1] user_id = args[2] + replay_id = args[4] enabled_mods = args[5] lzma = cacher.check_cache(map_id, user_id) if(lzma): replay_data = osrparse.parse_replay(lzma, pure_lzma=True).play_data - return Replay(replay_data, user_id, enabled_mods) + return Replay(replay_data, user_id, enabled_mods, replay_id=replay_id) else: return function(*args, **kwargs) return wrapper From ad55e54c84de57c1598250f1492eded340badee5 Mon Sep 17 00:00:00 2001 From: tybug Date: Sat, 2 Feb 2019 21:22:49 -0500 Subject: [PATCH 21/29] only attempt to revalidate users actually stored in db --- osu-ac/cacher.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/osu-ac/cacher.py b/osu-ac/cacher.py index 05d98ebd..49ad3273 100644 --- a/osu-ac/cacher.py +++ b/osu-ac/cacher.py @@ -52,19 +52,23 @@ def cache(self, map_id, user_id, lzma_bytes, replay_id): else: # else just insert self.write("INSERT INTO replays VALUES(?, ?, ?, ?)", [map_id, user_id, compressed_bytes, replay_id]) - def revalidate(self, map_id, user_to_replay): + def revalidate(self, map_id, user_info): """ Re-caches a stored replay if one of the given users has overwritten their score on the given map since it was cached. Args: String map_id: The map to revalidate. - Dictionary user_to_replay: The up to date mapping of user_id to [username, replay_id, enabled_mods] to revalidate. + Dictionary user_info: The up to date mapping of user_id to [username, replay_id, enabled_mods] to revalidate. Only contains information for a single map. """ result = self.cursor.execute("SELECT user_id, replay_id FROM replays WHERE map_id=?", [map_id]).fetchall() + print(result) + # filter result to only contain entries also in user_info + result = [info for info in result if info[0] in user_info.keys()] + print(result) for user_id, local_replay_id in result: - online_replay_id = user_to_replay[user_id][1] + online_replay_id = user_info[user_id][1] if(local_replay_id != online_replay_id): # local (outdated) id does not match online (updated) id print("replay outdated, redownloading...", end="") # this **could** conceivable be the source of a logic error by Loader.replay_data returning None and the cache storing None, From 553787fbd75d7afcb981e267a35f861541b3d5fb Mon Sep 17 00:00:00 2001 From: tybug Date: Sat, 2 Feb 2019 21:30:14 -0500 Subject: [PATCH 22/29] remove debugging --- osu-ac/cacher.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu-ac/cacher.py b/osu-ac/cacher.py index 49ad3273..43c0b72a 100644 --- a/osu-ac/cacher.py +++ b/osu-ac/cacher.py @@ -63,10 +63,9 @@ def revalidate(self, map_id, user_info): """ result = self.cursor.execute("SELECT user_id, replay_id FROM replays WHERE map_id=?", [map_id]).fetchall() - print(result) + # filter result to only contain entries also in user_info result = [info for info in result if info[0] in user_info.keys()] - print(result) for user_id, local_replay_id in result: online_replay_id = user_info[user_id][1] if(local_replay_id != online_replay_id): # local (outdated) id does not match online (updated) id From b120098c5d4bb97aaa72c07a0fae376a17c53696 Mon Sep 17 00:00:00 2001 From: tybug Date: Sat, 2 Feb 2019 21:45:39 -0500 Subject: [PATCH 23/29] bump version --- osu-ac/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-ac/config.py b/osu-ac/config.py index 7fa009b6..237b4232 100644 --- a/osu-ac/config.py +++ b/osu-ac/config.py @@ -20,4 +20,4 @@ PATH_DB = PATH_ROOT / "db" / "cache.db" # /absolute/path/db/cache.db -VERSION = "1.0d" +VERSION = "1.1" From 6f15c777d2602a73e81bbe3620ee81a608cf21c5 Mon Sep 17 00:00:00 2001 From: tybug Date: Sat, 2 Feb 2019 22:41:14 -0500 Subject: [PATCH 24/29] update dependencies for org name change --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 87cba2c4..15754c02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,5 @@ numpy>=1.16.0 matplotlib>=3.0.2 requests>=2.21.0 -https://github.com/osu-anticheat/osu-replay-parser/archive/v3.1.0.zip -https://github.com/osu-anticheat/wtc-lzma-compressor/archive/v1.1.1.zip +https://github.com/circleguard/osu-replay-parser/archive/v3.1.0.zip +https://github.com/circleguard/wtc-lzma-compressor/archive/v1.1.1.zip From c771258da65e546dadba48e3ab7ddafbab80dcff Mon Sep 17 00:00:00 2001 From: tybug Date: Sat, 2 Feb 2019 22:51:52 -0500 Subject: [PATCH 25/29] update org name to circleguard in readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 3c2cbe3a..e8026703 100644 --- a/README.md +++ b/README.md @@ -3,13 +3,13 @@ https://discord.gg/wanBtNY -# osu!anticheat +# Circleguard This project ultimately aims to create a comprehensive, player-run anticheat. A by no means complete list of cheats includes replay stealing, relax, replay editing, and timewarp. As of the v1.0 release, we only attempt to detect the first item in that list - replay stealing. -**Disclaimer: Neither the osu!ac organization nor any of the osu!anticheat devs are associated with osu! or the official osu! staff in any way.** +**Disclaimer: Neither the osu!ac organization nor any of the circleguard devs are associated with osu! or the official osu! staff in any way.** ## Getting Started @@ -87,7 +87,7 @@ When you click 'run' in the gui, keep an eye on the command line you started the ## Methodology This program compares the cursor positions of two replays to determine average distance between them. Since the times rarely match up perfectly between replays, the coordinates from one replay are interpolated from its previous and next position to estimate its position at a time identical to the other replay. By doing this we force all timestamps to be identical for easy comparison, at the cost of some precision. -If run with -c (or with the appropriate option checked in the GUI), downloaded replays will be lossily compressed to roughly half their original size with [wtc compression](https://github.com/osu-anticheat/wtc-lzma-compressor). This reduces the need to wait for API ratelimits if run again. +If run with -c (or with the appropriate option checked in the GUI), downloaded replays will be lossily compressed to roughly half their original size with [wtc compression](https://github.com/circleguard/wtc-lzma-compressor). This reduces the need to wait for API ratelimits if run again. ## Developement @@ -97,4 +97,4 @@ If you have feedback on the program, are interested in contributing, or just wan ## Credits -Thanks to [kszlim](https://github.com/kszlim), whose [replay parser](https://github.com/kszlim/osu-replay-parser) formed the basis of [our modified replay parser](https://github.com/osu-anticheat/osu-replay-parser). +Thanks to [kszlim](https://github.com/kszlim), whose [replay parser](https://github.com/kszlim/osu-replay-parser) formed the basis of [our modified replay parser](https://github.com/circleguard/osu-replay-parser). From bc277eca1f763c59b03b198f50f63e62e35ba657 Mon Sep 17 00:00:00 2001 From: tybug Date: Sat, 2 Feb 2019 23:31:02 -0500 Subject: [PATCH 26/29] missed one... --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e8026703..bf2bce33 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This project ultimately aims to create a comprehensive, player-run anticheat. A As of the v1.0 release, we only attempt to detect the first item in that list - replay stealing. -**Disclaimer: Neither the osu!ac organization nor any of the circleguard devs are associated with osu! or the official osu! staff in any way.** +**Disclaimer: Neither the Circleguard organization nor any of the circleguard devs are associated with osu! or the official osu! staff in any way.** ## Getting Started From 7e068d3de92879f09ac47c6fc5d33f3bba4bf408 Mon Sep 17 00:00:00 2001 From: tybug Date: Sun, 3 Feb 2019 11:18:36 -0500 Subject: [PATCH 27/29] remove any mention of anticheat in code --- README.md | 19 +++++++----------- {osu-ac => circleguard}/argparser.py | 0 {osu-ac => circleguard}/cacher.py | 0 .../circleguard.py | 19 +++++++++++------- {osu-ac => circleguard}/comparer.py | 0 {osu-ac => circleguard}/config.py | 5 ----- {osu-ac => circleguard}/db/cache.db | Bin {osu-ac => circleguard}/draw.py | 0 {osu-ac => circleguard}/enums.py | 0 circleguard/exceptions.py | 8 ++++++++ {osu-ac => circleguard}/gui.py | 14 ++++++------- {osu-ac => circleguard}/investigator.py | 0 {osu-ac => circleguard}/loader.py | 4 ++-- {osu-ac => circleguard}/local_replay.py | 0 {osu-ac => circleguard}/online_replay.py | 0 {osu-ac => circleguard}/replay.py | 0 {osu-ac => circleguard}/replays/cheater.osr | Bin {osu-ac => circleguard}/replays/elnabhan.osr | Bin {osu-ac => circleguard}/replays/woey.osr | Bin circleguard/secret.py | 1 + osu-ac/exceptions.py | 8 -------- 21 files changed, 37 insertions(+), 41 deletions(-) rename {osu-ac => circleguard}/argparser.py (100%) rename {osu-ac => circleguard}/cacher.py (100%) rename osu-ac/anticheat.py => circleguard/circleguard.py (88%) rename {osu-ac => circleguard}/comparer.py (100%) rename {osu-ac => circleguard}/config.py (72%) rename {osu-ac => circleguard}/db/cache.db (100%) rename {osu-ac => circleguard}/draw.py (100%) rename {osu-ac => circleguard}/enums.py (100%) create mode 100644 circleguard/exceptions.py rename {osu-ac => circleguard}/gui.py (91%) rename {osu-ac => circleguard}/investigator.py (100%) rename {osu-ac => circleguard}/loader.py (96%) rename {osu-ac => circleguard}/local_replay.py (100%) rename {osu-ac => circleguard}/online_replay.py (100%) rename {osu-ac => circleguard}/replay.py (100%) rename {osu-ac => circleguard}/replays/cheater.osr (100%) rename {osu-ac => circleguard}/replays/elnabhan.osr (100%) rename {osu-ac => circleguard}/replays/woey.osr (100%) create mode 100644 circleguard/secret.py delete mode 100644 osu-ac/exceptions.py diff --git a/README.md b/README.md index bf2bce33..26889764 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,3 @@ -**PLEASE JOIN OUR DISCORD AND REPORT CHEATERS THERE. Public reporting to /r/osureport will allow for cheaters to overwrite their stolen score before staff can get to them. The link to our discord can be found below** - -https://discord.gg/wanBtNY - - # Circleguard This project ultimately aims to create a comprehensive, player-run anticheat. A by no means complete list of cheats includes replay stealing, relax, replay editing, and timewarp. @@ -28,7 +23,7 @@ There are two ways to use the program - purely through the CLI, or through a GUI ### CLI -For the former, run the anticheat.py file with some or all of the following flags: +For the former, run the circleguard.py file with some or all of the following flags: | Flag | Usage | | --- | --- | @@ -47,19 +42,19 @@ For the former, run the anticheat.py file with some or all of the following flag ```bash # compares https://osu.ppy.sh/u/1019489's replay on https://osu.ppy.sh/b/1776628 with the 49 other leaderboard replays -$ python anticheat.py -m 1776628 -u 1019489 +$ python circleguard.py -m 1776628 -u 1019489 # compares the top 57 leaderboard replays against the other top 57 replays (57 choose 2 comparisons) -$ python anticheat.py -m 1618546 -n 57 +$ python circleguard.py -m 1618546 -n 57 # compares the top 50 leaderboard replays against the other top 50 replays (50 choose 2 comparisons) and sets the threshold to be one standard deviation below the average similarity. -$ python anticheat.py -m 1618546 -n 50 -a 1.0 +$ python circleguard.py -m 1618546 -n 50 -a 1.0 # compares all replays under user/ with the top 50 scores on https://osu.ppy.sh/b/1611251 -$ python anticheat.py -l -m 1611251 +$ python circleguard.py -l -m 1611251 # compares all replays under user/ with all replays under compare/ -$ python anticheat.py -l +$ python circleguard.py -l ``` This means that if you have a replay from a player and want to see if it's stolen, you should place it in the user/ directory and run with the -l and -m flags. @@ -87,7 +82,7 @@ When you click 'run' in the gui, keep an eye on the command line you started the ## Methodology This program compares the cursor positions of two replays to determine average distance between them. Since the times rarely match up perfectly between replays, the coordinates from one replay are interpolated from its previous and next position to estimate its position at a time identical to the other replay. By doing this we force all timestamps to be identical for easy comparison, at the cost of some precision. -If run with -c (or with the appropriate option checked in the GUI), downloaded replays will be lossily compressed to roughly half their original size with [wtc compression](https://github.com/circleguard/wtc-lzma-compressor). This reduces the need to wait for API ratelimits if run again. +If run with -c (or with the appropriate option checked in the GUI), downloaded replays will be lossily compressed to roughly half their original size with [wtc compression](https://github.com/circleguard/wtc-lzma-compressor) and then stored in a local databsae. This reduces the need to wait for API ratelimits if run again. ## Developement diff --git a/osu-ac/argparser.py b/circleguard/argparser.py similarity index 100% rename from osu-ac/argparser.py rename to circleguard/argparser.py diff --git a/osu-ac/cacher.py b/circleguard/cacher.py similarity index 100% rename from osu-ac/cacher.py rename to circleguard/cacher.py diff --git a/osu-ac/anticheat.py b/circleguard/circleguard.py similarity index 88% rename from osu-ac/anticheat.py rename to circleguard/circleguard.py index 57a56069..15523e51 100644 --- a/osu-ac/anticheat.py +++ b/circleguard/circleguard.py @@ -9,6 +9,8 @@ import sys import itertools +import os +from os.path import isfile, join from argparser import argparser from draw import Draw @@ -18,19 +20,22 @@ from comparer import Comparer from investigator import Investigator from cacher import Cacher -from config import PATH_REPLAYS, WHITELIST, VERSION +from config import PATH_REPLAYS_STUB, WHITELIST, VERSION -class Anticheat: +class Circleguard: def __init__(self, args): """ - Initializes an Anticheat instance. + Initializes a Circleguard instance. [SimpleNamespace or argparse.Namespace] args: A namespace-like object representing how and what to compare. An example may look like `Namespace(cache=False, local=False, map_id=None, number=50, threshold=20, user_id=None)` """ + # get all replays in path to check against. Load this per circleguard instance or users moving files around while the gui is open doesn't work. + self.PATH_REPLAYS = [join(PATH_REPLAYS_STUB, f) for f in os.listdir(PATH_REPLAYS_STUB) if isfile(join(PATH_REPLAYS_STUB, f)) and f != ".DS_Store"] + self.cacher = Cacher(args.cache) self.args = args if(args.map_id): @@ -71,7 +76,7 @@ def _run_local(self): args = self.args # get all local replays (used in every --local case) - replays1 = [LocalReplay.from_path(osr_path) for osr_path in PATH_REPLAYS] + replays1 = [LocalReplay.from_path(osr_path) for osr_path in self.PATH_REPLAYS] threshold = args.threshold stddevs = args.stddevs @@ -117,7 +122,7 @@ def _run_map(self): if __name__ == '__main__': args = argparser.parse_args() if(args.version): - print("osu!anticheat {}".format(VERSION)) + print("Circleguard {}".format(VERSION)) sys.exit(0) - anticheat = Anticheat(args) - anticheat.run() + circleguard = Circleguard(args) + circleguard.run() diff --git a/osu-ac/comparer.py b/circleguard/comparer.py similarity index 100% rename from osu-ac/comparer.py rename to circleguard/comparer.py diff --git a/osu-ac/config.py b/circleguard/config.py similarity index 72% rename from osu-ac/config.py rename to circleguard/config.py index 237b4232..a49aa154 100644 --- a/osu-ac/config.py +++ b/circleguard/config.py @@ -1,5 +1,3 @@ -import os -from os.path import isfile, join import pathlib from secret import API_KEY @@ -7,9 +5,6 @@ PATH_ROOT = pathlib.Path(__file__).parent PATH_REPLAYS_STUB = PATH_ROOT / "replays" -# get all replays in path to check against -PATH_REPLAYS = [join(PATH_REPLAYS_STUB, f) for f in os.listdir(PATH_REPLAYS_STUB) if isfile(join(PATH_REPLAYS_STUB, f)) and f != ".DS_Store"] - API_BASE = "https://osu.ppy.sh/api/" API_REPLAY = API_BASE + "get_replay?k=" + API_KEY + "&m=0&b={}&u={}" API_SCORES_ALL = API_BASE + "get_scores?k=" + API_KEY + "&m=0&b={}&limit={}" diff --git a/osu-ac/db/cache.db b/circleguard/db/cache.db similarity index 100% rename from osu-ac/db/cache.db rename to circleguard/db/cache.db diff --git a/osu-ac/draw.py b/circleguard/draw.py similarity index 100% rename from osu-ac/draw.py rename to circleguard/draw.py diff --git a/osu-ac/enums.py b/circleguard/enums.py similarity index 100% rename from osu-ac/enums.py rename to circleguard/enums.py diff --git a/circleguard/exceptions.py b/circleguard/exceptions.py new file mode 100644 index 00000000..fe79cb60 --- /dev/null +++ b/circleguard/exceptions.py @@ -0,0 +1,8 @@ +class CircleguardException(Exception): + """Base class for exceptions in the Circleguard program.""" + +class InvalidArgumentsException(CircleguardException): + """Indicates an invalid argument was passed to one of the flags.""" + +class APIException(CircleguardException): + """Indicates some error on the API's end that we were not prepared to handle.""" diff --git a/osu-ac/gui.py b/circleguard/gui.py similarity index 91% rename from osu-ac/gui.py rename to circleguard/gui.py index cb9c792f..2a44d3e5 100644 --- a/osu-ac/gui.py +++ b/circleguard/gui.py @@ -3,11 +3,11 @@ from types import SimpleNamespace import threading -from anticheat import Anticheat +from circleguard import Circleguard def run(): """ - Runs the anticheat with the options given in the gui. + Runs the circleguard with the options given in the gui. """ _map_id = map_id.get() @@ -22,18 +22,18 @@ def run(): _silent = True # Visualizations do very very bad things when not called from the main thread, so when using gui, we just...force ignore them _verify = verify.get() - def run_anticheat(): - anticheat = Anticheat(SimpleNamespace(map_id=_map_id, user_id=_user_id, local=_local, threshold=_threshold, stddevs=_stddevs, + def run_circleguard(): + circleguard = Circleguard(SimpleNamespace(map_id=_map_id, user_id=_user_id, local=_local, threshold=_threshold, stddevs=_stddevs, number=_number, cache=_cache, silent=_silent, verify=_verify)) - anticheat.run() + circleguard.run() - thread = threading.Thread(target=run_anticheat) + thread = threading.Thread(target=run_circleguard) thread.start() # Root and Frames configuration root = Tk() -root.title("Osu Anticheat") +root.title("Circleguard") root.columnconfigure(0, weight=1) root.rowconfigure(0, weight=1) # houses user input boxes and run button diff --git a/osu-ac/investigator.py b/circleguard/investigator.py similarity index 100% rename from osu-ac/investigator.py rename to circleguard/investigator.py diff --git a/osu-ac/loader.py b/circleguard/loader.py similarity index 96% rename from osu-ac/loader.py rename to circleguard/loader.py index 592aaa90..3510450b 100644 --- a/osu-ac/loader.py +++ b/circleguard/loader.py @@ -5,7 +5,7 @@ from enums import Error from config import API_SCORES_ALL, API_SCORES_USER, API_REPLAY -from exceptions import InvalidArgumentsException, APIException, AnticheatException +from exceptions import CircleguardException, InvalidArgumentsException, APIException def api(function): """ @@ -43,7 +43,7 @@ def __init__(self): This class should never be instantiated. All methods are static. """ - raise AnticheatException("This class is not meant to be instantiated. Use the static methods instead.") + raise CircleguardException("This class is not meant to be instantiated. Use the static methods instead.") @staticmethod @api diff --git a/osu-ac/local_replay.py b/circleguard/local_replay.py similarity index 100% rename from osu-ac/local_replay.py rename to circleguard/local_replay.py diff --git a/osu-ac/online_replay.py b/circleguard/online_replay.py similarity index 100% rename from osu-ac/online_replay.py rename to circleguard/online_replay.py diff --git a/osu-ac/replay.py b/circleguard/replay.py similarity index 100% rename from osu-ac/replay.py rename to circleguard/replay.py diff --git a/osu-ac/replays/cheater.osr b/circleguard/replays/cheater.osr similarity index 100% rename from osu-ac/replays/cheater.osr rename to circleguard/replays/cheater.osr diff --git a/osu-ac/replays/elnabhan.osr b/circleguard/replays/elnabhan.osr similarity index 100% rename from osu-ac/replays/elnabhan.osr rename to circleguard/replays/elnabhan.osr diff --git a/osu-ac/replays/woey.osr b/circleguard/replays/woey.osr similarity index 100% rename from osu-ac/replays/woey.osr rename to circleguard/replays/woey.osr diff --git a/circleguard/secret.py b/circleguard/secret.py new file mode 100644 index 00000000..77869a45 --- /dev/null +++ b/circleguard/secret.py @@ -0,0 +1 @@ +API_KEY = "aad7411a1ddb7e2f44cdb267473d1103028950f2" diff --git a/osu-ac/exceptions.py b/osu-ac/exceptions.py deleted file mode 100644 index 123c8599..00000000 --- a/osu-ac/exceptions.py +++ /dev/null @@ -1,8 +0,0 @@ -class AnticheatException(Exception): - """Base class for exceptions in the anticheat program.""" - -class InvalidArgumentsException(AnticheatException): - """Indicates an invalid argument was passed to one of the flags.""" - -class APIException(AnticheatException): - """Indicates some error on the API's end that we were not prepared to handle.""" From 4af63c586b6404eac4f5561d366081826134870a Mon Sep 17 00:00:00 2001 From: tybug Date: Sun, 3 Feb 2019 11:25:35 -0500 Subject: [PATCH 28/29] re-ignore secret.py --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f80f6e28..cb6a6366 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ *.DS_Store .spyproject osu-ac/secret.py +circleguard/secret.py ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. @@ -268,4 +269,4 @@ __pycache__/ *.pyc # pip nonsense -src/* \ No newline at end of file +src/* From 96f24bd894f44a811aec7ab107017b2629622dc5 Mon Sep 17 00:00:00 2001 From: Liam DeVoe Date: Sun, 3 Feb 2019 11:34:12 -0500 Subject: [PATCH 29/29] delete secret.py (oops) --- circleguard/secret.py | 1 - 1 file changed, 1 deletion(-) delete mode 100644 circleguard/secret.py diff --git a/circleguard/secret.py b/circleguard/secret.py deleted file mode 100644 index 77869a45..00000000 --- a/circleguard/secret.py +++ /dev/null @@ -1 +0,0 @@ -API_KEY = "aad7411a1ddb7e2f44cdb267473d1103028950f2"