From 98d34ebbacbc4274af20e6cc20684a2f4202864c Mon Sep 17 00:00:00 2001 From: pegasust Date: Fri, 4 Oct 2024 00:26:18 -0700 Subject: [PATCH] init --- .gitignore | 4 ++ README.md | 34 ++++++++++ scripts/extract_profiles.py | 122 ++++++++++++++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 scripts/extract_profiles.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4576937 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.bck +*.bak +*\~ +*.swp diff --git a/README.md b/README.md new file mode 100644 index 0000000..cf121dc --- /dev/null +++ b/README.md @@ -0,0 +1,34 @@ +# R2mods + +Knowledge dumping on ror2 modding community - how I can improve everything, as well as +keeping all of the code (or disassembled) dump in one place - tracking with a modpack +that me and my frenns are playing, duped `choncc` + +## Merging codebase across multiple sources + +This is the ultimate test for Git knowledge, you can't just go and submodule everything. +I'll maintain this along with `choncc` and dump as many versions of the mods being tracked + +## Disassemblies + +Use this IN CONJUNCTION WITH original code if available. This is mostly because code quality +from conventional codebase is no simpler than the disassembled code (surprisingly?). + +Will go onto [ilspy_dump](./ilspy_dump), before we figure out how to use `ilspycmd`, we'll +just use the GUI. + +For now, no naming convention yet, I'll try to export both a singular C# file AND the whole C# project + +- `ror2.dll` is in `//SteamLibrary/steamapps/common/Risk\ of\ Rain\ 2/Risk\ of\ Rain\ 2_Data/Managed/ror2.dll` + +## R2 profile footprint + +Per the code (TODO: ADD SOURCEGRAPH QUERY AND CODE HERE), it only contain mod manifests and configurations, +very little non-configuration files are in here because file extensions are hard-coded for filter during +profile sharing process. + +**Locations of mod profiles** + +- [r2modman](https://github.com/ebkr/r2modmanPlus/tags) is in `%AppData%/r2modmanPlus-local/RiskOfRain2/profiles` +- [thunderstore (WARNING: DOWNLOAD ON CLICK)](https://www.overwolf.com/app/Thunderstore-Thunderstore_Mod_Manager) is in `%AppData%/Thunderstore\ Mod\ Manager/DataFolder/RiskOfRain2/profiles` + diff --git a/scripts/extract_profiles.py b/scripts/extract_profiles.py new file mode 100644 index 0000000..05446dd --- /dev/null +++ b/scripts/extract_profiles.py @@ -0,0 +1,122 @@ +import os.path +import pathlib +import zipfile +import os +import sys + +import logging +import logging.config + +logging.config.dictConfig({ + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'standard': { + 'format': '%(asctime)s - %(name)s - %(levelname)s - %(message)s' + } + }, + 'handlers': { + 'console': { + 'class': 'logging.StreamHandler', + 'level': 'DEBUG', + 'formatter': 'standard', + 'stream': 'ext://sys.stderr', + }, + }, + 'loggers': { + '': { # root logger + 'handlers': ['console'], + 'level': 'DEBUG', + 'propagate': True + }, + } +}) + +logger = logging.getLogger() + +def extract(zip_path: str, dest: str, skip_checks: bool = False): + """ + Did you know that `.r2z` is just a badly compressed zip? + + Utilized to automate version controlling configs and plugin manifests + + NB: Do not use this to install profiles. Most mod manager has an install step + after extracting. + + `dest` needs to be profiled path, that is, should be something like + `//profiles/my-profile`, not just `//profiles` with `zip_path` + `//exports/my-profile` + """ + p = pathlib.Path(zip_path) + to_dir = pathlib.Path(dest) + if to_dir.is_file(): + raise KeyError("Expected dest to be directory or be recursively made") + + if not p.suffix.lower() == ".r2z" and not skip_checks: + raise KeyError("Expected r2z suffix, use `skip_checks` to bypass") + + profile_dir = to_dir + os.makedirs(profile_dir, exist_ok=True) + + with zipfile.ZipFile(p, 'r') as zip_ref: + logging.info(f"Extracting {p} to {profile_dir}") + zip_ref.extractall(profile_dir) + +def repo_root_impure(): + cwd = pathlib.Path.cwd() + return cwd.parent if cwd.name == "script" else cwd + +def main(): + dry_run = os.getenv("DRY_RUN", "").lower() not in ("false", "0", "no", "nope") + if dry_run: logger.debug("Doing dry run") + + ror2_f = "RiskOfRain2" + MODMAN_PATH_ENV = "ROR2_MODMAN_PATH" + MODMAN_CANDIDATES = [ + pathlib.Path.home().joinpath("AppData","Roaming", *modman_segs, ror2_f) + for modman_segs in (("r2modmanPlus-local",), ("Thunderstore Mod Manager", "DataFolder")) + ] + mod_man_roots = [pathlib.Path(f) for f in os.getenv(MODMAN_PATH_ENV, "").split(',') if f] or MODMAN_CANDIDATES + mod_man_roots = [e for e in mod_man_roots if e.is_dir()] + logging.debug(f"Using {mod_man_roots=}") + + dest_base_loc = pathlib.Path(os.getenv("DEST_BASE_LOC") or repo_root_impure().joinpath("r2_profiles")) + if dest_base_loc.is_file(): + raise KeyError("{dest_base_loc=} is a file, expected directory or able to recursively make dirs") + if not dry_run: + os.makedirs(dest_base_loc, exist_ok=True) + logging.debug(f"Using {dest_base_loc=}") + + if mod_man_roots == []: + raise KeyError(f'No mod manager found locally, set "{MODMAN_PATH_ENV}" env or check {MODMAN_CANDIDATES}') + + def profile_of(r2z_fname: pathlib.Path): + stem = r2z_fname.stem + v = stem.split("_") + if len(v) == 1: + return stem + *head, tail = v + if tail.isnumeric(): + return "_".join(head) + return stem + # latest profiles (based on OS's reported mtime, disregard r2modman's stem) + raw_profiles = [ + file + for file in sorted( + # flatten from both roots + [f for root in mod_man_roots for f in root.joinpath("exports").iterdir()], + key=lambda e: e.stat().st_mtime, + # dict comprehension chooses latter entry if duplicate key + reverse=False, + ) + if file.is_file() and file.suffix.lower() == ".r2z" + ] + profiles = { profile_of(file): file for file in raw_profiles } + logger.debug(f"{profiles=}") + if not dry_run: + for prof, r2z in profiles.items(): + extract(r2z, dest_base_loc.joinpath(prof)) + logger.info(f"done extracting {len(profiles)} profiles") + +if __name__ == "__main__": + main()