diff --git a/source/__init__.py b/source/__init__.py index 10b47a6..b8e063e 100644 --- a/source/__init__.py +++ b/source/__init__.py @@ -1,9 +1,3 @@ -import os -import sys -from threading import Thread -from typing import Callable - - # metadata __version__ = (0, 12, 0) __author__ = 'Faraphel' @@ -21,33 +15,10 @@ Mo: int = 1_000 * Ko Go: int = 1_000 * Mo minimum_space_available: int = 15*Go +file_block_size: int = 128*Ko # global type hint TemplateSafeEval: str TemplateMultipleSafeEval: str Env: dict[str, any] - - -# useful functions -def threaded(func: Callable) -> Callable: - """ - Decorate a function to run in a separate thread - :param func: a function - :return: the decorated function - """ - - def wrapper(*args, **kwargs): - # run the function in a Daemon, so it will stop when the main thread stops - thread = Thread(target=func, args=args, kwargs=kwargs, daemon=True) - thread.start() - return thread - - return wrapper - - -def restart_program(): - """ - Restart the program - """ - os.execl(sys.executable, sys.executable, *sys.argv) diff --git a/source/gui/install.py b/source/gui/install.py index c4f40b5..f0e4b8d 100644 --- a/source/gui/install.py +++ b/source/gui/install.py @@ -17,6 +17,7 @@ from source.progress import Progress from source.translation import translate as _, translate_external from source import plugins from source import * +from source.utils import threaded import os from source.mkw.collection.Extension import Extension diff --git a/source/mkw/ExtractedGame.py b/source/mkw/ExtractedGame.py index 269af36..5d3eb17 100644 --- a/source/mkw/ExtractedGame.py +++ b/source/mkw/ExtractedGame.py @@ -1,8 +1,10 @@ +import hashlib import shutil from io import BytesIO from pathlib import Path from typing import Generator, IO, TYPE_CHECKING +from source import file_block_size from source.mkw.ModConfig import ModConfig from source.mkw.Patch.Patch import Patch from source.mkw.collection.Extension import Extension @@ -202,3 +204,20 @@ class ExtractedGame: shutil.rmtree(self.path) return converted_game + + def get_hash_map(self) -> dict[str, str]: + """ + Return a dictionary associating all the game subfiles to a hash + :return: a dictionary associating all the game subfiles to a hash + """ + md5_map: dict[str, str] = {} + + for fp in filter(lambda fp: fp.is_file(), self.path.rglob("*")): + hasher = hashlib.md5() + + with open(fp, "rb") as file: + while block := file.read(file_block_size): + hasher.update(block) + md5_map[str(fp.relative_to(self.path))] = hasher.hexdigest() + + return md5_map diff --git a/source/mkw/Game.py b/source/mkw/Game.py index 6083cc8..64a4d17 100644 --- a/source/mkw/Game.py +++ b/source/mkw/Game.py @@ -7,6 +7,7 @@ from source.mkw.collection.Extension import Extension from source.mkw.collection.Region import Region from source.option import Options from source.progress import Progress +from source.utils import comp_dict_changes from source.wt.wit import WITPath from source.translation import translate as _ @@ -123,6 +124,9 @@ class Game: yield Progress(title=_("EXTRACTION"), set_part=1) yield from self.extract(extracted_game.path) + # Riivolution hash map for the final comparaison + riivolution_original_hash_map = extracted_game.get_hash_map() + # install mystuff yield Progress(title=_("MYSTUFF"), set_part=2) mystuff_packs = options.mystuff_packs.get() @@ -158,7 +162,15 @@ class Game: yield from extracted_game.install_all_patch(mod_config) yield from extracted_game.recreate_all_szs() + # Riivolution comparaison + riivolution_patched_hash_map = extracted_game.get_hash_map() + riivolution_diff: dict[str, Path] = comp_dict_changes( + riivolution_original_hash_map, + riivolution_patched_hash_map + ) + # convert the extracted game into a file yield Progress(title=_("CONVERTING_TO_GAME_FILE"), set_part=7) converted_game: WITPath = yield from extracted_game.convert_to(output_type) if converted_game is not None: yield from Game(converted_game.path).edit(mod_config) + diff --git a/source/mkw/ModConfig.py b/source/mkw/ModConfig.py index 4596a24..9f7dab2 100644 --- a/source/mkw/ModConfig.py +++ b/source/mkw/ModConfig.py @@ -1,11 +1,11 @@ import shutil from dataclasses import dataclass, field from pathlib import Path -from typing import Generator, Callable, Iterator, Iterable, TYPE_CHECKING +from typing import Generator, Callable, Iterator, TYPE_CHECKING import json from PIL import Image -from source import threaded +from source.utils import threaded from source.mkw import Tag from source.mkw.ModSettings.ModSettingsGroup import ModSettingsGroup from source.mkw.Track.Cup import Cup diff --git a/source/option.py b/source/option.py index 5cffef6..aef9fa8 100644 --- a/source/option.py +++ b/source/option.py @@ -1,7 +1,7 @@ import json from pathlib import Path -from source import restart_program +from source.utils import restart_program class OptionLoadingError(Exception): diff --git a/source/utils.py b/source/utils.py new file mode 100644 index 0000000..12fc559 --- /dev/null +++ b/source/utils.py @@ -0,0 +1,36 @@ +import os +import sys +from threading import Thread +from typing import Callable + + +# useful functions +def threaded(func: Callable) -> Callable: + """ + Decorate a function to run in a separate thread + :param func: a function + :return: the decorated function + """ + + def wrapper(*args, **kwargs): + # run the function in a Daemon, so it will stop when the main thread stops + thread = Thread(target=func, args=args, kwargs=kwargs, daemon=True) + thread.start() + return thread + + return wrapper + + +def restart_program(): + """ + Restart the program + """ + os.execl(sys.executable, sys.executable, *sys.argv) + + +def comp_dict_changes(d_old: dict, d_new: dict) -> dict: + """ + Return a comparaison dict showing every value that changed between d_old and d_new. Deleted value are ignored. + Example : {"a": 1, "b": 3, "d": 13}, {"b": 2, "c": 10, "d": 13} -> {"b": 2, "c": 10} + """ + return {name: value for name, value in d_new.items() if d_old.get(name) != value}