├── .gitignore ├── LICENSE ├── README.md ├── compile.py ├── config.ini ├── decompile.py ├── src ├── helpers │ ├── __init__.py │ └── injector.py ├── main.py ├── money.py ├── relationship.py └── skill.py └── utils ├── constants.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # IntelliJ 132 | .idea/ 133 | .DS_Store 134 | 135 | # Build and decompiled game files 136 | build/ 137 | game/ 138 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 lli 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TheSims4ScriptModBuilder 2 | Full Instruction: https://medium.com/@lli-1990/tutorial-write-the-sims-4-script-mod-with-python-part-1-introduction-6d49d0dcab92 3 | 4 | 5 | ## What is this tutorial for? 6 | 7 | The Sims4 script mod is a type of mod that allows creators to access and modify the game data by python script directly. For example, in the EA official cheat instruction, we can enter motherlode in the cheat console to get 50k simoleons. A script mod could achieve this with the following process: find what is the simoleons for the current sim, add 50k, then update it. 8 | One of the most popular script mods for The Sims4 is MC Command Center, with this module, you could expand the functionality and story progression within the game. 9 | 10 | I am a The Sims player, and also a software engineer. After using the MC Command Center for years, I would like to start writing some script mods myself. However, MCCC is not an open-source project, gladly, I am able to find some other Sims4 mod projects on Github. This tutorial is a basic version of the existing projects, a beginner guide, that helps new creators to build their ideal world within The Sims. 11 | -------------------------------------------------------------------------------- /compile.py: -------------------------------------------------------------------------------- 1 | import fnmatch 2 | import os 3 | import py_compile 4 | import shutil 5 | import string 6 | from pathlib import Path 7 | from zipfile import PyZipFile, ZIP_STORED 8 | 9 | from utils.constants import * 10 | from utils.utils import prepare_directory 11 | 12 | 13 | def compile_py(src: string, dest: string): 14 | # Compile the mod 15 | for root, dirs, files in os.walk(src): 16 | for filename in fnmatch.filter(files, '*.py'): 17 | src_py = root + '/' + filename 18 | relative_path = str(Path(root).relative_to(src)) 19 | desc_path = dest + '/' + relative_path 20 | compile_pyc = desc_path + '/' + filename.replace('.py', '.pyc') 21 | 22 | py_compile.compile(src_py, compile_pyc) 23 | 24 | 25 | def zip_ts4script(src: string): 26 | # zip 27 | zip_mod_file = mod_name + '.ts4script' 28 | compiled_mod = PyZipFile(project_build_dir + '/' + zip_mod_file, mode='w', compression=ZIP_STORED, allowZip64=True, 29 | optimize=2) 30 | for root, dirs, files in os.walk(src): 31 | relative_path = str(Path(root).relative_to(src)) 32 | for file in files: 33 | compiled_mod.write(os.path.join(root, file), relative_path + '/' + file) 34 | compiled_mod.close() 35 | 36 | return zip_mod_file 37 | 38 | 39 | def start_compile(): 40 | my_game_mod_dir = mods_dir + '/' + mod_name 41 | 42 | prepare_directory(project_build_dir) 43 | prepare_directory(project_build_compile_dir) 44 | prepare_directory(my_game_mod_dir) 45 | 46 | compile_py(project_src_dir, project_build_compile_dir) 47 | zip_mod_file = zip_ts4script(project_build_compile_dir) 48 | 49 | # Copy it over to the mods folder 50 | shutil.copyfile(project_build_dir + '/' + zip_mod_file, my_game_mod_dir + '/' + zip_mod_file) 51 | 52 | 53 | start_compile() 54 | -------------------------------------------------------------------------------- /config.ini: -------------------------------------------------------------------------------- 1 | [User] 2 | UserName = myName 3 | 4 | [Directory] 5 | # Default path for mac 6 | Sims4ModDir = /Users//Documents/Electronic Arts/The Sims 4/Mods 7 | Sims4GameContentDir = /Users//Applications/The Sims 4.app/Contents 8 | ProjectDir = /Users//Desktop/TheSims4ScriptModBuilder 9 | GameContentPython = /Python 10 | 11 | # Default path for windows 12 | # Sims4ModDir = C:\Users\\OneDrive\Documents\Electronic Arts\The Sims 4\Mods 13 | # Sims4GameContentDir = C:\Program Files (x86)\Origin Games\The Sims 4 14 | # ProjectDir = C:\Users\\Documents\lli-projects\TheSims4ScriptModBuilder 15 | # GameContentPython = /Game/Bin/Python 16 | 17 | [Dependency] 18 | # For mac: which uncompyle6 19 | Uncompyle6Path = /Library/Frameworks/Python.framework/Versions/3.7/bin/uncompyle6 20 | 21 | # For windows: where uncompyle6 22 | # Uncompyle6Path = C:\Users\\AppData\Local\Programs\Python\Python37\Scripts\uncompyle6.exe 23 | 24 | # Number of worker processes 25 | workers = 10 26 | 27 | [Mod] 28 | # add your mod name here 29 | Name = MyFirstMod -------------------------------------------------------------------------------- /decompile.py: -------------------------------------------------------------------------------- 1 | import fnmatch 2 | import multiprocessing 3 | import os 4 | import shutil 5 | import string 6 | import threading 7 | from pathlib import Path 8 | from subprocess import run 9 | from zipfile import PyZipFile 10 | from utils.constants import * 11 | from utils.utils import create_directory, prepare_directory 12 | from multiprocessing import Pool 13 | 14 | 15 | # copy the zip files 16 | def copy_zip(src: string, dest: string): 17 | shutil.copytree(src, dest) 18 | 19 | 20 | def unzip(src: string, dest: string): 21 | for file in os.listdir(src): 22 | if file.endswith('.zip'): 23 | PyZipFile(src + '/' + file).extractall(dest + '/' + file.title().split('.')[0].lower()) 24 | 25 | 26 | def decompile_worker(args): 27 | dest_path, src_file = args 28 | print("Decompiling %s" % src_file) 29 | rv = run([uncompyle6, "-o", dest_path, src_file], text=True, 30 | capture_output=True) 31 | print("Done %s" % src_file) 32 | return [rv.returncode, rv.stderr] 33 | 34 | 35 | def decompile(src: string): 36 | print('start decompiling ' + src) 37 | 38 | # total = 0 39 | # success = 0 40 | todo = [] 41 | 42 | for root, dirs, files in os.walk(src): 43 | for filename in fnmatch.filter(files, "*.pyc"): 44 | # print('.', end='') if success % 30 or success == 0 else print('.') # next line 45 | # total += 1 46 | 47 | src_file_path = str(os.path.join(root, filename)) 48 | relative_path = str(Path(root).relative_to(project_game_unzip_dir)) 49 | desc_path = project_game_decompile_dir + '/' + relative_path 50 | 51 | target_file_name = filename.replace('.pyc', '.py') 52 | todo.append([desc_path + "/" + target_file_name, src_file_path]) 53 | 54 | with Pool(num_decompilers) as pool: 55 | rv = pool.map(decompile_worker, todo) 56 | 57 | total = len(todo) 58 | success = sum([1 for x in rv if x[0] == 0 and not x[1]]) 59 | 60 | print('success rate: ' + str(round((success * 100) / total, 2)) + '%') 61 | 62 | 63 | def copy_files_and_unzip(): 64 | # The Sims 4.app/Contents/Python generated.zip 65 | # The Sims 4.app/Contents/Data/Simulation/Gameplay -> base.zip, core.zip, simulation.zip 66 | prepare_directory(project_game_zip_dir) 67 | copy_zip(game_content_python, project_game_zip_dir + project_game_python) 68 | copy_zip(game_content_gameplay, project_game_zip_dir + project_game_gameplay) 69 | 70 | prepare_directory(project_game_unzip_dir) 71 | unzip(project_game_zip_dir + project_game_python, project_game_unzip_dir) 72 | unzip(project_game_zip_dir + project_game_gameplay, project_game_unzip_dir) 73 | 74 | 75 | def run_decompile(): 76 | for folder in [project_game_unzip_dir + '/' + x for x in os.listdir(project_game_unzip_dir)]: 77 | decompile(folder) 78 | 79 | 80 | def prepare(): 81 | done = False 82 | while not done: 83 | try: 84 | create_directory(project_dir + '/game') 85 | copy_files_and_unzip() 86 | 87 | prepare_directory(project_game_decompile_dir) 88 | except PermissionError: 89 | continue 90 | done = True 91 | return 92 | 93 | 94 | if __name__ == '__main__': 95 | multiprocessing.freeze_support() 96 | prepare() 97 | run_decompile() 98 | -------------------------------------------------------------------------------- /src/helpers/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuquanLi/TheSims4ScriptModBuilder/6b41f49384a1058d1141f59035a857bc57323bff/src/helpers/__init__.py -------------------------------------------------------------------------------- /src/helpers/injector.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | from functools import wraps 3 | 4 | 5 | def inject(target_object, target_function_name, safe=False): 6 | if safe and not hasattr(target_object, target_function_name): 7 | def _self_wrap(wrap_function): 8 | return wrap_function 9 | 10 | return _self_wrap 11 | 12 | def _wrap_original_function(original_function, new_function): 13 | @wraps(original_function) 14 | def _wrapped_function(*args, **kwargs): 15 | if type(original_function) is property: 16 | return new_function(original_function.fget, *args, **kwargs) 17 | else: 18 | return new_function(original_function, *args, **kwargs) 19 | 20 | if inspect.ismethod(original_function): 21 | return classmethod(_wrapped_function) 22 | elif type(original_function) is property: 23 | return property(_wrapped_function) 24 | else: 25 | return _wrapped_function 26 | 27 | def _injected(wrap_function): 28 | original_function = getattr(target_object, target_function_name) 29 | setattr(target_object, target_function_name, _wrap_original_function(original_function, wrap_function)) 30 | 31 | return wrap_function 32 | 33 | return _injected 34 | -------------------------------------------------------------------------------- /src/main.py: -------------------------------------------------------------------------------- 1 | import sims4.commands 2 | 3 | 4 | @sims4.commands.Command('hello', command_type=sims4.commands.CommandType.Live) 5 | def hello_world(_connection=None): 6 | output = sims4.commands.CheatOutput(_connection) 7 | output("Hello World") 8 | 9 | 10 | @sims4.commands.Command('cheats_help', command_type=sims4.commands.CommandType.Live) 11 | def hello_world(_connection=None): 12 | output = sims4.commands.CheatOutput(_connection) 13 | output("add_money : add money to your current sim") 14 | output("remove_money : remove money from your current sim") 15 | output("max_skill : set the skill to max level") 16 | output("become_friend : become friend with the target sim") 17 | output("become_lover : become lover with the target sim") 18 | -------------------------------------------------------------------------------- /src/money.py: -------------------------------------------------------------------------------- 1 | import services 2 | import sims4.commands 3 | from protocolbuffers import Consts_pb2 4 | 5 | 6 | # Money 7 | @sims4.commands.Command('add_money', command_type=(sims4.commands.CommandType.Live)) 8 | def add_money(amount: int = 0, _connection=None): 9 | tgt_client = services.client_manager().get(_connection) 10 | output = sims4.commands.CheatOutput(_connection) 11 | 12 | modify_fund_helper(amount, Consts_pb2.TELEMETRY_MONEY_CHEAT, tgt_client.active_sim) 13 | 14 | output("Add ${0}".format(amount)) 15 | 16 | 17 | @sims4.commands.Command('remove_money', command_type=(sims4.commands.CommandType.Live)) 18 | def remove_money(amount: int = 0, _connection=None): 19 | tgt_client = services.client_manager().get(_connection) 20 | output = sims4.commands.CheatOutput(_connection) 21 | current_amount = tgt_client.active_sim.family_funds.money 22 | 23 | # if the amount is higher than family fund, only clear up the family fund 24 | is_amount_overflow = amount > current_amount 25 | remove_amount = current_amount if is_amount_overflow else amount 26 | 27 | modify_fund_helper(-remove_amount, Consts_pb2.TELEMETRY_MONEY_CHEAT, tgt_client.active_sim) 28 | 29 | if is_amount_overflow: 30 | output("Current family fund is not enough") 31 | 32 | output("Remove ${0}".format(remove_amount)) 33 | 34 | 35 | def modify_fund_helper(amount, reason, sim): 36 | if amount > 0: 37 | sim.family_funds.add(amount, reason, sim) 38 | else: 39 | sim.family_funds.try_remove(-amount, reason, sim) 40 | -------------------------------------------------------------------------------- /src/relationship.py: -------------------------------------------------------------------------------- 1 | import services 2 | import sims4.commands 3 | from server_commands.argument_helpers import SimInfoParam, TunableInstanceParam 4 | 5 | RELATIONSHIP_MAX_SCORE = 100 6 | FRIEND_TYPE = 'LTR_Friendship_Main' 7 | ROMANCE_TYPE = 'LTR_Romance_Main' 8 | 9 | 10 | # become_friend 11 | # the param HAS TO be : info1: SimInfoParam, info2: SimInfoParam 12 | @sims4.commands.Command('become_friend', command_type=(sims4.commands.CommandType.Live)) 13 | def become_friend(info1: SimInfoParam, _connection=None): 14 | tgt_client = services.client_manager().get(_connection) 15 | output = sims4.commands.CheatOutput(_connection) 16 | sim = tgt_client.active_sim 17 | 18 | if info1 is None: 19 | output("target sim not exists") 20 | return 21 | 22 | sim.relationship_tracker.set_relationship_score(info1.id, RELATIONSHIP_MAX_SCORE, 23 | TunableInstanceParam(sims4.resources.Types.STATISTIC)(FRIEND_TYPE)) 24 | output("become friends successfully.") 25 | 26 | 27 | @sims4.commands.Command('become_lover', command_type=(sims4.commands.CommandType.Live), ) 28 | def become_lover(info1: SimInfoParam, _connection=None): 29 | tgt_client = services.client_manager().get(_connection) 30 | output = sims4.commands.CheatOutput(_connection) 31 | sim = tgt_client.active_sim 32 | 33 | if info1 is None: 34 | output("target sim not exists") 35 | return 36 | 37 | sim.relationship_tracker.set_relationship_score(info1.id, RELATIONSHIP_MAX_SCORE, 38 | TunableInstanceParam(sims4.resources.Types.STATISTIC)(ROMANCE_TYPE)) 39 | output("become lovers successfully.") 40 | 41 | 42 | @sims4.commands.Command('assign_friend', command_type=(sims4.commands.CommandType.Live)) 43 | def assign_friend(info1: SimInfoParam, info2: SimInfoParam, _connection=None): 44 | output = sims4.commands.CheatOutput(_connection) 45 | 46 | if info1 is None or info2 is None: 47 | output("at least one of the target sim does not exist") 48 | return 49 | 50 | info1.relationship_tracker.set_relationship_score(info2.id, RELATIONSHIP_MAX_SCORE, 51 | TunableInstanceParam(sims4.resources.Types.STATISTIC)( 52 | FRIEND_TYPE)) 53 | output("set friends successfully.") 54 | 55 | 56 | @sims4.commands.Command('assign_lover', command_type=(sims4.commands.CommandType.Live)) 57 | def assign_lover(info1: SimInfoParam, info2: SimInfoParam, _connection=None): 58 | output = sims4.commands.CheatOutput(_connection) 59 | 60 | if info1 is None or info2 is None: 61 | output("at least one of the target sim does not exist") 62 | return 63 | 64 | info1.relationship_tracker.set_relationship_score(info2.id, RELATIONSHIP_MAX_SCORE, 65 | TunableInstanceParam(sims4.resources.Types.STATISTIC)( 66 | ROMANCE_TYPE)) 67 | output("set lovers successfully.") 68 | -------------------------------------------------------------------------------- /src/skill.py: -------------------------------------------------------------------------------- 1 | import string 2 | 3 | import services 4 | import sims4.commands 5 | import statistics.skill 6 | from server_commands.argument_helpers import TunableInstanceParam 7 | 8 | 9 | def get_skill_type(skill: string) -> TunableInstanceParam(sims4.resources.Types.STATISTIC): 10 | skill_map = { 11 | "singing": "Major_Singing", 12 | "herbalism": "Major_Herbalism", 13 | "baking": "Major_Baking", 14 | "charisma": "Major_Charisma", 15 | "comedy": "Major_Comedy", 16 | "cooking": "Major_HomestyleCooking", 17 | "dancing": "Minor_Dancing", 18 | "djmixing": "Major_DJMixing", 19 | "fishing": "Major_Fishing", 20 | "fitness": "Skill_Fitness", 21 | "gardening": "Major_Gardening", 22 | "gourmetcooking": "Major_GourmetCooking", 23 | "guitar": "Major_Guitar", 24 | "handiness": "Major_Handiness", 25 | "logic": "Major_Logic", 26 | "mischief": "Major_Mischief", 27 | "bartending": "Major_Bartending", 28 | "painting": "Major_Painting", 29 | "photography": "Major_Photography", 30 | "piano": "Major_Piano", 31 | "programming": "Major_Programming", 32 | "rocketscience": "Major_RocketScience", 33 | "videogaming": "Major_VideoGaming", 34 | "violin": "Major_Violin", 35 | "wellness": "Major_Wellness", 36 | "writing": "Major_Writing", 37 | "creativity": "Skill_Child_Creativity", 38 | "mental": "Skill_Child_Mental", 39 | "motor": "Skill_Child_Motor", 40 | "social": "Skill_Child_Social", 41 | "imagination": "Toddler_Imagination", 42 | "communication": "Toddler_Communication", 43 | "movement": "Toddler_Movement", 44 | "potty": "Toddler_Potty", 45 | "thinking": "Toddler_Thinking", 46 | "bowling": "Skill_Bowling", 47 | "parenting": "Skill_Parenting", 48 | "dogtraining": "Skill_DogTraining", 49 | "veterinarian": "Major_Veterinarian", 50 | "archaeology": "Major_Archaeology", 51 | "localculture": "Minor_LocalCulture", 52 | "skating": "Hidden_Skating", 53 | "flowerarranging": "AdultMajor_FlowerArranging", 54 | } 55 | return TunableInstanceParam(sims4.resources.Types.STATISTIC)(skill_map[skill]) 56 | 57 | 58 | # skill 59 | # stat_type: TunableInstanceParam(sims4.resources.Types.STATISTIC) 60 | @sims4.commands.Command('max_skill', command_type=(sims4.commands.CommandType.Live), 61 | console_type=(sims4.commands.CommandType.Cheat)) 62 | def max_skill(search_string=None, _connection=None): 63 | tgt_client = services.client_manager().get(_connection) 64 | sim = tgt_client.active_sim 65 | 66 | stat_type = get_skill_type(search_string.lower()) 67 | stat = sim.commodity_tracker.get_statistic(stat_type) 68 | 69 | if stat is None: 70 | stat = sim.commodity_tracker.add_statistic(stat_type) 71 | if stat is None: 72 | return 73 | 74 | if not isinstance(stat, statistics.skill.Skill): 75 | return 76 | 77 | sim.commodity_tracker.set_user_value(stat_type, stat_type.max_level) 78 | -------------------------------------------------------------------------------- /utils/constants.py: -------------------------------------------------------------------------------- 1 | import configparser 2 | 3 | config = configparser.ConfigParser() 4 | config.read('config.ini') 5 | 6 | project_dir = config['Directory']['ProjectDir'] 7 | game_content_dir = config['Directory']['Sims4GameContentDir'] 8 | mods_dir = config['Directory']['Sims4ModDir'] 9 | uncompyle6 = config['Dependency']['Uncompyle6Path'] 10 | mod_name = config['Mod']['Name'] 11 | num_decompilers = config.getint('Dependency','workers') 12 | 13 | game_content_python = game_content_dir + config['Directory']['GameContentPython'] 14 | game_content_gameplay = game_content_dir + '/Data/Simulation/Gameplay' 15 | 16 | project_game_zip_dir = project_dir + '/game/zip' 17 | project_game_unzip_dir = project_dir + '/game/unzip' 18 | project_game_decompile_dir = project_dir + '/game/decompile' 19 | project_game_python = '/python' 20 | project_game_gameplay = '/gameplay' 21 | project_build_dir = project_dir + '/build' 22 | project_build_compile_dir = project_build_dir + '/compile' 23 | project_src_dir = project_dir + '/src' 24 | -------------------------------------------------------------------------------- /utils/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | import shutil 4 | import string 5 | 6 | 7 | def create_directory(path: string): 8 | Path(path).mkdir(parents=True, exist_ok=True) 9 | 10 | 11 | # remove all files in this directory 12 | def clean_directory(path: string): 13 | if os.path.exists(path) and os.path.isdir(path): 14 | shutil.rmtree(path) 15 | 16 | 17 | def prepare_directory(path: string): 18 | clean_directory(path) 19 | create_directory(path) 20 | 21 | --------------------------------------------------------------------------------