diff --git a/.gitignore b/.gitignore index 1d8b4a5..e561f09 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ +__pycache__ .vscode build docs -test.luau tempCodeRunnerFile.luau +test.luau test.py \ No newline at end of file diff --git a/Tools/Content DataType Properties/Content DataType Properties Dumper.py b/Tools/Content DataType Properties/Content DataType Properties Dumper.py index dd6bb8b..bc09f5a 100644 --- a/Tools/Content DataType Properties/Content DataType Properties Dumper.py +++ b/Tools/Content DataType Properties/Content DataType Properties Dumper.py @@ -1,32 +1,45 @@ -import requests import os -import re +import sys -def api(): - deploy_history_url = "https://setup.rbxcdn.com/DeployHistory.txt" - deploy_history = requests.get(deploy_history_url).text +def import_dump_utils(): + """Smart function to find and import dump_utils from common directory""" + current_dir = os.path.dirname(os.path.abspath(__file__)) - lines = deploy_history.splitlines() + # Search up the directory tree + search_dir = current_dir + max_depth = 10 - for line in reversed(lines): - match = re.search(r"(version-[^\s]+)", line) - - if match: - version_hash = match.group(1) - api_dump_url = f"https://setup.rbxcdn.com/{version_hash}-Full-API-Dump.json" + for _ in range(max_depth): + common_path = os.path.join(search_dir, "common") + dump_utils_path = os.path.join(common_path, "dump_utils.py") + if os.path.exists(dump_utils_path): + if common_path not in sys.path: + sys.path.append(common_path) try: - response = requests.get(api_dump_url) - response.raise_for_status() - return response, version_hash + from dump_utils import write_dump_file, get_api_response - except requests.RequestException as e: - print(f"Error fetching API dump for {version_hash}: {e}") + print(f"Found dump_utils at: {common_path}") + return write_dump_file, get_api_response + except ImportError as e: + print(f"Failed to import from {common_path}: {e}") + break + + parent_dir = os.path.dirname(search_dir) + if parent_dir == search_dir: + break + search_dir = parent_dir + + raise ImportError("Could not find common/dump_utils.py in any parent directory") -def fetch_api(): - response, version_hash = api() +# Import utilities +write_dump_file, get_api_response = import_dump_utils() + + +def fetch_api(version_hash=None): + response, version_hash = get_api_response(version_hash) api_classes = response.json()["Classes"] output = version_hash + "\n\n" @@ -35,6 +48,7 @@ def fetch_api(): class_name = api_class["Name"] class_members = api_class["Members"] prev_len = len(output) + for member in class_members: member_name = member["Name"] member_type = member["MemberType"] @@ -49,7 +63,6 @@ def fetch_api(): and serialization["CanLoad"] and serialization["CanSave"] ): - output += f"{class_name}.{member_name}\n" if len(output) != prev_len: @@ -58,12 +71,14 @@ def fetch_api(): return output -try: - result = fetch_api() - print(result) - script_dir = os.path.dirname(os.path.realpath(__file__)) - output_file_path = os.path.join(script_dir, "Dump") - with open(output_file_path, "w") as file: - file.write(result) -except Exception as e: - print(f"Error: {e}") +if __name__ == "__main__": + version_hash = sys.argv[1] if len(sys.argv) > 1 else None + try: + content = fetch_api(version_hash) + print(content) + + script_dir = os.path.dirname(os.path.realpath(__file__)) + write_dump_file(content, "Dump", script_dir) + + except Exception as e: + print(f"Error: {e}") diff --git a/Tools/DataType Dumper/DataType Dumper.py b/Tools/DataType Dumper/DataType Dumper.py index 13cb219..0dc5ffd 100644 --- a/Tools/DataType Dumper/DataType Dumper.py +++ b/Tools/DataType Dumper/DataType Dumper.py @@ -1,122 +1,148 @@ -import requests import os -import re import sys -def array_to_dictionary(table, hybrid_mode=None): - tmp = {} - if hybrid_mode == "adjust": - for key, value in table.items(): - if isinstance(key, int): - tmp[value] = True - elif isinstance(value, dict): - tmp[key] = array_to_dictionary(value, "adjust") - else: - tmp[key] = value - else: - for value in table: - if isinstance(value, str): - tmp[value] = True - return tmp +def import_dump_utils(): + """Smart function to find and import dump_utils from common directory""" + current_dir = os.path.dirname(os.path.abspath(__file__)) + search_dir = current_dir + max_depth = 10 + + for _ in range(max_depth): + common_path = os.path.join(search_dir, "common") + dump_utils_path = os.path.join(common_path, "dump_utils.py") + + if os.path.exists(dump_utils_path): + if common_path not in sys.path: + sys.path.append(common_path) + try: + from dump_utils import ( + write_dump_file, + get_api_response, + array_to_dictionary, + ) + + print(f"Found dump_utils at: {common_path}") + return write_dump_file, get_api_response, array_to_dictionary + except ImportError as e: + print(f"Failed to import from {common_path}: {e}") + break + + parent_dir = os.path.dirname(search_dir) + if parent_dir == search_dir: + break + search_dir = parent_dir + + raise ImportError("Could not find common/dump_utils.py in any parent directory") + + +write_dump_file, get_api_response, array_to_dictionary = import_dump_utils() datatypes = [] datatypes_set = set() can_save_tracker = {} can_load_tracker = {} +all_member_tags = set() +all_capabilities = set() -def api(version_hash=None): - if version_hash: - # Use the provided version hash - api_dump_url = f"https://setup.rbxcdn.com/{version_hash}-API-Dump.json" - try: - response = requests.get(api_dump_url) - response.raise_for_status() - return response - except requests.RequestException as e: - print(f"Error fetching API dump for {version_hash}: {e}") - sys.exit(1) - else: - # Fall back to the original method of finding the latest version - deploy_history_url = "https://setup.rbxcdn.com/DeployHistory.txt" - deploy_history = requests.get(deploy_history_url).text - lines = deploy_history.splitlines() +def extract_capabilities(capabilities_data): + """Universal method to extract all capability strings from nested structures""" + capabilities = set() - for line in reversed(lines): - match = re.search(r"(version-[^\s]+)", line) - if match: - version_hash = match.group(1) - api_dump_url = ( - f"https://setup.rbxcdn.com/{version_hash}-Full-API-Dump.json" - ) - try: - response = requests.get(api_dump_url) - response.raise_for_status() - return response, version_hash - except requests.RequestException as e: - print(f"Error fetching API dump for {version_hash}: {e}") + if isinstance(capabilities_data, list): + for item in capabilities_data: + if isinstance(item, str): + capabilities.add(item) + elif isinstance(item, dict): + # Recursively extract from nested dictionaries + capabilities.update(extract_capabilities(item)) + elif isinstance(capabilities_data, dict): + for key, value in capabilities_data.items(): + if isinstance(value, (list, dict)): + # Recursively extract from nested structures + capabilities.update(extract_capabilities(value)) + elif isinstance(value, str): + capabilities.add(value) + + return capabilities def fetch_api(version_hash=None): - response, version_hash = api(version_hash) - api_classes = response.json()["Classes"] + response, version_hash = get_api_response(version_hash) + api_data = response.json() + api_classes = api_data["Classes"] + api_enums = api_data["Enums"] - global datatypes - global datatypes_set - global can_save_tracker - global can_load_tracker + global datatypes, datatypes_set, can_save_tracker, can_load_tracker + global all_member_tags, all_capabilities + # Process Classes for api_class in api_classes: class_members = api_class["Members"] for member in class_members: + member_tags = member.get("Tags") + if member_tags: + for tag in member_tags: + if isinstance(tag, str): + all_member_tags.add(tag) + + member_capabilities = member.get("Capabilities") + if member_capabilities: + extracted_caps = extract_capabilities(member_capabilities) + all_capabilities.update(extracted_caps) + if member["MemberType"] == "Property": - ignored = False - if not ignored: - member_tags = member.get("Tags") + serialization = member["Serialization"] + value_type = member["ValueType"] + value_type_name = value_type["Name"] + value_type_cat = value_type["Category"] - if member_tags: - member_tags = array_to_dictionary(member_tags) + if value_type_cat == "Enum" or value_type_cat == "Class": + continue - serialization = member["Serialization"] + # Track CanSave status + if serialization["CanSave"]: + can_save_tracker[value_type_name] = True + elif value_type_name not in can_save_tracker: + can_save_tracker[value_type_name] = False - value_type = member["ValueType"] - value_type_name = value_type["Name"] - value_type_cat = value_type["Category"] - if value_type_cat == "Enum" or value_type_cat == "Class": - continue + # Track CanLoad status + if serialization["CanLoad"]: + can_load_tracker[value_type_name] = True + elif value_type_name not in can_load_tracker: + can_load_tracker[value_type_name] = False - if serialization["CanSave"] == True: - can_save_tracker[value_type_name] = True - else: - if value_type_name not in can_save_tracker: - can_save_tracker[value_type_name] = False + if value_type_name not in datatypes_set: + datatypes_set.add(value_type_name) + datatypes.append(value_type_name) - if serialization["CanLoad"] == True: - can_load_tracker[value_type_name] = True - else: - if value_type_name not in can_load_tracker: - can_load_tracker[value_type_name] = False + # Process Enums - look for SecurityCapability + for api_enum in api_enums: + if api_enum["Name"] == "SecurityCapability": + items = api_enum["Items"] + for item in items: + capability_name = item["Name"] + all_capabilities.add(capability_name) + break # Found SecurityCapability, no need to continue - if value_type_name not in datatypes_set: - datatypes_set.add(value_type_name) - datatypes.append(value_type_name) return version_hash if __name__ == "__main__": - version_hash = None - if len(sys.argv) > 1: - version_hash = sys.argv[1] + version_hash = sys.argv[1] if len(sys.argv) > 1 else None + try: version_hash = fetch_api(version_hash) datatypes.sort() output_lines = [] + + output_lines.append("=== DATATYPES ===") for value_type in datatypes: can_save_status = can_save_tracker.get(value_type, False) can_load_status = can_load_tracker.get(value_type, False) @@ -135,12 +161,34 @@ if __name__ == "__main__": parts.append(verdict_text) output_lines.append(" ".join(parts).strip()) - s = "\n".join(output_lines) + "\n" - print(s) - s = version_hash + "\n\n" + s + output_lines.append("") + + output_lines.append("=== MEMBER TAGS ===") + output_lines.append(f"Total unique tags: {len(all_member_tags)}") + sorted_tags = sorted(list(all_member_tags)) + + for tag in sorted_tags: + output_lines.append(f" {tag}") + + output_lines.append("") + + output_lines.append("=== CAPABILITIES ===") + output_lines.append(f"Total unique capabilities: {len(all_capabilities)}") + sorted_capabilities = sorted(list(all_capabilities)) + + for capability in sorted_capabilities: + output_lines.append(f" {capability}") + + content_body = "\n".join(output_lines) + "\n" + full_content = version_hash + "\n\n" + content_body + + print(content_body) + script_dir = os.path.dirname(os.path.realpath(__file__)) - output_file_path = os.path.join(script_dir, "Dump") - with open(output_file_path, "w") as file: - file.write(s) + write_dump_file(full_content, "Dump", script_dir) + except Exception as e: print(f"Error: {e}") + import traceback + + traceback.print_exc() diff --git a/Tools/DataType Dumper/Dump b/Tools/DataType Dumper/Dump index 0209b66..4e92f5a 100644 --- a/Tools/DataType Dumper/Dump +++ b/Tools/DataType Dumper/Dump @@ -1,5 +1,6 @@ -version-874602c66c70451a +version-d638b440a9224bb2 +=== DATATYPES === Axes {CanSave} {CanLoad} BinaryString {CanSave} {CanLoad} BrickColor {CanSave} {CanLoad} @@ -43,3 +44,53 @@ float {CanSave} {CanLoad} int {CanSave} {CanLoad} int64 {CanSave} {CanLoad} string {CanSave} {CanLoad} + +=== MEMBER TAGS === +Total unique tags: 11 + CanYield + CustomLuaState + Deprecated + Hidden + NoYield + NotBrowsable + NotReplicated + NotScriptable + ReadOnly + WriteOnly + Yields + +=== CAPABILITIES === +Total unique capabilities: 33 + AccessOutsideWrite + Animation + AssetRequire + Assistant + Audio + Avatar + Basic + CSG + CapabilityControl + Chat + CreateInstances + DataStore + Environment + Input + InternalTest + LegacySound + LoadString + LocalUser + Network + Physics + Players + Plugin + PluginOrOpenCloud + RemoteCommand + RemoteEvent + RobloxEngine + RobloxScript + RunClientScript + RunServerScript + ScriptGlobals + UI + Unassigned + WritePlayer diff --git a/Tools/Doesn't Inherit from Class Dumper/Doesn't Inherit from Class Dumper.py b/Tools/Doesn't Inherit from Class Dumper/Doesn't Inherit from Class Dumper.py index f2eeb1b..ecab876 100644 --- a/Tools/Doesn't Inherit from Class Dumper/Doesn't Inherit from Class Dumper.py +++ b/Tools/Doesn't Inherit from Class Dumper/Doesn't Inherit from Class Dumper.py @@ -1,31 +1,45 @@ -import requests import os -import re +import sys -def array_to_dictionary(table, hybrid_mode=None): - tmp = {} - if hybrid_mode == "adjust": - for key, value in table.items(): - if isinstance(key, int): - tmp[value] = True - elif isinstance(value, dict): - tmp[key] = array_to_dictionary(value, "adjust") - else: - tmp[key] = value - else: - for value in table: - if isinstance(value, str): - tmp[value] = True - return tmp +def import_dump_utils(): + """Smart function to find and import dump_utils from common directory""" + current_dir = os.path.dirname(os.path.abspath(__file__)) + + # Search up the directory tree + search_dir = current_dir + max_depth = 10 + + for _ in range(max_depth): + common_path = os.path.join(search_dir, "common") + dump_utils_path = os.path.join(common_path, "dump_utils.py") + + if os.path.exists(dump_utils_path): + if common_path not in sys.path: + sys.path.append(common_path) + try: + from dump_utils import ( + write_dump_file, + get_api_response, + array_to_dictionary, + ) + + print(f"Found dump_utils at: {common_path}") + return write_dump_file, get_api_response, array_to_dictionary + except ImportError as e: + print(f"Failed to import from {common_path}: {e}") + break + + parent_dir = os.path.dirname(search_dir) + if parent_dir == search_dir: + break + search_dir = parent_dir + + raise ImportError("Could not find common/dump_utils.py in any parent directory") -def find_first_table(array): - for item in array: - if isinstance(item, dict): - return item - return None - +# Import utilities +write_dump_file, get_api_response, array_to_dictionary = import_dump_utils() Class = "Instance" @@ -39,40 +53,11 @@ def check_superclass_inheritance(class_name, class_list): return False -s = "\n" - - -def api(): - - deploy_history_url = "https://setup.rbxcdn.com/DeployHistory.txt" - deploy_history = requests.get(deploy_history_url).text - - lines = deploy_history.splitlines() - - for line in reversed(lines): - - match = re.search(r"(version-[^\s]+)", line) - - if match: - version_hash = match.group(1) - - api_dump_url = f"https://setup.rbxcdn.com/{version_hash}-Full-API-Dump.json" - - try: - response = requests.get(api_dump_url) - response.raise_for_status() - return response, version_hash - - except requests.RequestException as e: - print(f"Error fetching API dump for {version_hash}: {e}") - - -def fetch_api(): - response, version_hash = api() +def fetch_api(version_hash=None): + response, version_hash = get_api_response(version_hash) api_classes = response.json()["Classes"] class_list = {cls["Name"]: cls for cls in api_classes} - global s s = version_hash + "\n\n" for api_class in api_classes: class_name = api_class["Name"] @@ -81,18 +66,17 @@ def fetch_api(): if not check_superclass_inheritance(class_name, class_list): s += f"{class_name} does not inherit from {Class}\n" - prev_len = len(s) - - if len(s) != prev_len: - s += "\n" + return s -try: - fetch_api() - print(s) - script_dir = os.path.dirname(os.path.realpath(__file__)) - output_file_path = os.path.join(script_dir, "Dump") - with open(output_file_path, "w") as file: - file.write(s) -except Exception as e: - print(f"Error: {e}") +if __name__ == "__main__": + version_hash = sys.argv[1] if len(sys.argv) > 1 else None + try: + content = fetch_api(version_hash) + print(content) + + script_dir = os.path.dirname(os.path.realpath(__file__)) + write_dump_file(content, "Dump", script_dir) + + except Exception as e: + print(f"Error: {e}") diff --git a/Tools/ExpectedClassDumper for NotCreatable/Expected Class for NotCreatable Dumper.py b/Tools/ExpectedClassDumper for NotCreatable/Expected Class for NotCreatable Dumper.py index 5e74942..bfb88cd 100644 --- a/Tools/ExpectedClassDumper for NotCreatable/Expected Class for NotCreatable Dumper.py +++ b/Tools/ExpectedClassDumper for NotCreatable/Expected Class for NotCreatable Dumper.py @@ -1,61 +1,57 @@ -import requests, os, re +import os +import sys -def array_to_dictionary(table, hybrid_mode=None): - tmp = {} - if hybrid_mode == "adjust": - for key, value in table.items(): - if isinstance(key, int): - tmp[value] = True - elif isinstance(value, dict): - tmp[key] = array_to_dictionary(value, "adjust") - else: - tmp[key] = value - else: - for value in table: - if isinstance(value, str): - tmp[value] = True - return tmp +def import_dump_utils(): + """Smart function to find and import dump_utils from common directory""" + current_dir = os.path.dirname(os.path.abspath(__file__)) + # Search up the directory tree + search_dir = current_dir + max_depth = 10 -s = "\n" - - -def api(): - - deploy_history_url = "https://setup.rbxcdn.com/DeployHistory.txt" - deploy_history = requests.get(deploy_history_url).text - - lines = deploy_history.splitlines() - - for line in reversed(lines): - - match = re.search(r"(version-[^\s]+)", line) - - if match: - version_hash = match.group(1) - - api_dump_url = f"https://setup.rbxcdn.com/{version_hash}-Full-API-Dump.json" + for _ in range(max_depth): + common_path = os.path.join(search_dir, "common") + dump_utils_path = os.path.join(common_path, "dump_utils.py") + if os.path.exists(dump_utils_path): + if common_path not in sys.path: + sys.path.append(common_path) try: - response = requests.get(api_dump_url) - response.raise_for_status() - return response, version_hash + from dump_utils import ( + write_dump_file, + get_api_response, + array_to_dictionary, + ) - except requests.RequestException as e: - print(f"Error fetching API dump for {version_hash}: {e}") + print(f"Found dump_utils at: {common_path}") + return write_dump_file, get_api_response, array_to_dictionary + except ImportError as e: + print(f"Failed to import from {common_path}: {e}") + break + + parent_dir = os.path.dirname(search_dir) + if parent_dir == search_dir: + break + search_dir = parent_dir + + raise ImportError("Could not find common/dump_utils.py in any parent directory") -def fetch_api(): - response, version_hash = api() +# Import utilities +write_dump_file, get_api_response, array_to_dictionary = import_dump_utils() + + +def fetch_api(version_hash=None): + response, version_hash = get_api_response(version_hash) api_classes = response.json()["Classes"] - global s s = version_hash + "\n\n" for api_class in api_classes: class_name = api_class["Name"] class_members = api_class["Members"] prevlen = len(s) + for member in class_members: member_name = member["Name"] member_type = member["MemberType"] @@ -69,16 +65,21 @@ def fetch_api(): and value_type["Name"] != "Instance" ): s += f"{class_name}.{member_name} {{{value_type['Name']}}}\n" + if prevlen != len(s): s += "\n" + return s -try: - fetch_api() - print(s) - script_dir = os.path.dirname(os.path.realpath(__file__)) - output_file_path = os.path.join(script_dir, "Dump") - with open(output_file_path, "w") as file: - file.write(s) -except Exception as e: - print(f"Error: {e}") + +if __name__ == "__main__": + version_hash = sys.argv[1] if len(sys.argv) > 1 else None + try: + content = fetch_api(version_hash) + print(content) + + script_dir = os.path.dirname(os.path.realpath(__file__)) + write_dump_file(content, "Dump", script_dir) + + except Exception as e: + print(f"Error: {e}") diff --git a/Tools/Interesting Properties Behavior/Dump b/Tools/Interesting Properties Behavior/Dump index 4627706..8d01d5f 100644 --- a/Tools/Interesting Properties Behavior/Dump +++ b/Tools/Interesting Properties Behavior/Dump @@ -1,4 +1,4 @@ -version-874602c66c70451a +version-d638b440a9224bb2 Capture.CaptureTime {CanSave Only} Capture.CaptureType {CanSave Only} @@ -217,7 +217,7 @@ FunctionalTest.Is30FpsThrottleEnabled {CanLoad Only} FunctionalTest.PhysicsEnvironmentalThrottle {CanLoad Only} FunctionalTest.Timeout {CanLoad Only} -GameSettings.VideoRecording {CanLoad Only} +GameSettings.VideoCaptureEnabled {Deprecated} {CanSave} GamepadService is NotCreatable but GamepadService.GamepadCursorEnabled has a default value: false @@ -400,17 +400,17 @@ BasePart.TopParamB {Deprecated} {CanSave} BasePart.TopSurfaceInput {Deprecated} {CanSave} BasePart.Velocity {Deprecated} {CanSave} BasePart.brickColor {CanLoad Only} {Deprecated} -BasePart.siz {CanLoad Only} +BasePart.siz {WriteOnly} {CanLoad Only} FormFactorPart.FormFactor {CanLoad Only} {Deprecated} FormFactorPart.formFactor {CanLoad Only} {Deprecated} Part.Shape {CanLoad Only} -Part.shap {CanLoad Only} +Part.shap {WriteOnly} {CanLoad Only} -Terrain.ClusterGrid {CanLoad Only} -Terrain.ClusterGridV2 {CanLoad Only} -Terrain.ClusterGridV3 {CanLoad Only} +Terrain.ClusterGrid {WriteOnly} {CanLoad Only} +Terrain.ClusterGridV2 {WriteOnly} {CanLoad Only} +Terrain.ClusterGridV3 {WriteOnly} {CanLoad Only} Terrain is NotCreatable but Terrain.AcquisitionMethod has a default value: None TriangleMeshPart.CollisionFidelity {CanLoad Only} @@ -429,10 +429,10 @@ Camera.NearPlaneZ {CanSave Only} Camera.ViewportSize {CanSave Only} Camera.focus {CanLoad Only} {Deprecated} -HopperBin.Command {CanLoad Only} -HopperBin.TextureName {CanLoad Only} +HopperBin.Command {WriteOnly} {CanLoad Only} +HopperBin.TextureName {WriteOnly} {CanLoad Only} -Workspace.CollisionGroups {CanLoad Only} +Workspace.CollisionGroups {WriteOnly} {CanLoad Only} Workspace.StreamingPauseMode {CanLoad Only} PackageLink.PackageId {CanSave Only} @@ -582,7 +582,7 @@ TeleportAsyncResult.ReservedServerAccessCode {CanSave Only} TeleportService.CustomizedTeleportUI {CanLoad Only} {Deprecated} -TerrainRegion.GridV3 {CanLoad Only} +TerrainRegion.GridV3 {WriteOnly} {CanLoad Only} TestService.ErrorCount {CanSave Only} TestService.Is30FpsThrottleEnabled {CanLoad Only} {Deprecated} @@ -609,6 +609,7 @@ TextSource is NotCreatable but TextSource.CanSend has a default value: true ThirdPartyUserService.FriendCommunicationRestrictionStatus {CanSave Only} ThirdPartyUserService.HasActiveUser {CanSave Only} +ThirdPartyUserService.VoiceChatRestrictionStatus {CanSave Only} Translator.LocaleId {CanSave Only} diff --git a/Tools/Interesting Properties Behavior/Interesting Properties Behavior.py b/Tools/Interesting Properties Behavior/Interesting Properties Behavior.py index 513a0d4..9f8a810 100644 --- a/Tools/Interesting Properties Behavior/Interesting Properties Behavior.py +++ b/Tools/Interesting Properties Behavior/Interesting Properties Behavior.py @@ -1,58 +1,51 @@ -import requests import os -import re +import sys -def array_to_dictionary(table, hybrid_mode=None): - tmp = {} - if hybrid_mode == "adjust": - for key, value in table.items(): - if isinstance(key, int): - tmp[value] = True - elif isinstance(value, dict): - tmp[key] = array_to_dictionary(value, "adjust") - else: - tmp[key] = value - else: - for value in table: - if isinstance(value, str): - tmp[value] = True - return tmp +def import_dump_utils(): + """Smart function to find and import dump_utils from common directory""" + current_dir = os.path.dirname(os.path.abspath(__file__)) + # Search up the directory tree + search_dir = current_dir + max_depth = 10 -s = "\n" - - -def api(): - - deploy_history_url = "https://setup.rbxcdn.com/DeployHistory.txt" - deploy_history = requests.get(deploy_history_url).text - - lines = deploy_history.splitlines() - - for line in reversed(lines): - - match = re.search(r"(version-[^\s]+)", line) - - if match: - version_hash = match.group(1) - - api_dump_url = f"https://setup.rbxcdn.com/{version_hash}-Full-API-Dump.json" + for _ in range(max_depth): + common_path = os.path.join(search_dir, "common") + dump_utils_path = os.path.join(common_path, "dump_utils.py") + if os.path.exists(dump_utils_path): + if common_path not in sys.path: + sys.path.append(common_path) try: - response = requests.get(api_dump_url) - response.raise_for_status() - return response, version_hash + from dump_utils import ( + write_dump_file, + get_api_response, + array_to_dictionary, + ) - except requests.RequestException as e: - print(f"Error fetching API dump for {version_hash}: {e}") + print(f"Found dump_utils at: {common_path}") + return write_dump_file, get_api_response, array_to_dictionary + except ImportError as e: + print(f"Failed to import from {common_path}: {e}") + break + + parent_dir = os.path.dirname(search_dir) + if parent_dir == search_dir: + break + search_dir = parent_dir + + raise ImportError("Could not find common/dump_utils.py in any parent directory") -def fetch_api(): - response, version_hash = api() +# Import utilities +write_dump_file, get_api_response, array_to_dictionary = import_dump_utils() + + +def fetch_api(version_hash=None): + response, version_hash = get_api_response(version_hash) api_classes = response.json()["Classes"] - global s s = version_hash + "\n\n" class_list = {} @@ -117,8 +110,6 @@ def fetch_api(): class_tags = api_class.get("Tags") if class_tags: - if len(class_tags) == 0: - print("tagsempty") class_tags = array_to_dictionary(class_tags) else: print(class_name, "notags") @@ -134,59 +125,61 @@ def fetch_api(): for member in class_members: if member["MemberType"] == "Property": property_name = member["Name"] - ignored = False + member_tags = member.get("Tags") - if not ignored: - member_tags = member.get("Tags") + if member_tags: + member_tags = array_to_dictionary(member_tags) + else: + member_tags = {} - if member_tags: - member_tags = array_to_dictionary(member_tags) + serialization = member["Serialization"] - serialization = member["Serialization"] + # Check if this property uses a ValueType from NotScriptable properties + value_type = member["ValueType"] + value_type_name = value_type["Name"] + value_type_cat = value_type["Category"] + uses_notscriptable_type = ( + value_type_name in not_scriptable_types + and value_type_cat not in ["Enum", "Class"] + and not member_tags.get("NotScriptable") + and value_type_name not in NOTSCRIPTABLE_BLACKLIST + ) + tags = [] - # Check if this property uses a ValueType from NotScriptable properties - value_type = member["ValueType"] - value_type_name = value_type["Name"] - value_type_cat = value_type["Category"] - uses_notscriptable_type = ( - value_type_name in not_scriptable_types - and value_type_cat not in ["Enum", "Class"] - and (not member_tags or not member_tags.get("NotScriptable")) - and value_type_name not in NOTSCRIPTABLE_BLACKLIST + if uses_notscriptable_type: + tags.append( + f"{{Uses NotScriptable Type without the tag - {value_type_name} }}" ) - tags = [] - if uses_notscriptable_type: - tags.append( - f"{{Uses NotScriptable Type without the tag - {value_type_name} }}" - ) + if member_tags.get("WriteOnly"): + if member_tags.get("NotScriptable"): + tags.append("{WriteOnly}") + else: + tags.append("{Has WriteOnly without NotScriptable}") - if ( - serialization["CanLoad"] or serialization["CanSave"] - ): # Check if at least one is true + if serialization["CanLoad"] or serialization["CanSave"]: + # Add CanLoad/CanSave conditions + if serialization["CanLoad"] and not serialization["CanSave"]: + tags.append("{CanLoad Only}") + elif serialization["CanSave"] and not serialization["CanLoad"]: + tags.append("{CanSave Only}") - # Add CanLoad/CanSave conditions - if serialization["CanLoad"] and not serialization["CanSave"]: - tags.append("{CanLoad Only}") - elif serialization["CanSave"] and not serialization["CanLoad"]: - tags.append("{CanSave Only}") + # Add Deprecated tag if present + if member_tags.get("Deprecated"): + deprecated_tag = "{Deprecated}" + if serialization["CanSave"]: + deprecated_tag += " {CanSave}" + tags.append(deprecated_tag) - # Add Deprecated tag if present - if member_tags and member_tags.get("Deprecated"): - deprecated_tag = "{Deprecated}" - if serialization["CanSave"]: - deprecated_tag += " {CanSave}" - tags.append(deprecated_tag) + # Combine tags into one line, each tag in separate brackets + if tags: + s += f"{class_name}.{property_name} {' '.join(tags)}\n" - # Combine tags into one line, each tag in separate brackets - if tags: - s += f"{class_name}.{property_name} {' '.join(tags)}\n" - - class_info["Properties"][property_name] = { - "Serialization": serialization, - "Tags": member_tags, - "Default": member.get("Default"), - } + class_info["Properties"][property_name] = { + "Serialization": serialization, + "Tags": member_tags, + "Default": member.get("Default"), + } if class_tags and class_tags.get("NotCreatable"): for property_name, property_info in class_info["Properties"].items(): @@ -203,6 +196,7 @@ def fetch_api(): class_list[class_name] = class_info + # Add analysis section s += "\n" + "=" * 50 + "\n" s += "NOTSCRIPTABLE VALUE TYPE ANALYSIS\n" s += "=" * 50 + "\n\n" @@ -219,15 +213,17 @@ def fetch_api(): for type_name in sorted(NOTSCRIPTABLE_BLACKLIST): s += f" - {type_name}\n" - return class_list + return s -try: - fetch_api() - print(s) - script_dir = os.path.dirname(os.path.realpath(__file__)) - output_file_path = os.path.join(script_dir, "Dump") - with open(output_file_path, "w") as file: - file.write(s) -except Exception as e: - print(f"Error: {e}") +if __name__ == "__main__": + version_hash = sys.argv[1] if len(sys.argv) > 1 else None + try: + content = fetch_api(version_hash) + print(content) + + script_dir = os.path.dirname(os.path.realpath(__file__)) + write_dump_file(content, "Dump", script_dir) + + except Exception as e: + print(f"Error: {e}") diff --git a/Tools/NotCreatable Dumper/NotCreatable Dumper.py b/Tools/NotCreatable Dumper/NotCreatable Dumper.py index 43b30b9..580a7ec 100644 --- a/Tools/NotCreatable Dumper/NotCreatable Dumper.py +++ b/Tools/NotCreatable Dumper/NotCreatable Dumper.py @@ -1,67 +1,51 @@ -import requests import os -import re import sys -def array_to_dictionary(table, hybrid_mode=None): - tmp = {} - if hybrid_mode == "adjust": - for key, value in table.items(): - if isinstance(key, int): - tmp[value] = True - elif isinstance(value, dict): - tmp[key] = array_to_dictionary(value, "adjust") - else: - tmp[key] = value - else: - for value in table: - if isinstance(value, str): - tmp[value] = True - return tmp +def import_dump_utils(): + """Smart function to find and import dump_utils from common directory""" + current_dir = os.path.dirname(os.path.abspath(__file__)) + # Search up the directory tree + search_dir = current_dir + max_depth = 10 -s = "\n" + for _ in range(max_depth): + common_path = os.path.join(search_dir, "common") + dump_utils_path = os.path.join(common_path, "dump_utils.py") - -def api(version_hash=None): - if version_hash: - # Use the provided version hash - api_dump_url = f"https://setup.rbxcdn.com/{version_hash}-Full-API-Dump.json" - try: - response = requests.get(api_dump_url) - response.raise_for_status() - return response - except requests.RequestException as e: - print(f"Error fetching API dump for {version_hash}: {e}") - sys.exit(1) - else: - # Fall back to the original method of finding the latest version - deploy_history_url = "https://setup.rbxcdn.com/DeployHistory.txt" - deploy_history = requests.get(deploy_history_url).text - - lines = deploy_history.splitlines() - - for line in reversed(lines): - match = re.search(r"(version-[^\s]+)", line) - if match: - version_hash = match.group(1) - api_dump_url = ( - f"https://setup.rbxcdn.com/{version_hash}-Full-API-Dump.json" + if os.path.exists(dump_utils_path): + if common_path not in sys.path: + sys.path.append(common_path) + try: + from dump_utils import ( + write_dump_file, + get_api_response, + array_to_dictionary, ) - try: - response = requests.get(api_dump_url) - response.raise_for_status() - return response, version_hash - except requests.RequestException as e: - print(f"Error fetching API dump for {version_hash}: {e}") + + print(f"Found dump_utils at: {common_path}") + return write_dump_file, get_api_response, array_to_dictionary + except ImportError as e: + print(f"Failed to import from {common_path}: {e}") + break + + parent_dir = os.path.dirname(search_dir) + if parent_dir == search_dir: + break + search_dir = parent_dir + + raise ImportError("Could not find common/dump_utils.py in any parent directory") + + +# Import utilities +write_dump_file, get_api_response, array_to_dictionary = import_dump_utils() def fetch_api(version_hash=None): - response, version_hash = api(version_hash) + response, version_hash = get_api_response(version_hash) api_classes = response.json()["Classes"] - global s s = version_hash + "\n\n" class_list = {} @@ -70,9 +54,6 @@ def fetch_api(version_hash=None): class_tags = api_class.get("Tags") if class_tags: - # print(class_tags) - if len(class_tags) == 0: - print("tagsempty") class_tags = array_to_dictionary(class_tags) else: print(class_name, "notags") @@ -101,21 +82,19 @@ def fetch_api(version_hash=None): class_list[class_name] = class_info - return class_list + return s if __name__ == "__main__": # Check if version hash was passed as a command line argument - version_hash = None - if len(sys.argv) > 1: - version_hash = sys.argv[1] + version_hash = sys.argv[1] if len(sys.argv) > 1 else None try: - fetch_api(version_hash) - print(s) + content = fetch_api(version_hash) + print(content) + script_dir = os.path.dirname(os.path.realpath(__file__)) - output_file_path = os.path.join(script_dir, "Dump") - with open(output_file_path, "w") as file: - file.write(s) + write_dump_file(content, "Dump", script_dir) + except Exception as e: print(f"Error: {e}") diff --git a/Tools/NotScriptable-Related/NotScriptable Dumper/NotScriptable Dumper.py b/Tools/NotScriptable-Related/NotScriptable Dumper/NotScriptable Dumper.py index 370e4f7..9247d6a 100644 --- a/Tools/NotScriptable-Related/NotScriptable Dumper/NotScriptable Dumper.py +++ b/Tools/NotScriptable-Related/NotScriptable Dumper/NotScriptable Dumper.py @@ -1,60 +1,55 @@ -import requests import os +import sys import re -def array_to_dictionary(table, hybrid_mode=None): - tmp = {} - if hybrid_mode == "adjust": - for key, value in table.items(): - if isinstance(key, int): - tmp[value] = True - elif isinstance(value, dict): - tmp[key] = array_to_dictionary(value, "adjust") - else: - tmp[key] = value - else: - for value in table: - if isinstance(value, str): - tmp[value] = True - return tmp +def import_dump_utils(): + """Smart function to find and import dump_utils from common directory""" + current_dir = os.path.dirname(os.path.abspath(__file__)) + # Search up the directory tree + search_dir = current_dir + max_depth = 10 -s = "\n" -filtered_properties = [] - - -def api(): - - deploy_history_url = "https://setup.rbxcdn.com/DeployHistory.txt" - deploy_history = requests.get(deploy_history_url).text - - lines = deploy_history.splitlines() - - for line in reversed(lines): - - match = re.search(r"(version-[^\s]+)", line) - - if match: - version_hash = match.group(1) - - api_dump_url = f"https://setup.rbxcdn.com/{version_hash}-Full-API-Dump.json" + for _ in range(max_depth): + common_path = os.path.join(search_dir, "common") + dump_utils_path = os.path.join(common_path, "dump_utils.py") + if os.path.exists(dump_utils_path): + if common_path not in sys.path: + sys.path.append(common_path) try: - response = requests.get(api_dump_url) - response.raise_for_status() - return response, version_hash + from dump_utils import ( + write_dump_file, + get_api_response, + array_to_dictionary, + ) - except requests.RequestException as e: - print(f"Error fetching API dump for {version_hash}: {e}") + print(f"Found dump_utils at: {common_path}") + return write_dump_file, get_api_response, array_to_dictionary + except ImportError as e: + print(f"Failed to import from {common_path}: {e}") + break + + parent_dir = os.path.dirname(search_dir) + if parent_dir == search_dir: + break + search_dir = parent_dir + + raise ImportError("Could not find common/dump_utils.py in any parent directory") -def fetch_api(): - response, version_hash = api() +# Import utilities +write_dump_file, get_api_response, array_to_dictionary = import_dump_utils() + + +def fetch_api(version_hash=None): + response, version_hash = get_api_response(version_hash) api_classes = response.json()["Classes"] - global s, filtered_properties s = version_hash + "\n\n" + filtered_properties = [] + for api_class in api_classes: class_name = api_class["Name"] class_members = api_class["Members"] @@ -82,24 +77,22 @@ def fetch_api(): s += f"{class_name}.{member_name} {{{value_type}}}" for item in original_tags: if isinstance(item, dict): - - s += f"{'{PreferredDescriptorName: '+item.get('PreferredDescriptorName')+'}'}" + s += f"{{PreferredDescriptorName: {item.get('PreferredDescriptorName')}}}" s += "\n" if re.search( r"xml|internal|serial|replica", member_name, re.IGNORECASE ): - - str = f"{class_name}.{member_name}" + prop_str = f"{class_name}.{member_name}" if not special: - str += " {Scriptable}" + prop_str += " {Scriptable}" if not ( serialization.get("CanLoad", True) and serialization.get("CanSave", True) ): - str += " {Serialize: False}" + prop_str += " {Serialize: False}" - filtered_properties.append(str) + filtered_properties.append(prop_str) for enum_type, real_member_name in enum_members.items(): for member in class_members: @@ -119,13 +112,17 @@ def fetch_api(): s += "\nPotential Proxy Properties:\n" s += "\n".join(filtered_properties) + "\n" + return s -try: - fetch_api() - print(s) - script_dir = os.path.dirname(os.path.realpath(__file__)) - output_file_path = os.path.join(script_dir, "Dump") - with open(output_file_path, "w") as file: - file.write(s) -except Exception as e: - print(f"Error: {e}") + +if __name__ == "__main__": + version_hash = sys.argv[1] if len(sys.argv) > 1 else None + try: + content = fetch_api(version_hash) + print(content) + + script_dir = os.path.dirname(os.path.realpath(__file__)) + write_dump_file(content, "Dump", script_dir) + + except Exception as e: + print(f"Error: {e}") diff --git a/Tools/Run Dumpers.py b/Tools/Run Dumpers.py index 887740e..a79a2f2 100644 --- a/Tools/Run Dumpers.py +++ b/Tools/Run Dumpers.py @@ -22,7 +22,7 @@ def run_python_files_in_directories(directory, script_name, version_hash=None): print(f"Executed: {file_path}") except subprocess.CalledProcessError as e: print(f"Error running {file_path}: {e}") - else: + elif file.endswith(".luau") or file.endswith(".lua"): _, ext = os.path.splitext(file) if ext and ext.lower() != ".py": file_path = os.path.join(root, file) diff --git a/Tools/common/dump_utils.py b/Tools/common/dump_utils.py new file mode 100644 index 0000000..c54fef9 --- /dev/null +++ b/Tools/common/dump_utils.py @@ -0,0 +1,93 @@ +# common/dump_utils.py +import os +import sys +import requests +import re + + +def array_to_dictionary(table, hybrid_mode=None): + tmp = {} + if hybrid_mode == "adjust": + for key, value in table.items(): + if isinstance(key, int): + tmp[value] = True + elif isinstance(value, dict): + tmp[key] = array_to_dictionary(value, "adjust") + else: + tmp[key] = value + else: + for value in table: + if isinstance(value, str): + tmp[value] = True + return tmp + + +def should_write_file(output_file_path, new_content): + if not os.path.exists(output_file_path): + return True, "File doesn't exist, writing new file." + + with open(output_file_path, "r") as file: + existing_content = file.read() + + existing_lines = existing_content.splitlines() + current_lines = new_content.splitlines() + + if len(existing_lines) > 0 and len(current_lines) > 0: + existing_rest = "\n".join(existing_lines[1:]) + current_rest = "\n".join(current_lines[1:]) + + if existing_rest == current_rest: + return ( + False, + f"Only version hash changed ({current_lines[0]} -> {existing_lines[0]}), skipping file write.", + ) + else: + return True, "API content has changed, writing to file." + + return True, "File structure differs, writing to file." + + +def write_dump_file(content, filename="Dump", script_dir=None): + if script_dir is None: + script_dir = os.path.dirname(os.path.realpath(__file__)) + + output_file_path = os.path.join(script_dir, filename) + + should_write, message = should_write_file(output_file_path, content) + print(message) + + if should_write: + with open(output_file_path, "w") as file: + file.write(content) + + return should_write + + +def get_api_response(version_hash=None): + if version_hash: + api_dump_url = f"https://setup.rbxcdn.com/{version_hash}-Full-API-Dump.json" + try: + response = requests.get(api_dump_url) + response.raise_for_status() + return response, version_hash + except requests.RequestException as e: + print(f"Error fetching API dump for {version_hash}: {e}") + sys.exit(1) + else: + deploy_history_url = "https://setup.rbxcdn.com/DeployHistory.txt" + deploy_history = requests.get(deploy_history_url).text + lines = deploy_history.splitlines() + + for line in reversed(lines): + match = re.search(r"(version-[^\s]+)", line) + if match: + version_hash = match.group(1) + api_dump_url = ( + f"https://setup.rbxcdn.com/{version_hash}-Full-API-Dump.json" + ) + try: + response = requests.get(api_dump_url) + response.raise_for_status() + return response, version_hash + except requests.RequestException as e: + print(f"Error fetching API dump for {version_hash}: {e}")