From f1800e8d8791c40b21c79f7d7b8c07d288b3a156 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Wed, 3 Sep 2025 01:29:33 +0300 Subject: [PATCH 01/28] restructured commands --- scr/commands/basic/dependencies.py | 69 ++++-- scr/commands/basic/help.py | 29 ++- scr/commands/basic/info.py | 52 ++++- scr/commands/commands.py | 230 -------------------- scr/commands/obfuscation/obfuscate.py | 57 +++++ scr/commands/obfuscation/obfuscatelegacy.py | 42 ++++ scr/config.py | 35 ++- scr/pyshield.py | 50 ++++- scr/utils/optionsParser.py | 4 +- 9 files changed, 293 insertions(+), 275 deletions(-) delete mode 100644 scr/commands/commands.py diff --git a/scr/commands/basic/dependencies.py b/scr/commands/basic/dependencies.py index e3a5397..9d4397a 100644 --- a/scr/commands/basic/dependencies.py +++ b/scr/commands/basic/dependencies.py @@ -2,24 +2,61 @@ from utils.logger import Log from config import Command +class Dependencies(Command): + exclusiveOptions = [["--show", "--install", "--uninstall", "--update"]] + requiredOptions = [["--show", "--install", "--uninstall", "--update"]] -def DependenciesHandler(this: Command): - dependencies = ["cryptography", "pycryptodome", "cython", "nuitka", "colorama", "setuptools"] + options = { + "--quiet": False, + "--log": "", + "--no-color ": False, + "--no-input": False, - if this.options["--show"]: - string = "" - for dep in dependencies: - string+=f"\n {dep}" - Log.Custom(f"Project's dependencies:{string}") + "--show": False, + "--install": False, + "--uninstall": False, + "--update": False, + } - if this.options["--install"]: - for dep in dependencies: - system(f"pip install {f" --log {Log.logFile}" if Log.logFile else ""}{ "--quiet" if Log.quiet else ""}{ "--no-input" if Log.noInput else ""} {dep}") + def Handler(self): + dependencies = ["cryptography", "pycryptodome", "cython", "nuitka", "colorama", "setuptools"] - if this.options["--uninstall"]: - for dep in dependencies: - system(f"pip uninstall {f" --log {Log.logFile}" if Log.logFile else ""}{ "--quiet" if Log.quiet else ""}{ "--no-input" if Log.noInput else ""} {dep}") + if self.options["--show"]: + string = "" + for dep in dependencies: + string+=f"\n {dep}" + Log.Custom(f"Project's dependencies:{string}") - if this.options["--update"]: - for dep in dependencies: - system(f"pip install --upgrade {f" --log {Log.logFile}" if Log.logFile else ""}{ "--quiet" if Log.quiet else ""}{ "--no-input" if Log.noInput else ""} {dep}") \ No newline at end of file + if self.options["--install"]: + for dep in dependencies: + system(f"pip install {f" --log {Log.logFile}" if Log.logFile else ""}{ "--quiet" if Log.quiet else ""}{ "--no-input" if Log.noInput else ""} {dep}") + + if self.options["--uninstall"]: + for dep in dependencies: + system(f"pip uninstall {f" --log {Log.logFile}" if Log.logFile else ""}{ "--quiet" if Log.quiet else ""}{ "--no-input" if Log.noInput else ""} {dep}") + + if self.options["--update"]: + for dep in dependencies: + system(f"pip install --upgrade {f" --log {Log.logFile}" if Log.logFile else ""}{ "--quiet" if Log.quiet else ""}{ "--no-input" if Log.noInput else ""} {dep}") + + help = f''' +Usage: + py-shield dependencies [options] +Example: + py-shield dependencies --quiet --no-input y --install + +Note: + ` -> only one option from a group can be used. + * -> required option. + +Options: + --help -> show help for commands. + --quiet -> give less output. + --log -> write all logs to a file. + --no-color -> suppress colored output. + --no-input -> disable prompting for input. + + --show*` -> show all dependencies of the program. + --install*` -> install all dependencies of the program. + --uninstall*` -> uninstall all dependencies of the program. + --update*` -> update all dependencies of the program''' \ No newline at end of file diff --git a/scr/commands/basic/help.py b/scr/commands/basic/help.py index bb12e64..a1a0b83 100644 --- a/scr/commands/basic/help.py +++ b/scr/commands/basic/help.py @@ -1,5 +1,30 @@ from utils.logger import Log from config import Command -def HelpHandler(command: Command): - Log.Custom(command.help) \ No newline at end of file +class Help(Command): + exclusiveOptions = None + requiredOptions = None + options = None + + def Handler(command: Command = None): + Log.Custom(command.help) + + help = f''' +Usage: + py-shield [options] +Example: + py-shield obfuscate --help + +Commands: + obfuscate -> obfuscate code using advanced techniques. + obfuscatelegacy -> obfuscate code using legacy techniques. + dependencies -> command to work with dependencies. + info -> show general information about the program. + help -> show general help. + +General Options: + --help -> show help for commands. + --quiet -> give less output. + --log -> write all logs to a file. + --no-color -> suppress colored output. + --no-input -> disable prompting for input.''' \ No newline at end of file diff --git a/scr/commands/basic/info.py b/scr/commands/basic/info.py index 9bb5d3a..d5cf417 100644 --- a/scr/commands/basic/info.py +++ b/scr/commands/basic/info.py @@ -1,15 +1,49 @@ from utils.logger import Log, Fore from config import Command, NAME, VERSION, AUTHOR, URL, DESCRIPTION -def InfoHandler(this: Command): - if this.options["--all"]: - Log.Custom(f"{NAME} version {VERSION}\nby {AUTHOR}\n{DESCRIPTION}\nRepo: {Fore.BLUE if Log.colored else ""}{URL}{Fore.RESET}") +class Info(Command): + exclusiveOptions = [["--all", "--version", "--url", "--description"]] + requiredOptions = [["--all", "--version", "--url", "--description"]] - elif this.options["--version"]: - Log.Custom(f"{NAME} version {VERSION}") + options = { + "--log": "", + "--no-color": False, - elif this.options["--url"]: - Log.Custom(f"Repo: {Fore.BLUE if Log.colored else ""}{URL}{Fore.RESET}") + "--all": False, + "--version": False, + "--url": False, + "--description": False, + } - elif this.options["--description"]: - Log.Custom(f"{DESCRIPTION}") + def Handler(self): + if self.options["--all"]: + Log.Custom(f"{NAME} version {VERSION}\nby {AUTHOR}\n{DESCRIPTION}\nRepo: {Fore.BLUE if Log.colored else ""}{URL}{Fore.RESET}") + + elif self.options["--version"]: + Log.Custom(f"{NAME} version {VERSION}") + + elif self.options["--url"]: + Log.Custom(f"Repo: {Fore.BLUE if Log.colored else ""}{URL}{Fore.RESET}") + + elif self.options["--description"]: + Log.Custom(f"{DESCRIPTION}") + + help = f''' +Usage: + py-shield info [options] +Example: + py-shield info --all + +Note: + ` -> only one option from a group can be used. + * -> required option. + +Options: + --help -> show help for commands. + --log -> write all logs to a file. + --no-color -> suppress colored output. + + --all*` -> show all information about the program. + --version*` -> show version of the program. + --url*` -> show URL of program's github repo. + --description*` -> show description of the program.''' diff --git a/scr/commands/commands.py b/scr/commands/commands.py deleted file mode 100644 index 7d45b11..0000000 --- a/scr/commands/commands.py +++ /dev/null @@ -1,230 +0,0 @@ -from config import Command - -from commands.basic.dependencies import DependenciesHandler -from commands.basic.help import HelpHandler -from commands.basic.info import InfoHandler - -from commands.obfuscation.obfuscate import Obfuscation -from commands.obfuscation.obfuscatelegacy import ObfuscationLegacy - -# class Name_of_the_command(Command): #note: only first letter should be capital -# -# #May be left not initialized if your command has no options# -# exclusiveOptions = [["--install","--uninstall"], ["--up","--down"]] #groups of options that can't be used together -# requiredOptions = ["entrypoint", [""]] #options and groups(any option from a group is required) that are required -# -# options = { #all the options that your command has. note: don't write help here, it's hendeled elsewhere -# #General Options -# "--quiet": False, -# "--log": "", -# "--no-color ": False, -# "--no-input": False, -# -# #Your Options -# "--option1": False, -# "entrypoint": "" #only 1 fixed option name used to store program entrypoint file path -# } -# -# #Should be always initialized# -# handler = None #command handler that will be called when the command is executed -# help = f'''I\'ll help u''' #help message for the command - -class Obfuscate(Command): - exclusiveOptions = [] - requiredOptions = ["entrypoint", ["--hashdata", "--fernet", "--aes", "--chacha", "--salsa" "--base64", "--recursive"]] - options = { - "--quiet": False, - "--log": "", - "--no-color ": False, - "--no-input": False, - - "--hashdata": False, - "--fernet": False, - "--aes": False, - "--chacha": False, - "--salsa": False, - "--base64": False, - "--recursive": 0, - "--no-protect": False, - "--dirs": [], - "--files": [], - "--output": "", - "--follow-imports" : False, - "entrypoint": "" - } - - handler = Obfuscation - - help = f''' -Usage: - py-shield obfuscate [options] main.py -Example: - py-shield obfuscate --hashdata --aes --follow-imports main.py - -Notes: - text,text -> to add more than one arg to option. - main.py -> the entry point of your program. - -Options: - --help -> show help for commands. - --quiet -> give less output. - --log -> write all logs to a file. - --no-color -> suppress colored output. - --no-input -> disable prompting for input. - - --hashdata -> convert all strings and var names into hash. - --fernet -> obfuscation and encryption using fernet. - --aes -> obfuscation and encryption using aes256. - --chacha -> obfuscation and encryption using chacha20. - --salsa -> obfuscation and encryption using salsa20. - --base64 -> obfuscation and encryption using base64. - --recursive -> not strong but good if u need to hide ur prog from AVs. - --no-protect -> disable file modification protection. - --dirs -> obfuscate all files in dir. - --files -> files for obfuscation. - --output -> output dir. - --follow-imports -> add all imports to the protected script.''' - -class Obfuscatelegacy(Command): - exclusiveOptions = [] - requiredOptions = ["--loops", "--mode", ["--files", "--dirs"]] - options = { - "--quiet": False, - "--log": "", - "--no-color ": False, - "--no-input": False, - - "--loops": 0, - "--mode": 0, - "--dirs": [], - "--files": [], - "--output": "" - } - - handler = ObfuscationLegacy - - help = f''' -Usage: - py-shield obfuscatelegacy [options] -Example: - py-shield obfuscatelegacy --loops 3 --mode 2 --file code.py - -Notes: - * -> required option. - text,text -> to add more than one arg to option. - -Options: - --help -> show help for commands. - --quiet -> give less output. - --log -> write all logs to a file. - --no-color -> suppress colored output. - --no-input -> disable prompting for input. - - --loops * -> number of obfuscation loops. - --mode * -> obfuscation mode(1-4) as bigger number as better obfuscation but the output file is larger. - --dirs * -> obfuscate all files in dir(required files or/and dir). - --files * -> files for obfuscation(required files or/and dir). - --output -> output dir.''' - -class Dependencies(Command): - exclusiveOptions = [["--show", "--install", "--uninstall", "--update"]] - requiredOptions = [["--show", "--install", "--uninstall", "--update"]] - - options = { - "--quiet": False, - "--log": "", - "--no-color ": False, - "--no-input": False, - - "--show": False, - "--install": False, - "--uninstall": False, - "--update": False, - } - - handler = DependenciesHandler - - help = f''' -Usage: - py-shield dependencies [options] -Example: - py-shield dependencies --quiet --no-input y --install - -Note: - ` -> only one option from a group can be used. - * -> required option. - -Options: - --help -> show help for commands. - --quiet -> give less output. - --log -> write all logs to a file. - --no-color -> suppress colored output. - --no-input -> disable prompting for input. - - --show*` -> show all dependencies of the program. - --install*` -> install all dependencies of the program. - --uninstall*` -> uninstall all dependencies of the program. - --update*` -> update all dependencies of the program''' - -class Info(Command): - exclusiveOptions = [["--all", "--version", "--url", "--description"]] - requiredOptions = [["--all", "--version", "--url", "--description"]] - - options = { - "--log": "", - "--no-color": False, - - "--all": False, - "--version": False, - "--url": False, - "--description": False, - } - - handler = InfoHandler - - help = f''' -Usage: - py-shield info [options] -Example: - py-shield info --all - -Note: - ` -> only one option from a group can be used. - * -> required option. - -Options: - --help -> show help for commands. - --log -> write all logs to a file. - --no-color -> suppress colored output. - - --all*` -> show all information about the program. - --version*` -> show version of the program. - --url*` -> show URL of program's github repo. - --description*` -> show description of the program.''' - -class Help(Command): - exclusiveOptions = None - requiredOptions = None - options = None - - handler = HelpHandler - - help = f''' -Usage: - py-shield [options] -Example: - py-shield obfuscate --help - -Commands: - obfuscate -> obfuscate code using advanced techniques. - obfuscatelegacy -> obfuscate code using legacy techniques. - dependencies -> command to work with dependencies. - info -> show general information about the program. - help -> show general help. - -General Options: - --help -> show help for commands. - --quiet -> give less output. - --log -> write all logs to a file. - --no-color -> suppress colored output. - --no-input -> disable prompting for input.''' \ No newline at end of file diff --git a/scr/commands/obfuscation/obfuscate.py b/scr/commands/obfuscation/obfuscate.py index 2c6586f..56e911e 100644 --- a/scr/commands/obfuscation/obfuscate.py +++ b/scr/commands/obfuscation/obfuscate.py @@ -5,6 +5,63 @@ from utils.langMgr import RemoveComments, GetImports from utils.obfuscation import MainObfuscation +class Obfuscate(Command): + exclusiveOptions = [] + requiredOptions = ["entrypoint", ["--hashdata", "--fernet", "--aes", "--chacha", "--salsa" "--base64", "--recursive"]] + options = { + "--quiet": False, + "--log": "", + "--no-color ": False, + "--no-input": False, + + "--hashdata": False, + "--fernet": False, + "--aes": False, + "--chacha": False, + "--salsa": False, + "--base64": False, + "--recursive": 0, + "--no-protect": False, + "--dirs": [], + "--files": [], + "--output": "", + "--follow-imports" : False, + "entrypoint": "" + } + + def Handler(self): + Obfuscation(self) + + help = f''' +Usage: + py-shield obfuscate [options] main.py +Example: + py-shield obfuscate --hashdata --aes --follow-imports main.py + +Notes: + text,text -> to add more than one arg to option. + main.py -> the entry point of your program. + +Options: + --help -> show help for commands. + --quiet -> give less output. + --log -> write all logs to a file. + --no-color -> suppress colored output. + --no-input -> disable prompting for input. + + --hashdata -> convert all strings and var names into hash. + --fernet -> obfuscation and encryption using fernet. + --aes -> obfuscation and encryption using aes256. + --chacha -> obfuscation and encryption using chacha20. + --salsa -> obfuscation and encryption using salsa20. + --base64 -> obfuscation and encryption using base64. + --recursive -> not strong but good if u need to hide ur prog from AVs. + --no-protect -> disable file modification protection. + --dirs -> obfuscate all files in dir. + --files -> files for obfuscation. + --output -> output dir. + --follow-imports -> add all imports to the protected script.''' + class Obfuscation: def __init__(self, this: Command): self.this = this diff --git a/scr/commands/obfuscation/obfuscatelegacy.py b/scr/commands/obfuscation/obfuscatelegacy.py index 722289b..f158407 100644 --- a/scr/commands/obfuscation/obfuscatelegacy.py +++ b/scr/commands/obfuscation/obfuscatelegacy.py @@ -5,6 +5,48 @@ from utils.obfuscation import LegacyObfuscation from utils.langMgr import RemoveComments +class Obfuscatelegacy(Command): + exclusiveOptions = [] + requiredOptions = ["--loops", "--mode", ["--files", "--dirs"]] + options = { + "--quiet": False, + "--log": "", + "--no-color ": False, + "--no-input": False, + + "--loops": 0, + "--mode": 0, + "--dirs": [], + "--files": [], + "--output": "" + } + + def Handler(self): + ObfuscationLegacy(self) + + help = f''' +Usage: + py-shield obfuscatelegacy [options] +Example: + py-shield obfuscatelegacy --loops 3 --mode 2 --file code.py + +Notes: + * -> required option. + text,text -> to add more than one arg to option. + +Options: + --help -> show help for commands. + --quiet -> give less output. + --log -> write all logs to a file. + --no-color -> suppress colored output. + --no-input -> disable prompting for input. + + --loops * -> number of obfuscation loops. + --mode * -> obfuscation mode(1-4) as bigger number as better obfuscation but the output file is larger. + --dirs * -> obfuscate all files in dir(required files or/and dir). + --files * -> files for obfuscation(required files or/and dir). + --output -> output dir.''' + class ObfuscationLegacy: def __init__(self, this: Command): self.this = this diff --git a/scr/config.py b/scr/config.py index 0b0e70b..57361c4 100644 --- a/scr/config.py +++ b/scr/config.py @@ -1,15 +1,42 @@ -from abc import ABC +from abc import ABC, abstractmethod NAME = "Py-Shield" AUTHOR = "ByteCorum" URL = "https://github.com/ByteCorum/Py-Shield" -VERSION = "3.0.0.0" +VERSION = "3.1.0.0" DESCRIPTION = "Tool/Library for Python used to obfuscate and protect your code in static and runtime from decompilation, reverse debug, etc. Also, can prevent detection by antiviruses." class Command(ABC): exclusiveOptions: list requiredOptions: list options: dict + help: str - handler: callable - help: str \ No newline at end of file + @abstractmethod + def Handler(self): + pass + +# class Name_of_the_command(Command): #note: only first letter should be capital +# +# #May be left not initialized if your command has no options# +# exclusiveOptions = [["--install","--uninstall"], ["--up","--down"]] #groups of options that can't be used together +# requiredOptions = ["entrypoint", [""]] #options and groups(any option from a group is required) that are required +# +# options = { #all the options that your command has. note: don't write help here, it's hendeled elsewhere +# #General Options +# "--quiet": False, +# "--log": "", +# "--no-color ": False, +# "--no-input": False, +# +# #Your Options +# "--option1": False, +# "entrypoint": "" #only 1 fixed option name used to store program entrypoint file path +# } +# +# #Should be always initialized# +# help = f'''I\'ll help u''' #help message for the command +# +# @abstractmethod +# def Handler(self):#command handler that will be called when the command is executed +# pass \ No newline at end of file diff --git a/scr/pyshield.py b/scr/pyshield.py index 5109139..4d8ebcb 100644 --- a/scr/pyshield.py +++ b/scr/pyshield.py @@ -1,16 +1,24 @@ from sys import argv, exit +from os import walk from inspect import isclass, isabstract from utils.optionsParser import OptionsParser from utils.logger import Log from config import Command, NAME -import commands.commands as cmds +from commands.basic.help import Help class PyShield: + class CommandReference: + def __init__(self, path, name): + self.path = path + self.name = name + + path: str + name: str + def __init__(self): - self.command = None - self.commandName = "" - self.noOptions = False + self.commands = [] + try: self.ParseArgs() self.SetGlobalVars() @@ -18,27 +26,45 @@ def __init__(self): except Exception as error: Log.Fail(f"Fatal error occurred: {error}", True) + def GetCommands(self, path: str = "commands/"): + for dirpath, dirnames, filenames in walk(path): + for filename in filenames: + if filename.endswith(".py"): + self.commands.append(self.CommandReference(f"{dirpath}/{filename}", "")) + for dirname in dirnames: + self.GetCommands(f"{dirpath}/{dirname}") + def ParseArgs(self): - commands = [] - for cmdName in dir(cmds): - cmd = getattr(cmds, cmdName) - if isclass(cmd) and not isabstract(cmd) and issubclass(cmd, Command) and cmd.__name__ != 'Command': - commands.append(cmdName.lower()) + self.GetCommands() + + print(self.commands) + + for reference in self.commands: + for cmdName in dir(reference.path): + cmd = getattr(reference.path, cmdName) + if isclass(cmd) and not isabstract(cmd) and issubclass(cmd, Command) and cmd.__name__ != 'Command': + reference.name = cmdName.lower() + + print(self.commands) try: if len(argv) < 2: raise Exception("missing command name.") - if argv[1] not in commands: + if argv[1] not in self.commandsList: raise Exception(f"invalid command name: \"{argv[1]}\".") except Exception as error: - cmds.Help.handler(cmds.Help) + Help.Handler(Help) Log.Fail("Command parsing failed: "+str(error), True) self.commandName = argv[1] try: - self.command: Command = getattr(cmds, self.commandName.title()) + for reference in self.commands: + if reference.name == self.commandName: + self.command: Command = getattr(reference.path, self.commandName.title()) + break + if not self.command.options: self.noOptions = True return diff --git a/scr/utils/optionsParser.py b/scr/utils/optionsParser.py index fc5e8d4..d780701 100644 --- a/scr/utils/optionsParser.py +++ b/scr/utils/optionsParser.py @@ -1,6 +1,6 @@ from utils.logger import Log from config import Command -from commands.commands import Help +from commands.basic.help import Help class OptionsParser: def __init__(self, argv, command: Command): @@ -11,7 +11,7 @@ def __init__(self, argv, command: Command): def Parse(self): if "--help" in self.argv: - Help.handler(self.command) + Help.Handler(self.command) if self.argc > 1: Log.Warning("--help found, other options ignored.") self.ended = True From 863bb8d0d1f9309f1c61e52dc02cab277af9a9e4 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Wed, 3 Sep 2025 12:54:22 +0300 Subject: [PATCH 02/28] refactor: reworked commands structure and search Reduced memory and cpu consumption and increased speed. Removed junk classes and variables Reworked commands structure --- scr/pyshield.py | 82 +++++++++++++++++++++++++------------------------ 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/scr/pyshield.py b/scr/pyshield.py index 4d8ebcb..f87469f 100644 --- a/scr/pyshield.py +++ b/scr/pyshield.py @@ -1,6 +1,7 @@ from sys import argv, exit from os import walk from inspect import isclass, isabstract +import importlib.util from utils.optionsParser import OptionsParser from utils.logger import Log from config import Command, NAME @@ -8,17 +9,10 @@ class PyShield: - class CommandReference: - def __init__(self, path, name): - self.path = path - self.name = name - - path: str - name: str + command: Command + commandName: str def __init__(self): - self.commands = [] - try: self.ParseArgs() self.SetGlobalVars() @@ -26,45 +20,19 @@ def __init__(self): except Exception as error: Log.Fail(f"Fatal error occurred: {error}", True) - def GetCommands(self, path: str = "commands/"): - for dirpath, dirnames, filenames in walk(path): - for filename in filenames: - if filename.endswith(".py"): - self.commands.append(self.CommandReference(f"{dirpath}/{filename}", "")) - for dirname in dirnames: - self.GetCommands(f"{dirpath}/{dirname}") - def ParseArgs(self): - self.GetCommands() - - print(self.commands) - - for reference in self.commands: - for cmdName in dir(reference.path): - cmd = getattr(reference.path, cmdName) - if isclass(cmd) and not isabstract(cmd) and issubclass(cmd, Command) and cmd.__name__ != 'Command': - reference.name = cmdName.lower() - - print(self.commands) - try: if len(argv) < 2: raise Exception("missing command name.") - if argv[1] not in self.commandsList: - raise Exception(f"invalid command name: \"{argv[1]}\".") + self.commandName = argv[1].title() + self.command = self.GetCommand(self.commandName) except Exception as error: Help.Handler(Help) Log.Fail("Command parsing failed: "+str(error), True) - self.commandName = argv[1] try: - for reference in self.commands: - if reference.name == self.commandName: - self.command: Command = getattr(reference.path, self.commandName.title()) - break - if not self.command.options: self.noOptions = True return @@ -75,11 +43,45 @@ def ParseArgs(self): exit(0) parser.Validate() - self.command = parser.command + self.command = parser.command#get filled command object except Exception as error: Log.Fail(f"Options parsing failed: {error}", True) + def GetCommand(self, name): + command = self.SearchCommand(name) + if not command: + raise Exception(f"invalid command name: \"{name}\".") + + return command + + + def SearchCommand(self, name, path: str = "commands/"): + for dirpath, dirnames, filenames in walk(path): + for filename in filenames: + if filename.endswith(".py") and filename != "__init__.py": + filePath = f"{dirpath}/{filename}" + spec = importlib.util.spec_from_file_location("temp_module", filePath) + + if spec and spec.loader: + module = importlib.util.module_from_spec(spec) + try: + spec.loader.exec_module(module) + for cmdName in dir(module): + command = getattr(module, cmdName) + if (isclass(command) and + not isabstract(command) and + issubclass(command, Command) and + command.__name__ != 'Command' and + command.__name__ == name): + return command + + except Exception as error: + # Skip files that can't be imported + continue + return None + + def SetGlobalVars(self): if self.noOptions: return @@ -100,6 +102,6 @@ def RunCommand(self): Log.Info(f"{NAME}\n", True) try: self.command.handler(self.command) - Log.Success(f"{self.commandName.title()} successfully completed.") + Log.Success(f"{self.commandName} successfully completed.") except Exception as error: - Log.Fail(f"{self.commandName.title()} failed: {error}", True) \ No newline at end of file + Log.Fail(f"{self.commandName} failed: {error}", True) \ No newline at end of file From 3d0122bc5d319b2be462f8df8550f89add97ea34 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Wed, 3 Sep 2025 13:08:48 +0300 Subject: [PATCH 03/28] fix: added basic encryption modules to followimports --- scr/commands/obfuscation/obfuscate.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/scr/commands/obfuscation/obfuscate.py b/scr/commands/obfuscation/obfuscate.py index 56e911e..db37433 100644 --- a/scr/commands/obfuscation/obfuscate.py +++ b/scr/commands/obfuscation/obfuscate.py @@ -224,6 +224,23 @@ def FollowImports(self, content): if self.this.options["--follow-imports"]: modules = GetImports(content) + if not self.this.options["--no-protect"]: + modules.append("hashlib") + modules.append("os") + + if self.this.options["--chacha"] or self.this.options["--salsa"]: + modules.append("Crypto.Cipher") + + if self.this.options["--aes"]: + modules.append("cryptography.hazmat.primitives.ciphers.aead") + + if self.this.options["--fernet"]: + modules.append("cryptography.fernet") + + modules.append("sys") + modules.append("base64") + modules.append("zlib") + for module in modules: if not module in self.imports: self.imports.append(module) From c1343e1d22bc2e5789fe8df14a17880b5cefceca Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Wed, 3 Sep 2025 13:15:24 +0300 Subject: [PATCH 04/28] fix: fixed runtime dependencies fixed: commands dir ain't carried with program --- scr/pyshield.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/scr/pyshield.py b/scr/pyshield.py index f87469f..6abb87d 100644 --- a/scr/pyshield.py +++ b/scr/pyshield.py @@ -1,11 +1,12 @@ from sys import argv, exit from os import walk from inspect import isclass, isabstract -import importlib.util +from importlib.util import spec_from_file_location, module_from_spec + from utils.optionsParser import OptionsParser from utils.logger import Log from config import Command, NAME -from commands.basic.help import Help +import commands class PyShield: @@ -29,7 +30,7 @@ def ParseArgs(self): self.command = self.GetCommand(self.commandName) except Exception as error: - Help.Handler(Help) + commands.basic.help.Help.Handler(commands.basic.help.Help) Log.Fail("Command parsing failed: "+str(error), True) try: @@ -61,10 +62,10 @@ def SearchCommand(self, name, path: str = "commands/"): for filename in filenames: if filename.endswith(".py") and filename != "__init__.py": filePath = f"{dirpath}/{filename}" - spec = importlib.util.spec_from_file_location("temp_module", filePath) + spec = spec_from_file_location("temp_module", filePath) if spec and spec.loader: - module = importlib.util.module_from_spec(spec) + module = module_from_spec(spec) try: spec.loader.exec_module(module) for cmdName in dir(module): From 5bec5c51a923bb65a433cfb79f75c9ca001a118a Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Wed, 3 Sep 2025 13:34:19 +0300 Subject: [PATCH 05/28] fix: added commands dir as a pkg to build fix: commands dir ain't carried with program --- scr/build-linux.sh | 1 + scr/build-mac.sh | 1 + scr/build-win.cmd | 1 + scr/commands/__init__.py | 0 4 files changed, 3 insertions(+) create mode 100644 scr/commands/__init__.py diff --git a/scr/build-linux.sh b/scr/build-linux.sh index 9a33353..8345dd0 100644 --- a/scr/build-linux.sh +++ b/scr/build-linux.sh @@ -7,6 +7,7 @@ python -m nuitka \ --assume-yes-for-downloads \ --output-filename=py-shield \ --linux-icon=../assets/icon.png \ + --include-package=commands \ --company-name="ByteCorum" \ --product-name="Py-Shield" \ --file-version=3.0.0.0 \ diff --git a/scr/build-mac.sh b/scr/build-mac.sh index e311bb9..be62993 100644 --- a/scr/build-mac.sh +++ b/scr/build-mac.sh @@ -6,6 +6,7 @@ python -m nuitka \ --onefile \ --assume-yes-for-downloads \ --output-filename=py-shield \ + --include-package=commands \ --company-name="ByteCorum" \ --product-name="Py-Shield" \ --file-version=3.0.0.0 \ diff --git a/scr/build-win.cmd b/scr/build-win.cmd index 689cde9..69eadb2 100644 --- a/scr/build-win.cmd +++ b/scr/build-win.cmd @@ -7,6 +7,7 @@ python -m nuitka ^ --onefile ^ --output-filename=py-shield ^ --windows-icon-from-ico=../assets/icon.ico ^ + --include-package=commands ^ --company-name="ByteCorum" ^ --product-name="Py-Shield" ^ --file-version=3.0.0.0 ^ diff --git a/scr/commands/__init__.py b/scr/commands/__init__.py new file mode 100644 index 0000000..e69de29 From 24f0ab891985f2c247cf83e2909835ddb524d4de Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Wed, 3 Sep 2025 13:52:16 +0300 Subject: [PATCH 06/28] fix: searching commands dir using abs prog path fix: can't find command in any dirs except home --- scr/pyshield.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scr/pyshield.py b/scr/pyshield.py index 6abb87d..a230d06 100644 --- a/scr/pyshield.py +++ b/scr/pyshield.py @@ -1,5 +1,5 @@ from sys import argv, exit -from os import walk +from os import walk, path from inspect import isclass, isabstract from importlib.util import spec_from_file_location, module_from_spec @@ -12,6 +12,7 @@ class PyShield: command: Command commandName: str + noOptions = False def __init__(self): try: @@ -50,14 +51,14 @@ def ParseArgs(self): Log.Fail(f"Options parsing failed: {error}", True) def GetCommand(self, name): - command = self.SearchCommand(name) + command = self.SearchCommand(name, f"{path.dirname(path.abspath(__file__))}/commands/") if not command: raise Exception(f"invalid command name: \"{name}\".") return command - def SearchCommand(self, name, path: str = "commands/"): + def SearchCommand(self, name: str, path: str): for dirpath, dirnames, filenames in walk(path): for filename in filenames: if filename.endswith(".py") and filename != "__init__.py": @@ -102,7 +103,7 @@ def SetGlobalVars(self): def RunCommand(self): Log.Info(f"{NAME}\n", True) try: - self.command.handler(self.command) + self.command.Handler(self.command) Log.Success(f"{self.commandName} successfully completed.") except Exception as error: Log.Fail(f"{self.commandName} failed: {error}", True) \ No newline at end of file From 92a73043c730a55d7017e63cda335f763edbcfe3 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:07:03 +0300 Subject: [PATCH 07/28] fix: added commands dir as a pkg to build fix: commands dir ain't carried with program --- scr/build-linux.sh | 6 ++++++ scr/build-mac.sh | 6 ++++++ scr/build-win.cmd | 9 ++++++++- scr/commands/__init__.py | 0 scr/pyshield.py | 6 ++++-- 5 files changed, 24 insertions(+), 3 deletions(-) delete mode 100644 scr/commands/__init__.py diff --git a/scr/build-linux.sh b/scr/build-linux.sh index 8345dd0..f3fc20a 100644 --- a/scr/build-linux.sh +++ b/scr/build-linux.sh @@ -8,6 +8,12 @@ python -m nuitka \ --output-filename=py-shield \ --linux-icon=../assets/icon.png \ --include-package=commands \ + --follow-import-to=commands \ + --include-data-files=commands/obfuscation/obfuscate.py=commands/obfuscation/obfuscate.py \ + --include-data-files=commands/obfuscation/obfuscatelegacy.py=commands/obfuscation/obfuscatelegacy.py \ + --include-data-files=commands/basic/help.py=commands/basic/help.py \ + --include-data-files=commands/basic/info.py=commands/basic/info.py \ + --include-data-files=commands/basic/dependencies.py=commands/basic/dependencies.py \ --company-name="ByteCorum" \ --product-name="Py-Shield" \ --file-version=3.0.0.0 \ diff --git a/scr/build-mac.sh b/scr/build-mac.sh index be62993..4609073 100644 --- a/scr/build-mac.sh +++ b/scr/build-mac.sh @@ -7,6 +7,12 @@ python -m nuitka \ --assume-yes-for-downloads \ --output-filename=py-shield \ --include-package=commands \ + --follow-import-to=commands \ + --include-data-files=commands/obfuscation/obfuscate.py=commands/obfuscation/obfuscate.py \ + --include-data-files=commands/obfuscation/obfuscatelegacy.py=commands/obfuscation/obfuscatelegacy.py \ + --include-data-files=commands/basic/help.py=commands/basic/help.py \ + --include-data-files=commands/basic/info.py=commands/basic/info.py \ + --include-data-files=commands/basic/dependencies.py=commands/basic/dependencies.py \ --company-name="ByteCorum" \ --product-name="Py-Shield" \ --file-version=3.0.0.0 \ diff --git a/scr/build-win.cmd b/scr/build-win.cmd index 69eadb2..03eb7b4 100644 --- a/scr/build-win.cmd +++ b/scr/build-win.cmd @@ -8,10 +8,17 @@ python -m nuitka ^ --output-filename=py-shield ^ --windows-icon-from-ico=../assets/icon.ico ^ --include-package=commands ^ + --follow-import-to=commands ^ + --include-data-files=commands/obfuscation/obfuscate.py=commands/obfuscation/obfuscate.py ^ + --include-data-files=commands/obfuscation/obfuscatelegacy.py=commands/obfuscation/obfuscatelegacy.py ^ + --include-data-files=commands/basic/help.py=commands/basic/help.py ^ + --include-data-files=commands/basic/info.py=commands/basic/info.py ^ + --include-data-files=commands/basic/dependencies.py=commands/basic/dependencies.py ^ --company-name="ByteCorum" ^ --product-name="Py-Shield" ^ --file-version=3.0.0.0 ^ --product-version=3.0.0.0 ^ --file-description="Tool/Library for Python used to obfuscate and protect your code in static and runtime from decompilation, reverse debug, etc. Also, can prevent detection by antiviruses." ^ --copyright="https://github.com/ByteCorum/Py-Shield/blob/main/LICENSE" ^ - main.py \ No newline at end of file + main.py +pause \ No newline at end of file diff --git a/scr/commands/__init__.py b/scr/commands/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/scr/pyshield.py b/scr/pyshield.py index a230d06..d85ff81 100644 --- a/scr/pyshield.py +++ b/scr/pyshield.py @@ -6,7 +6,7 @@ from utils.optionsParser import OptionsParser from utils.logger import Log from config import Command, NAME -import commands +from commands.basic.help import Help class PyShield: @@ -31,7 +31,7 @@ def ParseArgs(self): self.command = self.GetCommand(self.commandName) except Exception as error: - commands.basic.help.Help.Handler(commands.basic.help.Help) + Help.Handler(Help) Log.Fail("Command parsing failed: "+str(error), True) try: @@ -51,6 +51,8 @@ def ParseArgs(self): Log.Fail(f"Options parsing failed: {error}", True) def GetCommand(self, name): + print(f"{path.dirname(path.abspath(__file__))}") + input() command = self.SearchCommand(name, f"{path.dirname(path.abspath(__file__))}/commands/") if not command: raise Exception(f"invalid command name: \"{name}\".") From 8d7f48ea4e0c08b828539ad872dece8b3552f1bb Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Wed, 3 Sep 2025 15:28:01 +0300 Subject: [PATCH 08/28] style: removed lines used for debugging --- scr/pyshield.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/scr/pyshield.py b/scr/pyshield.py index d85ff81..c3b940e 100644 --- a/scr/pyshield.py +++ b/scr/pyshield.py @@ -51,8 +51,6 @@ def ParseArgs(self): Log.Fail(f"Options parsing failed: {error}", True) def GetCommand(self, name): - print(f"{path.dirname(path.abspath(__file__))}") - input() command = self.SearchCommand(name, f"{path.dirname(path.abspath(__file__))}/commands/") if not command: raise Exception(f"invalid command name: \"{name}\".") From 2fa74bf699935e3ce359fa9a8e5a59a294e05d7c Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Thu, 4 Sep 2025 23:35:55 +0300 Subject: [PATCH 09/28] feat: dynamically loading help cmd Now help cmd is like any other cmd. It has params, etc. Now it's easier to override help cmd cuz it's loading dynamically as any other cmd --- scr/commands/basic/help.py | 6 +++--- scr/pyshield.py | 28 ++++++++++++---------------- scr/utils/optionsParser.py | 6 +++--- 3 files changed, 18 insertions(+), 22 deletions(-) diff --git a/scr/commands/basic/help.py b/scr/commands/basic/help.py index a1a0b83..f53343a 100644 --- a/scr/commands/basic/help.py +++ b/scr/commands/basic/help.py @@ -2,9 +2,9 @@ from config import Command class Help(Command): - exclusiveOptions = None - requiredOptions = None - options = None + exclusiveOptions = [] + requiredOptions = [] + options = {} def Handler(command: Command = None): Log.Custom(command.help) diff --git a/scr/pyshield.py b/scr/pyshield.py index c3b940e..142a528 100644 --- a/scr/pyshield.py +++ b/scr/pyshield.py @@ -6,16 +6,16 @@ from utils.optionsParser import OptionsParser from utils.logger import Log from config import Command, NAME -from commands.basic.help import Help class PyShield: - command: Command - commandName: str - noOptions = False + command: Command = None + commandName: str = None + helpCmd: Command = None def __init__(self): try: + self.helpCmd = self.GetCommand("Help") self.ParseArgs() self.SetGlobalVars() self.RunCommand() @@ -31,15 +31,11 @@ def ParseArgs(self): self.command = self.GetCommand(self.commandName) except Exception as error: - Help.Handler(Help) + self.helpCmd.Handler(self.helpCmd) Log.Fail("Command parsing failed: "+str(error), True) try: - if not self.command.options: - self.noOptions = True - return - - parser = OptionsParser(argv[2:], self.command) + parser = OptionsParser(self.helpCmd, argv[2:], self.command) parser.Parse() if parser.ended: exit(0) @@ -74,9 +70,12 @@ def SearchCommand(self, name: str, path: str): if (isclass(command) and not isabstract(command) and issubclass(command, Command) and - command.__name__ != 'Command' and - command.__name__ == name): - return command + command.__name__ != 'Command'): + + # if (self.helpCmd and command.__name__ == "Help"): + # self.helpCmd = command + if (command.__name__ == name): + return command except Exception as error: # Skip files that can't be imported @@ -85,9 +84,6 @@ def SearchCommand(self, name: str, path: str): def SetGlobalVars(self): - if self.noOptions: - return - if "--log" in self.command.options: Log.logFile = self.command.options["--log"] diff --git a/scr/utils/optionsParser.py b/scr/utils/optionsParser.py index d780701..741488f 100644 --- a/scr/utils/optionsParser.py +++ b/scr/utils/optionsParser.py @@ -1,17 +1,17 @@ from utils.logger import Log from config import Command -from commands.basic.help import Help class OptionsParser: - def __init__(self, argv, command: Command): + def __init__(self, helpCmd: Command, argv, command: Command): self.argv = argv self.argc = len(self.argv) self.command = command + self.helpCmd = helpCmd self.ended = False# handles the --help command with the highest priority def Parse(self): if "--help" in self.argv: - Help.Handler(self.command) + self.helpCmd.Handler(self.command) if self.argc > 1: Log.Warning("--help found, other options ignored.") self.ended = True From 09c8de880fdc94975863bce2b63d44408e940a17 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Thu, 4 Sep 2025 23:42:22 +0300 Subject: [PATCH 10/28] added some things to todo --- TODO.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/TODO.txt b/TODO.txt index e69de29..0223b40 100644 --- a/TODO.txt +++ b/TODO.txt @@ -0,0 +1,10 @@ +- Add option not to encrypt executor +- Add option to retry if cytonization fails +- Refactor self giving to cmds +- Add precompilation of cmds dir +- Test imports manager on challenging cases +- Add workflows to check obfuscation on mac and linux +- Add workflows to check onefile/multifile legacy/global obfuscation +- Add data from github template repo +- Rename the project +- Publish as python lib \ No newline at end of file From 8726f27db5e75cc8fc7221c7824a0a14c06524a6 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Fri, 5 Sep 2025 22:18:10 +0300 Subject: [PATCH 11/28] style: renamed obfuscation funcs renamed obfuscation funcs called by Handler to avoid mess --- scr/commands/obfuscation/obfuscate.py | 4 ++-- scr/commands/obfuscation/obfuscatelegacy.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scr/commands/obfuscation/obfuscate.py b/scr/commands/obfuscation/obfuscate.py index db37433..cec9e8a 100644 --- a/scr/commands/obfuscation/obfuscate.py +++ b/scr/commands/obfuscation/obfuscate.py @@ -30,7 +30,7 @@ class Obfuscate(Command): } def Handler(self): - Obfuscation(self) + PerformObfuscation(self) help = f''' Usage: @@ -62,7 +62,7 @@ def Handler(self): --output -> output dir. --follow-imports -> add all imports to the protected script.''' -class Obfuscation: +class PerformObfuscation: def __init__(self, this: Command): self.this = this self.workingDir = getcwd() diff --git a/scr/commands/obfuscation/obfuscatelegacy.py b/scr/commands/obfuscation/obfuscatelegacy.py index f158407..bf1c027 100644 --- a/scr/commands/obfuscation/obfuscatelegacy.py +++ b/scr/commands/obfuscation/obfuscatelegacy.py @@ -22,7 +22,7 @@ class Obfuscatelegacy(Command): } def Handler(self): - ObfuscationLegacy(self) + PerformLegacyObfuscation(self) help = f''' Usage: @@ -47,7 +47,7 @@ def Handler(self): --files * -> files for obfuscation(required files or/and dir). --output -> output dir.''' -class ObfuscationLegacy: +class PerformLegacyObfuscation: def __init__(self, this: Command): self.this = this self.workingDir = getcwd() From b72342d5d582a865c198fca1fd130310e27ef5d0 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Fri, 5 Sep 2025 22:24:15 +0300 Subject: [PATCH 12/28] feat: added option not to encrypt executor --- TODO.txt | 1 - scr/commands/obfuscation/obfuscate.py | 5 ++++- scr/utils/obfuscation.py | 12 ++++++++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/TODO.txt b/TODO.txt index 0223b40..b7b26c5 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,4 +1,3 @@ -- Add option not to encrypt executor - Add option to retry if cytonization fails - Refactor self giving to cmds - Add precompilation of cmds dir diff --git a/scr/commands/obfuscation/obfuscate.py b/scr/commands/obfuscation/obfuscate.py index cec9e8a..aefadb2 100644 --- a/scr/commands/obfuscation/obfuscate.py +++ b/scr/commands/obfuscation/obfuscate.py @@ -22,6 +22,7 @@ class Obfuscate(Command): "--base64": False, "--recursive": 0, "--no-protect": False, + "--enc-exec" : False, "--dirs": [], "--files": [], "--output": "", @@ -57,6 +58,7 @@ def Handler(self): --base64 -> obfuscation and encryption using base64. --recursive -> not strong but good if u need to hide ur prog from AVs. --no-protect -> disable file modification protection. + --enc-exec -> obfuscate executor via legacy encryption method. --dirs -> obfuscate all files in dir. --files -> files for obfuscation. --output -> output dir. @@ -142,7 +144,8 @@ def ObfuscateFiles(self): self.this.options["--salsa"], self.this.options["--base64"], self.this.options["--recursive"], - self.this.options["--no-protect"]) + self.this.options["--no-protect"], + self.this.options["--enc-exec"]) for file in self.this.options["--files"]: with open(file, "r", encoding="utf-8") as pyFile: diff --git a/scr/utils/obfuscation.py b/scr/utils/obfuscation.py index 0d7687c..50d99aa 100644 --- a/scr/utils/obfuscation.py +++ b/scr/utils/obfuscation.py @@ -10,7 +10,9 @@ from utils.logger import Log class MainObfuscation: - def __init__(self, hashdata: bool, fernet: bool, aes: bool, chacha20: bool, salsa20: bool, base64: bool, recursive: int, noProtect: bool) -> None: + def __init__(self, hashdata: bool, fernet: bool, aes: bool, + chacha20: bool, salsa20: bool, base64: bool, + recursive: int, noProtect: bool, encExec: bool) -> None: self.hashdata = hashdata self.fernet = fernet self.aes = aes @@ -18,6 +20,7 @@ def __init__(self, hashdata: bool, fernet: bool, aes: bool, chacha20: bool, sals self.salsa20 = salsa20 self.base64 = base64 self.noProtect = noProtect + self.encExec = encExec self.recursive = recursive if self.recursive < 0: raise Exception("Invalid recursive value.") @@ -238,9 +241,10 @@ def _(self): outputDir =f"{outputDir}/PyShield" makedirs(outputDir) - obfuscator = LegacyObfuscation(3, 6, LegacyObfuscation.GenSeperator(12)) - context = obfuscator.Encrypt(context) - context = obfuscator.Wrap(context) + if self.encExec: + obfuscator = LegacyObfuscation(3, 6, LegacyObfuscation.GenSeperator(12)) + context = obfuscator.Encrypt(context) + context = obfuscator.Wrap(context) with open(f"{outputDir}/script_{self.number}.py", "w", encoding="utf-8") as file: file.write(context) From 5c254137e4dffc9c003ed7d95bf54715ebfcae7a Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Fri, 5 Sep 2025 22:28:36 +0300 Subject: [PATCH 13/28] fix: cytonization fails due to unexpected str removed conflicting punctuation from legacy obfuscation --- TODO.txt | 1 - scr/utils/obfuscation.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/TODO.txt b/TODO.txt index b7b26c5..45d491f 100644 --- a/TODO.txt +++ b/TODO.txt @@ -1,4 +1,3 @@ -- Add option to retry if cytonization fails - Refactor self giving to cmds - Add precompilation of cmds dir - Test imports manager on challenging cases diff --git a/scr/utils/obfuscation.py b/scr/utils/obfuscation.py index 50d99aa..dfa64b9 100644 --- a/scr/utils/obfuscation.py +++ b/scr/utils/obfuscation.py @@ -1,5 +1,5 @@ from random import choice, randint -from string import ascii_letters, digits, punctuation +from string import ascii_letters, digits from utils.crypto import FernetCipher, AesCipher, ChaCha20Cipher, Salsa20Cipher, compress, b64encode import ast from hashlib import sha256 @@ -119,7 +119,7 @@ def ProtectFile(self, outputDir, filepath): self.files.append([filepath, fileHash]) def CreateExecutor(self, outputDir): - secret = sha256(''.join(choice(ascii_letters+digits+punctuation) for _ in range(randint(16,32))).encode("utf-8")).hexdigest() + secret = sha256(''.join(choice(ascii_letters+digits) for _ in range(randint(16,32))).encode("utf-8")).hexdigest() context = f''' {'''from hashlib import sha256 from os import path, getcwd''' if not self.noProtect else ""} @@ -397,4 +397,4 @@ def LiteObfuscation(self,content): @staticmethod def GenSeperator(length): - return ''.join(choice(ascii_letters+digits+punctuation) for _ in range(length)) \ No newline at end of file + return ''.join(choice(ascii_letters+digits) for _ in range(length)) \ No newline at end of file From f4313aa17ad8ece85413c3d18b5f84c38cd09f86 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Fri, 5 Sep 2025 22:37:37 +0300 Subject: [PATCH 14/28] docs: added github files added files from amazing repo template --- .editorconfig | 12 ++++ .github/CODEOWNERS | 1 + .github/FUNDING.yml | 12 ++++ .github/ISSUE_TEMPLATE/bug-report.md | 30 ++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 +++ .github/ISSUE_TEMPLATE/feature-request.md | 25 ++++++++ .github/ISSUE_TEMPLATE/question.md | 21 +++++++ .github/labels.yml | 73 +++++++++++++++++++++++ .github/pull_request_template.md | 31 ++++++++++ .github/workflows/issues-reply.yml | 59 ++++++++++++++++++ .github/workflows/issues.yml | 32 ---------- .github/workflows/prs-reply.yml | 44 ++++++++++++++ .github/workflows/pull-request.yml | 33 ---------- .github/workflows/sync-labels.yml | 23 +++++++ TODO.txt | 8 --- docs/ChangeLog.md | 5 ++ docs/CommitMessagesGuidance.md | 49 +++++++++++++++ docs/Todo.md | 19 ++++++ 18 files changed, 412 insertions(+), 73 deletions(-) create mode 100644 .editorconfig create mode 100644 .github/CODEOWNERS create mode 100644 .github/FUNDING.yml create mode 100644 .github/ISSUE_TEMPLATE/bug-report.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature-request.md create mode 100644 .github/ISSUE_TEMPLATE/question.md create mode 100644 .github/labels.yml create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/issues-reply.yml delete mode 100644 .github/workflows/issues.yml create mode 100644 .github/workflows/prs-reply.yml delete mode 100644 .github/workflows/pull-request.yml create mode 100644 .github/workflows/sync-labels.yml delete mode 100644 TODO.txt create mode 100644 docs/ChangeLog.md create mode 100644 docs/CommitMessagesGuidance.md create mode 100644 docs/Todo.md diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b9c127d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +# EditorConfig is awesome: https://EditorConfig.org + +# top-most EditorConfig file +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = crlf +charset = utf-8 +trim_trailing_whitespace = false +insert_final_newline = false \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..068955e --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @ByteCorum \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..54dd1b6 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,12 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: bytecorum # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..4cb62a7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,30 @@ +--- +name: Bug Report +about: Please take your time and provide us some info about an issue that you've faced. +title: "Bug: " +labels: bug, no-priority +assignees: ByteCorum +--- + +### Description + +A field to provide the detailed info about faced issue. + +### Condition & circumstances + +A field to provide the information how did u get this issue and how to reproduce these circumstances. + +### Expected behavior + +A field to provide the information of what have you expected to happen. + +### PC configuration + +- CPU: +- GPU: +- RAM: +- OS: + +### Additional context + +A field to provide any other context about the problem: logs, screenshots, debug info. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..c324c06 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Contact Author + url: https://discord.com/users/798503509522645012 + about: Author's discord account. + - name: Stackoverflow + url: https://stackoverflow.com/users/26622521/bytecorum + about: Maybe you can find solution on stackoverflow. diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000..dd4abfa --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,25 @@ +--- +name: Feature request +about: "Please take your time and provide us info about your idea for this project " +title: "Request: " +labels: suggestion, no-priority +assignees: ByteCorum +--- + +### General + +- Related issue or pr: +- Similar to feature in todo: +- Should be involved in: + +### Description + +A field to provide the detailed info about your idea and what you want to happen. + +### Alternatives + +A field to provide the description of any alternative solutions or features you've considered. + +### Additional context + +A field to provide any other context about the idea: templates, screenshots, code blocks. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md new file mode 100644 index 0000000..974b4b0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/question.md @@ -0,0 +1,21 @@ +--- +name: Question +about: Please take your time and tell us about your question. +title: "Question: " +labels: question, no-priority +assignees: ByteCorum +--- + +### Basic + +- Related issue or pr: +- Related functionality: +- Should be documented: y/n + +### Description + +A field to provide the detailed info about faced issue. + +### Additional context + +A field to provide any other context about the question: screenshots, code blocks. diff --git a/.github/labels.yml b/.github/labels.yml new file mode 100644 index 0000000..1bf7cd8 --- /dev/null +++ b/.github/labels.yml @@ -0,0 +1,73 @@ +# Type +- name: "bug" + color: ee0701 + description: "Something isn't working as expected or causes issues." +- name: "question" + color: cc317c + description: "Further information is requested or clarification needed." +- name: "suggestion" + color: 84b6eb + description: "Ideas for improvements or new features." +- name: "docs" + color: 0075ca + description: "Documentation improvements or additions." +- name: "feat" + color: 0e8a16 + description: "New feature or functionality." +- name: "fix" + color: ee0701 + description: "Bug fix or correction." +- name: "style" + color: f9d0c4 + description: "Code style, formatting, or cosmetic changes." +- name: "refactor" + color: fbca04 + description: "Code restructuring without changing functionality." +- name: "test" + color: 1d76db + description: "Adding or updating tests." +- name: "chore" + color: fef2c0 + description: "Maintenance tasks, build processes, or tooling changes." + +# Priority +- name: "high-priority" + color: b60205 + description: "High priority issue that should be addressed urgently." +- name: "medium-priority" + color: fbca04 + description: "Medium priority issue for normal workflow." +- name: "low-priority" + color: 0e8a16 + description: "Low priority issue that can be addressed when time permits." +- name: "no-priority" + color: e4e669 + description: "No specific priority assigned." + +# Status +- name: "wip-status" + color: ff9500 + description: "Work in progress - currently being worked on." +- name: "suspended-status" + color: 5319e7 + description: "Work has been suspended or put on hold." +- name: "invalid-status" + color: 000000 + description: "This issue or pull request is not valid." +- name: "duplicate-status" + color: cccccc + description: "This issue or pull request already exists." +- name: "wontfix-status" + color: ffffff + description: "This will not be worked on or fixed." + +# Impact +- name: "major" + color: d73a4a + description: "Major changes with significant impact." +- name: "minor" + color: 0075ca + description: "Minor changes with limited impact." +- name: "regular" + color: 28a745 + description: "Regular changes as part of normal development." diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..6e37202 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,31 @@ + + +### General + +- Related issue: +- Related functionality: +- Feature in todo: +- Should be documented: y/n + + +### Type + +- [ ] Bugfix +- [ ] Feature +- [ ] Code style update +- [ ] Refactor +- [ ] Tests-related changes +- [ ] Documentation content changes +- [ ] Other (please describe): + +### Description + +A field to provide the detailed info about what have you modified. + +### Effect + +A field to provide the detailed info about how do your changes affect entire project. + +## Other information + +A field to provide any other context about your changes: logs, screenshots, debug info. diff --git a/.github/workflows/issues-reply.yml b/.github/workflows/issues-reply.yml new file mode 100644 index 0000000..d365fce --- /dev/null +++ b/.github/workflows/issues-reply.yml @@ -0,0 +1,59 @@ +name: Auto-Reply to Issues + +on: + issues: + types: [opened] + +jobs: + auto-reply: + runs-on: ubuntu-latest + steps: + - name: Reply to issue based on type + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const issue = context.payload.issue; + const issueTitle = issue.title.toLowerCase(); + const issueBody = issue.body || ''; + const labels = issue.labels.map(label => label.name); + let issueComment = ''; + + // Determine issue type and set appropriate response + if (labels.includes('bug') || issueTitle.includes('bug:')) { + issueComment = `**Thank you for reporting this bug!** + We appreciate you taking the time to provide detailed information about the issue you've encountered. Our team will investigate this bug and work on a fix as soon as possible. + Thank you for helping us improve our software!`; + + } else if (labels.includes('enhancement') || issueTitle.includes('request:')) { + issueComment = `**Thank you for your feature request!** + We're excited to hear your ideas for improving our project. Feature requests from our community are valuable and help shape the future direction of our software. + Thank you for contributing to our project's growth!`; + + } else if (labels.includes('question') || issueTitle.includes('question:')) { + issueComment = `**Thank you for your question!** + We're here to help you get the most out of our software. Questions from users help us identify areas where our documentation can be improved. + We appreciate your engagement with our project!`; + + } else { + // Generic response for unclassified issues + issueComment = `**Thank you for opening this issue!** + We appreciate you taking the time to contribute to our project. To help us assist you better, please: + Our team will review and respond soon. Thank you for using our software!`; + } + + // Post the comment + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: issueComment + }); + + // Add reaction to show the bot has processed the issue + await github.rest.reactions.createForIssue({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + content: 'eyes' + }); diff --git a/.github/workflows/issues.yml b/.github/workflows/issues.yml deleted file mode 100644 index 5217bd4..0000000 --- a/.github/workflows/issues.yml +++ /dev/null @@ -1,32 +0,0 @@ -name: Auto-Reply to Issues - -on: - issues: - types: [opened] - -jobs: - auto-reply: - runs-on: ubuntu-latest - steps: - - name: Reply to issue - uses: actions/github-script@v5 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const issueComment = ` - 🖐️ Hello @${{ github.actor }}! - Thanks for using our software! We will look into and fix your issue asap.`; - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: issueComment - }); - - github.rest.issues.update({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['new'] - }); \ No newline at end of file diff --git a/.github/workflows/prs-reply.yml b/.github/workflows/prs-reply.yml new file mode 100644 index 0000000..d136419 --- /dev/null +++ b/.github/workflows/prs-reply.yml @@ -0,0 +1,44 @@ +name: Auto-Reply to Pull Requests +on: + pull_request: + types: [opened, ready_for_review] + +jobs: + auto-reply: + runs-on: ubuntu-latest + permissions: + pull-requests: write + contents: read + steps: + - name: Reply to pull request + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const pr = context.payload.pull_request; + const isDraft = pr.draft; + + // Skip if it's a draft PR being opened (not converted to ready) + if (isDraft && context.payload.action === 'opened') { + return; + } + + let prComment = `**Thank you for contributing into our project!** + We appreciate the invest of your time and efforts to help us improve our project. + Your pull request will be reviewed as soon as possible!`; + + // Post the comment + await github.rest.issues.createComment({ + issue_number: pr.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: prComment + }); + + // Add reaction to show the bot has processed the issue + await github.rest.reactions.createForIssue({ + issue_number: pr.number, + owner: context.repo.owner, + repo: context.repo.repo, + content: 'eyes' + }); diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml deleted file mode 100644 index 37cc4bc..0000000 --- a/.github/workflows/pull-request.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Auto-Reply to PR -on: - pull_request: - types: [opened] - -jobs: - comment: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - - name: Reply to PR - uses: actions/github-script@v5 - with: - script: | - const prComment = ` - 🖐️ Hello @${{ github.actor }}! - Thank you for your contribution. We will review your pull request soon. Please make sure you have followed our contributing guidelines. - `; - - github.rest.issues.createComment({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - body: prComment - }); - - github.rest.issues.update({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['enhancement', 'new'] - }); diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml new file mode 100644 index 0000000..36d9641 --- /dev/null +++ b/.github/workflows/sync-labels.yml @@ -0,0 +1,23 @@ +name: Sync labels + +on: + push: + branches: + - main + - master + - stable + paths: + - .github/labels.yml + +jobs: + labels: + permissions: write-all + name: Sync labels + runs-on: ubuntu-latest + steps: + - name: Check out code from GitHub + uses: actions/checkout@v2 + - name: Run Label Syncer + uses: micnncim/action-label-syncer@v1.2.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/TODO.txt b/TODO.txt deleted file mode 100644 index 45d491f..0000000 --- a/TODO.txt +++ /dev/null @@ -1,8 +0,0 @@ -- Refactor self giving to cmds -- Add precompilation of cmds dir -- Test imports manager on challenging cases -- Add workflows to check obfuscation on mac and linux -- Add workflows to check onefile/multifile legacy/global obfuscation -- Add data from github template repo -- Rename the project -- Publish as python lib \ No newline at end of file diff --git a/docs/ChangeLog.md b/docs/ChangeLog.md new file mode 100644 index 0000000..afd4e80 --- /dev/null +++ b/docs/ChangeLog.md @@ -0,0 +1,5 @@ +# Change log + +### Program v3.1.0.0 + +--- diff --git a/docs/CommitMessagesGuidance.md b/docs/CommitMessagesGuidance.md new file mode 100644 index 0000000..15e2082 --- /dev/null +++ b/docs/CommitMessagesGuidance.md @@ -0,0 +1,49 @@ +# Commit messages guidance + +### Basic suggestion + +- Don't mess all changes in one commit, commit per change +- Write exact and understandable subject +- Provide more detailed description +- Avoid non linear commit history + +### Structure + +Commit should be made up with three parts: + +``` + + + + + +``` + +#### Subject + +``` +(optional scope): (50 characters max) + +example: +chore: Update Python version to use newer libs +``` + +#### Description + +``` +More recent versions of important project libs no longer support Python +3.6. This has prevented us from using new features offered by such libs. +Add support for Python 3.12. +``` + +### Types table + +| Type | Description | +| :----------- | :------------------------------------------ | +| **feat** | Introduce a new feature to the codebase | +| **fix** | Fix a bug in the codebase | +| **docs** | Create/update documentation | +| **style** | Feature and updates related to styling | +| **refactor** | Refactor a specific section of the codebase | +| **test** | Add or update code related to testing | +| **chore** | Regular code maintenance | diff --git a/docs/Todo.md b/docs/Todo.md new file mode 100644 index 0000000..db39c78 --- /dev/null +++ b/docs/Todo.md @@ -0,0 +1,19 @@ +# Todo list + +### Height Priority + +- [ ] Test imports manager on challenging cases +- [ ] Rename the project + +### Medium Priority + +- [ ] Add workflows to check obfuscation on mac and linux +- [ ] Add workflows to check onefile/multifile legacy/global obfuscation + +### Low Priority + +- [ ] Refactor self giving to cmds +- [ ] Add precompilation of cmds dir +- [ ] Publish as python lib + +### Suspended From be0d6eb7ddb90a5d5d973e0231096dd46e6fee39 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Fri, 5 Sep 2025 22:51:08 +0300 Subject: [PATCH 15/28] test: separated tests and added more --- .github/workflows/linux-tests.yml | 31 +++++ .github/workflows/mac-tests.yml | 31 +++++ .../workflows/{tests.yml => win-tests.yml} | 128 ++++++++++++------ 3 files changed, 152 insertions(+), 38 deletions(-) create mode 100644 .github/workflows/linux-tests.yml create mode 100644 .github/workflows/mac-tests.yml rename .github/workflows/{tests.yml => win-tests.yml} (62%) diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml new file mode 100644 index 0000000..81ac3fd --- /dev/null +++ b/.github/workflows/linux-tests.yml @@ -0,0 +1,31 @@ +name: Linux tests + +on: + push: + branches: [ stable, WIP ] + pull_request: + branches: [ stable, WIP ] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Build Executable + working-directory: scr + run: | + chmod +x build-linux.sh + ./build-linux.sh + - name: Archive Executable + uses: actions/upload-artifact@v4 + with: + name: py-shield-linux + path: scr/py-shield \ No newline at end of file diff --git a/.github/workflows/mac-tests.yml b/.github/workflows/mac-tests.yml new file mode 100644 index 0000000..adb90fe --- /dev/null +++ b/.github/workflows/mac-tests.yml @@ -0,0 +1,31 @@ +name: MacOS tests + +on: + push: + branches: [ stable, WIP ] + pull_request: + branches: [ stable, WIP ] + +jobs: + build: + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + - name: Build Executable + working-directory: scr + run: | + chmod +x build-mac.sh + ./build-mac.sh + - name: Archive Executable + uses: actions/upload-artifact@v4 + with: + name: py-shield-mac + path: scr/py-shield \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/win-tests.yml similarity index 62% rename from .github/workflows/tests.yml rename to .github/workflows/win-tests.yml index a873831..5e35ef2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/win-tests.yml @@ -1,4 +1,4 @@ -name: tests +name: Windows tests on: push: @@ -34,54 +34,106 @@ jobs: name: py-shield-win path: scr/py-shield.exe - linux-build: - runs-on: ubuntu-latest + obfuscation-multifile: + needs: ["win-build"] + runs-on: windows-latest + steps: - uses: actions/checkout@v4 + - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' - - name: Install dependencies + + - name: Install Dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - - name: Build Executable - working-directory: scr - run: | - chmod +x build-linux.sh - ./build-linux.sh - - name: Archive Executable - uses: actions/upload-artifact@v4 + + - name: Download Executable + uses: actions/download-artifact@v4 with: - name: py-shield-linux - path: scr/py-shield + name: py-shield-win + path: "examples" + + - name: Preparation For Tests + working-directory: examples + run: | + move "py-shield.exe" "multifile" + Remove-Item -Path "multifile\obfuscated" -Recurse -Force + + - name: Obfuscation + working-directory: examples\multifile + run: | + .\py-shield.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input --files lib.py main.py + + - name: Testing + working-directory: examples\multifile\obfuscated + run: | + $OUTPUT = $(python main.py) + Write-Host "Output: " + Write-Host $OUTPUT + + Write-Host "Result: " + if ($OUTPUT -eq "hello world") { + Write-Host "Success: main.py output contains 'hello world'" + } else { + Write-Host "Failure: main.py output does not contain 'hello world'" + exit 1 + } + + obfuscation-onefile: + needs: ["win-build"] + runs-on: windows-latest - mac-build: - runs-on: macos-latest steps: - uses: actions/checkout@v4 + - name: Set up Python uses: actions/setup-python@v5 with: python-version: '3.12' - - name: Install dependencies + + - name: Install Dependencies run: | python -m pip install --upgrade pip pip install -r requirements.txt - - name: Build Executable - working-directory: scr - run: | - chmod +x build-mac.sh - ./build-mac.sh - - name: Archive Executable - uses: actions/upload-artifact@v4 + + - name: Download Executable + uses: actions/download-artifact@v4 with: - name: py-shield-mac - path: scr/py-shield + name: py-shield-win + path: "examples" + + - name: Preparation For Tests + working-directory: examples + run: | + move "py-shield.exe" "onefile" + Remove-Item -Path "onefile\obfuscated" -Recurse -Force + + - name: Obfuscation + working-directory: examples\onefile + run: | + .\py-shield.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input main.py + + - name: Testing + working-directory: examples\onefile\obfuscated + run: | + $OUTPUT = $(python main.py) + Write-Host "Output: " + Write-Host $OUTPUT - obfuscation-test: - needs: ["win-build", "linux-build", "mac-build"] + Write-Host "Result: " + if ($OUTPUT -eq "hello world") { + Write-Host "Success: main.py output contains 'hello world'" + } else { + Write-Host "Failure: main.py output does not contain 'hello world'" + exit 1 + } + + obfuscationlegacy-multifile: + needs: ["win-build"] runs-on: windows-latest steps: @@ -106,16 +158,16 @@ jobs: - name: Preparation For Tests working-directory: examples run: | - move "py-shield.exe" "multifile" - Remove-Item -Path "multifile\obfuscated" -Recurse -Force + move "py-shield.exe" "multifile-legacy" + Remove-Item -Path "multifile-legacy\obfuscated" -Recurse -Force - name: Obfuscation - working-directory: examples\multifile + working-directory: examples\multifile-legacy run: | - .\py-shield.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input --files lib.py main.py + .\py-shield obfuscatelegacy --no-input --mode 3 --loops 6 --files lib.py,main.py - name: Testing - working-directory: examples\multifile\obfuscated + working-directory: examples\multifile-legacy\obfuscated run: | $OUTPUT = $(python main.py) Write-Host "Output: " @@ -129,8 +181,8 @@ jobs: exit 1 } - obfuscationlegacy-test: - needs: ["win-build", "linux-build", "mac-build"] + obfuscationlegacy-onefile: + needs: ["win-build"] runs-on: windows-latest steps: @@ -155,16 +207,16 @@ jobs: - name: Preparation For Tests working-directory: examples run: | - move "py-shield.exe" "onefile" - Remove-Item -Path "onefile\obfuscated" -Recurse -Force + move "py-shield.exe" "onefile-legacy" + Remove-Item -Path "onefile-legacy\obfuscated" -Recurse -Force - name: Obfuscation - working-directory: examples\onefile + working-directory: examples\onefile-legacy run: | .\py-shield obfuscatelegacy --no-input --mode 3 --loops 6 --files main.py - name: Testing - working-directory: examples\onefile\obfuscated + working-directory: examples\onefile-legacy\obfuscated run: | $OUTPUT = $(python main.py) Write-Host "Output: " From b8a8941727553c80c77f97ae64631b48951094df Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Fri, 5 Sep 2025 23:18:06 +0300 Subject: [PATCH 16/28] test: added mac and linux tests --- .github/workflows/linux-tests.yml | 202 +++++++++++++++++++++++++++++- .github/workflows/mac-tests.yml | 202 +++++++++++++++++++++++++++++- .github/workflows/win-tests.yml | 10 +- docs/Todo.md | 4 +- 4 files changed, 409 insertions(+), 9 deletions(-) diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml index 81ac3fd..30663df 100644 --- a/.github/workflows/linux-tests.yml +++ b/.github/workflows/linux-tests.yml @@ -28,4 +28,204 @@ jobs: uses: actions/upload-artifact@v4 with: name: py-shield-linux - path: scr/py-shield \ No newline at end of file + path: scr/py-shield + + obfuscation-multifile-linux: + needs: ["build"] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Download Executable + uses: actions/download-artifact@v4 + with: + name: py-shield-linux + path: "examples" + + - name: Preparation For Tests + working-directory: examples + run: | + mv py-shield multifile/ + chmod +x multifile/py-shield + rm -rf multifile/obfuscated + + - name: Obfuscation + working-directory: examples/multifile + run: | + ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input --files lib.py main.py + + - name: Testing + working-directory: examples/multifile/obfuscated + run: | + OUTPUT=$(python main.py) + echo "Output: " + echo "$OUTPUT" + + echo "Result: " + if [ "$OUTPUT" = "hello world" ]; then + echo "Success: main.py output contains 'hello world'" + else + echo "Failure: main.py output does not contain 'hello world'" + exit 1 + fi + + obfuscation-onefile-linux: + needs: ["build"] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Download Executable + uses: actions/download-artifact@v4 + with: + name: py-shield-linux + path: "examples" + + - name: Preparation For Tests + working-directory: examples + run: | + mv py-shield onefile/ + chmod +x onefile/py-shield + rm -rf onefile/obfuscated + + - name: Obfuscation + working-directory: examples/onefile + run: | + ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input main.py + + - name: Testing + working-directory: examples/onefile/obfuscated + run: | + OUTPUT=$(python main.py) + echo "Output: " + echo "$OUTPUT" + + echo "Result: " + if [ "$OUTPUT" = "hello world" ]; then + echo "Success: main.py output contains 'hello world'" + else + echo "Failure: main.py output does not contain 'hello world'" + exit 1 + fi + + obfuscationlegacy-multifile-linux: + needs: ["build"] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Download Executable + uses: actions/download-artifact@v4 + with: + name: py-shield-linux + path: "examples" + + - name: Preparation For Tests + working-directory: examples + run: | + mv py-shield multifile-legacy/ + chmod +x multifile-legacy/py-shield + rm -rf multifile-legacy/obfuscated + + - name: Obfuscation + working-directory: examples/multifile-legacy + run: | + ./py-shield obfuscatelegacy --no-input --mode 3 --loops 6 --files lib.py,main.py + + - name: Testing + working-directory: examples/multifile-legacy/obfuscated + run: | + OUTPUT=$(python main.py) + echo "Output: " + echo "$OUTPUT" + + echo "Result: " + if [ "$OUTPUT" = "hello world" ]; then + echo "Success: main.py output contains 'hello world'" + else + echo "Failure: main.py output does not contain 'hello world'" + exit 1 + fi + + obfuscationlegacy-onefile-linux: + needs: ["build"] + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Download Executable + uses: actions/download-artifact@v4 + with: + name: py-shield-linux + path: "examples" + + - name: Preparation For Tests + working-directory: examples + run: | + mv py-shield onefile-legacy/ + chmod +x onefile-legacy/py-shield + rm -rf onefile-legacy/obfuscated + + - name: Obfuscation + working-directory: examples/onefile-legacy + run: | + ./py-shield obfuscatelegacy --no-input --mode 3 --loops 6 --files main.py + + - name: Testing + working-directory: examples/onefile-legacy/obfuscated + run: | + OUTPUT=$(python main.py) + echo "Output: " + echo "$OUTPUT" + + echo "Result: " + if [ "$OUTPUT" = "hello world" ]; then + echo "Success: main.py output contains 'hello world'" + else + echo "Failure: main.py output does not contain 'hello world'" + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/mac-tests.yml b/.github/workflows/mac-tests.yml index adb90fe..a488886 100644 --- a/.github/workflows/mac-tests.yml +++ b/.github/workflows/mac-tests.yml @@ -28,4 +28,204 @@ jobs: uses: actions/upload-artifact@v4 with: name: py-shield-mac - path: scr/py-shield \ No newline at end of file + path: scr/py-shield + + obfuscation-multifile-macos: + needs: ["build"] + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Download Executable + uses: actions/download-artifact@v4 + with: + name: py-shield-macos + path: "examples" + + - name: Preparation For Tests + working-directory: examples + run: | + mv py-shield multifile/ + chmod +x multifile/py-shield + rm -rf multifile/obfuscated + + - name: Obfuscation + working-directory: examples/multifile + run: | + ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input --files lib.py main.py + + - name: Testing + working-directory: examples/multifile/obfuscated + run: | + OUTPUT=$(python main.py) + echo "Output: " + echo "$OUTPUT" + + echo "Result: " + if [ "$OUTPUT" = "hello world" ]; then + echo "Success: main.py output contains 'hello world'" + else + echo "Failure: main.py output does not contain 'hello world'" + exit 1 + fi + + obfuscation-onefile-macos: + needs: ["build"] + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Download Executable + uses: actions/download-artifact@v4 + with: + name: py-shield-macos + path: "examples" + + - name: Preparation For Tests + working-directory: examples + run: | + mv py-shield onefile/ + chmod +x onefile/py-shield + rm -rf onefile/obfuscated + + - name: Obfuscation + working-directory: examples/onefile + run: | + ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input main.py + + - name: Testing + working-directory: examples/onefile/obfuscated + run: | + OUTPUT=$(python main.py) + echo "Output: " + echo "$OUTPUT" + + echo "Result: " + if [ "$OUTPUT" = "hello world" ]; then + echo "Success: main.py output contains 'hello world'" + else + echo "Failure: main.py output does not contain 'hello world'" + exit 1 + fi + + obfuscationlegacy-multifile-macos: + needs: ["build"] + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Download Executable + uses: actions/download-artifact@v4 + with: + name: py-shield-macos + path: "examples" + + - name: Preparation For Tests + working-directory: examples + run: | + mv py-shield multifile-legacy/ + chmod +x multifile-legacy/py-shield + rm -rf multifile-legacy/obfuscated + + - name: Obfuscation + working-directory: examples/multifile-legacy + run: | + ./py-shield obfuscatelegacy --no-input --mode 3 --loops 6 --files lib.py,main.py + + - name: Testing + working-directory: examples/multifile-legacy/obfuscated + run: | + OUTPUT=$(python main.py) + echo "Output: " + echo "$OUTPUT" + + echo "Result: " + if [ "$OUTPUT" = "hello world" ]; then + echo "Success: main.py output contains 'hello world'" + else + echo "Failure: main.py output does not contain 'hello world'" + exit 1 + fi + + obfuscationlegacy-onefile-macos: + needs: ["build"] + runs-on: macos-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install Dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Download Executable + uses: actions/download-artifact@v4 + with: + name: py-shield-macos + path: "examples" + + - name: Preparation For Tests + working-directory: examples + run: | + mv py-shield onefile-legacy/ + chmod +x onefile-legacy/py-shield + rm -rf onefile-legacy/obfuscated + + - name: Obfuscation + working-directory: examples/onefile-legacy + run: | + ./py-shield obfuscatelegacy --no-input --mode 3 --loops 6 --files main.py + + - name: Testing + working-directory: examples/onefile-legacy/obfuscated + run: | + OUTPUT=$(python main.py) + echo "Output: " + echo "$OUTPUT" + + echo "Result: " + if [ "$OUTPUT" = "hello world" ]; then + echo "Success: main.py output contains 'hello world'" + else + echo "Failure: main.py output does not contain 'hello world'" + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/win-tests.yml b/.github/workflows/win-tests.yml index 5e35ef2..fb67877 100644 --- a/.github/workflows/win-tests.yml +++ b/.github/workflows/win-tests.yml @@ -7,7 +7,7 @@ on: branches: [ stable, WIP ] jobs: - win-build: + build: runs-on: windows-latest steps: @@ -35,7 +35,7 @@ jobs: path: scr/py-shield.exe obfuscation-multifile: - needs: ["win-build"] + needs: ["build"] runs-on: windows-latest steps: @@ -84,7 +84,7 @@ jobs: } obfuscation-onefile: - needs: ["win-build"] + needs: ["build"] runs-on: windows-latest steps: @@ -133,7 +133,7 @@ jobs: } obfuscationlegacy-multifile: - needs: ["win-build"] + needs: ["build"] runs-on: windows-latest steps: @@ -182,7 +182,7 @@ jobs: } obfuscationlegacy-onefile: - needs: ["win-build"] + needs: ["build"] runs-on: windows-latest steps: diff --git a/docs/Todo.md b/docs/Todo.md index db39c78..1e655f6 100644 --- a/docs/Todo.md +++ b/docs/Todo.md @@ -7,8 +7,8 @@ ### Medium Priority -- [ ] Add workflows to check obfuscation on mac and linux -- [ ] Add workflows to check onefile/multifile legacy/global obfuscation +- [x] Add workflows to check obfuscation on mac and linux +- [x] Add workflows to check onefile/multifile legacy/global obfuscation ### Low Priority From 169402aa8536dac46e371928334e3cfe42e0ed3f Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Fri, 5 Sep 2025 23:25:57 +0300 Subject: [PATCH 17/28] fix: fixed imports tested and fixed imports --- docs/Todo.md | 2 +- scr/commands/obfuscation/obfuscate.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Todo.md b/docs/Todo.md index 1e655f6..217adda 100644 --- a/docs/Todo.md +++ b/docs/Todo.md @@ -2,7 +2,7 @@ ### Height Priority -- [ ] Test imports manager on challenging cases +- [x] Test imports manager on challenging cases - [ ] Rename the project ### Medium Priority diff --git a/scr/commands/obfuscation/obfuscate.py b/scr/commands/obfuscation/obfuscate.py index aefadb2..4417d45 100644 --- a/scr/commands/obfuscation/obfuscate.py +++ b/scr/commands/obfuscation/obfuscate.py @@ -211,7 +211,7 @@ def SaveFile(self, filename, filepath, content, entrypoint = False): if entrypoint: imports = "" for module in self.imports: - imp+=f"import {module}\n" + imports+=f"import {module}\n" filepath = self.this.options["--output"]+sep+filepath makedirs(filepath, exist_ok=True) From c5861be8804dab7237ae11bf3dcdc12827032546 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Fri, 5 Sep 2025 23:26:17 +0300 Subject: [PATCH 18/28] test: added --follow-imports to test --- .github/workflows/linux-tests.yml | 8 ++++---- .github/workflows/win-tests.yml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml index 30663df..f8a6c17 100644 --- a/.github/workflows/linux-tests.yml +++ b/.github/workflows/linux-tests.yml @@ -63,7 +63,7 @@ jobs: - name: Obfuscation working-directory: examples/multifile run: | - ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input --files lib.py main.py + ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py - name: Testing working-directory: examples/multifile/obfuscated @@ -113,7 +113,7 @@ jobs: - name: Obfuscation working-directory: examples/onefile run: | - ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input main.py + ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py - name: Testing working-directory: examples/onefile/obfuscated @@ -163,7 +163,7 @@ jobs: - name: Obfuscation working-directory: examples/multifile-legacy run: | - ./py-shield obfuscatelegacy --no-input --mode 3 --loops 6 --files lib.py,main.py + ./py-shield obfuscatelegacy --no-input --mode 4 --loops 6 --files lib.py,main.py - name: Testing working-directory: examples/multifile-legacy/obfuscated @@ -213,7 +213,7 @@ jobs: - name: Obfuscation working-directory: examples/onefile-legacy run: | - ./py-shield obfuscatelegacy --no-input --mode 3 --loops 6 --files main.py + ./py-shield obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py - name: Testing working-directory: examples/onefile-legacy/obfuscated diff --git a/.github/workflows/win-tests.yml b/.github/workflows/win-tests.yml index fb67877..2bbb807 100644 --- a/.github/workflows/win-tests.yml +++ b/.github/workflows/win-tests.yml @@ -66,7 +66,7 @@ jobs: - name: Obfuscation working-directory: examples\multifile run: | - .\py-shield.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input --files lib.py main.py + .\py-shield.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py - name: Testing working-directory: examples\multifile\obfuscated @@ -115,7 +115,7 @@ jobs: - name: Obfuscation working-directory: examples\onefile run: | - .\py-shield.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input main.py + .\py-shield.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py - name: Testing working-directory: examples\onefile\obfuscated @@ -213,7 +213,7 @@ jobs: - name: Obfuscation working-directory: examples\onefile-legacy run: | - .\py-shield obfuscatelegacy --no-input --mode 3 --loops 6 --files main.py + .\py-shield obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py - name: Testing working-directory: examples\onefile-legacy\obfuscated From 959d1b333b078fb120fa3795459ca754eb5b6f19 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Fri, 5 Sep 2025 23:26:29 +0300 Subject: [PATCH 19/28] test: fixed mac workflows --- .github/workflows/mac-tests.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/mac-tests.yml b/.github/workflows/mac-tests.yml index a488886..183fa1b 100644 --- a/.github/workflows/mac-tests.yml +++ b/.github/workflows/mac-tests.yml @@ -50,7 +50,7 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-macos + name: py-shield-mac path: "examples" - name: Preparation For Tests @@ -63,7 +63,7 @@ jobs: - name: Obfuscation working-directory: examples/multifile run: | - ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input --files lib.py main.py + ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py - name: Testing working-directory: examples/multifile/obfuscated @@ -100,7 +100,7 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-macos + name: py-shield-mac path: "examples" - name: Preparation For Tests @@ -113,7 +113,7 @@ jobs: - name: Obfuscation working-directory: examples/onefile run: | - ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input main.py + ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py - name: Testing working-directory: examples/onefile/obfuscated @@ -150,7 +150,7 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-macos + name: py-shield-mac path: "examples" - name: Preparation For Tests @@ -163,7 +163,7 @@ jobs: - name: Obfuscation working-directory: examples/multifile-legacy run: | - ./py-shield obfuscatelegacy --no-input --mode 3 --loops 6 --files lib.py,main.py + ./py-shield obfuscatelegacy --no-input --mode 4 --loops 6 --files lib.py,main.py - name: Testing working-directory: examples/multifile-legacy/obfuscated @@ -200,7 +200,7 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-macos + name: py-shield-mac path: "examples" - name: Preparation For Tests @@ -213,7 +213,7 @@ jobs: - name: Obfuscation working-directory: examples/onefile-legacy run: | - ./py-shield obfuscatelegacy --no-input --mode 3 --loops 6 --files main.py + ./py-shield obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py - name: Testing working-directory: examples/onefile-legacy/obfuscated From eebb3797d950d0fd8e6708c8a90909e57770db1b Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Sat, 6 Sep 2025 02:13:42 +0300 Subject: [PATCH 20/28] refactor: cmd exec system Replaced side func Handler with build in __init__. Avoided self passing to cmd. Cleaned code from similar lines and names. --- scr/commands/basic/dependencies.py | 47 +++++----- scr/commands/basic/help.py | 10 ++- scr/commands/basic/info.py | 26 +++--- scr/commands/obfuscation/obfuscate.py | 95 ++++++++++----------- scr/commands/obfuscation/obfuscatelegacy.py | 57 ++++++------- scr/config.py | 13 +-- scr/pyshield.py | 21 ++--- scr/utils/optionsParser.py | 8 +- 8 files changed, 133 insertions(+), 144 deletions(-) diff --git a/scr/commands/basic/dependencies.py b/scr/commands/basic/dependencies.py index 9d4397a..0a74cf9 100644 --- a/scr/commands/basic/dependencies.py +++ b/scr/commands/basic/dependencies.py @@ -18,27 +18,6 @@ class Dependencies(Command): "--update": False, } - def Handler(self): - dependencies = ["cryptography", "pycryptodome", "cython", "nuitka", "colorama", "setuptools"] - - if self.options["--show"]: - string = "" - for dep in dependencies: - string+=f"\n {dep}" - Log.Custom(f"Project's dependencies:{string}") - - if self.options["--install"]: - for dep in dependencies: - system(f"pip install {f" --log {Log.logFile}" if Log.logFile else ""}{ "--quiet" if Log.quiet else ""}{ "--no-input" if Log.noInput else ""} {dep}") - - if self.options["--uninstall"]: - for dep in dependencies: - system(f"pip uninstall {f" --log {Log.logFile}" if Log.logFile else ""}{ "--quiet" if Log.quiet else ""}{ "--no-input" if Log.noInput else ""} {dep}") - - if self.options["--update"]: - for dep in dependencies: - system(f"pip install --upgrade {f" --log {Log.logFile}" if Log.logFile else ""}{ "--quiet" if Log.quiet else ""}{ "--no-input" if Log.noInput else ""} {dep}") - help = f''' Usage: py-shield dependencies [options] @@ -59,4 +38,28 @@ def Handler(self): --show*` -> show all dependencies of the program. --install*` -> install all dependencies of the program. --uninstall*` -> uninstall all dependencies of the program. - --update*` -> update all dependencies of the program''' \ No newline at end of file + --update*` -> update all dependencies of the program''' + + def __init__(self): + dependencies = ["cryptography", "pycryptodome", "cython", "nuitka", "colorama", "setuptools"] + + if self.options["--show"]: + string = "" + for dep in dependencies: + string+=f"\n {dep}" + Log.Custom(f"Project's dependencies:{string}") + + if self.options["--install"]: + for dep in dependencies: + system(f"pip install {f" --log {Log.logFile}" if Log.logFile else ""}{ "--quiet" if Log.quiet else ""}{ "--no-input" if Log.noInput else ""} {dep}") + Log.Success("Dependencies installed") + + if self.options["--uninstall"]: + for dep in dependencies: + system(f"pip uninstall {f" --log {Log.logFile}" if Log.logFile else ""}{ "--quiet" if Log.quiet else ""}{ "--no-input" if Log.noInput else ""} {dep}") + Log.Success("Dependencies uninstalled") + + if self.options["--update"]: + for dep in dependencies: + system(f"pip install --upgrade {f" --log {Log.logFile}" if Log.logFile else ""}{ "--quiet" if Log.quiet else ""}{ "--no-input" if Log.noInput else ""} {dep}") + Log.Success("Dependencies updated") \ No newline at end of file diff --git a/scr/commands/basic/help.py b/scr/commands/basic/help.py index f53343a..b7c19e5 100644 --- a/scr/commands/basic/help.py +++ b/scr/commands/basic/help.py @@ -6,9 +6,6 @@ class Help(Command): requiredOptions = [] options = {} - def Handler(command: Command = None): - Log.Custom(command.help) - help = f''' Usage: py-shield [options] @@ -27,4 +24,9 @@ def Handler(command: Command = None): --quiet -> give less output. --log -> write all logs to a file. --no-color -> suppress colored output. - --no-input -> disable prompting for input.''' \ No newline at end of file + --no-input -> disable prompting for input.''' + + def __init__(self, command: Command = None): + if command == None: + command = self + Log.Custom(command.help) \ No newline at end of file diff --git a/scr/commands/basic/info.py b/scr/commands/basic/info.py index d5cf417..889026f 100644 --- a/scr/commands/basic/info.py +++ b/scr/commands/basic/info.py @@ -15,19 +15,6 @@ class Info(Command): "--description": False, } - def Handler(self): - if self.options["--all"]: - Log.Custom(f"{NAME} version {VERSION}\nby {AUTHOR}\n{DESCRIPTION}\nRepo: {Fore.BLUE if Log.colored else ""}{URL}{Fore.RESET}") - - elif self.options["--version"]: - Log.Custom(f"{NAME} version {VERSION}") - - elif self.options["--url"]: - Log.Custom(f"Repo: {Fore.BLUE if Log.colored else ""}{URL}{Fore.RESET}") - - elif self.options["--description"]: - Log.Custom(f"{DESCRIPTION}") - help = f''' Usage: py-shield info [options] @@ -47,3 +34,16 @@ def Handler(self): --version*` -> show version of the program. --url*` -> show URL of program's github repo. --description*` -> show description of the program.''' + + def __init__(self): + if self.options["--all"]: + Log.Custom(f"{NAME} version {VERSION}\nby {AUTHOR}\n{DESCRIPTION}\nRepo: {Fore.BLUE if Log.colored else ""}{URL}{Fore.RESET}") + + elif self.options["--version"]: + Log.Custom(f"{NAME} version {VERSION}") + + elif self.options["--url"]: + Log.Custom(f"Repo: {Fore.BLUE if Log.colored else ""}{URL}{Fore.RESET}") + + elif self.options["--description"]: + Log.Custom(f"{DESCRIPTION}") \ No newline at end of file diff --git a/scr/commands/obfuscation/obfuscate.py b/scr/commands/obfuscation/obfuscate.py index 4417d45..066d068 100644 --- a/scr/commands/obfuscation/obfuscate.py +++ b/scr/commands/obfuscation/obfuscate.py @@ -30,9 +30,6 @@ class Obfuscate(Command): "entrypoint": "" } - def Handler(self): - PerformObfuscation(self) - help = f''' Usage: py-shield obfuscate [options] main.py @@ -64,36 +61,38 @@ def Handler(self): --output -> output dir. --follow-imports -> add all imports to the protected script.''' -class PerformObfuscation: - def __init__(self, this: Command): - self.this = this - self.workingDir = getcwd() - self.imports = [] + def __init__(self): + self.InitVars() self.CheckOptions() self.ObfuscateFiles() - self.obfuscation.CreateExecutor(self.this.options["--output"]) + self.obfuscation.CreateExecutor(self.options["--output"]) + Log.Success("Obfuscation compleated") + + def InitVars(self): + self.workingDir = getcwd() + self.imports = [] def CheckOptions(self): - Log.Info("Obfuscation.") - if self.this.options["--recursive"] < 0: + Log.Info("Obfuscation") + if self.options["--recursive"] < 0: raise Exception("Invalid --recursive value.") - if not self.this.options["--output"]: - self.this.options["--output"] = "obfuscated" + if not self.options["--output"]: + self.options["--output"] = "obfuscated" - if path.exists(self.this.options["--output"]): - Log.Warning(f"Output directory already exists: \"{self.this.options['--output']}\".") + if path.exists(self.options["--output"]): + Log.Warning(f"Output directory already exists: \"{self.options['--output']}\".") responce = "" while responce != "y" or responce != "n" or responce != "ignore": responce = Log.Question("Override directory? (y/n)").lower() match responce: case "ignore": - rmtree(self.this.options["--output"]) + rmtree(self.options["--output"]) Log.Info("Directory overridden.") break case "y": - rmtree(self.this.options["--output"]) + rmtree(self.options["--output"]) Log.Success("Directory overridden.") break case "n": @@ -103,51 +102,51 @@ def CheckOptions(self): Log.Fail("Invalid response. Please enter 'y' or 'n'.") print() - self.entryPoint = self.this.options["entrypoint"] + self.entryPoint = self.options["entrypoint"] if not path.exists(self.entryPoint) or not path.isfile(self.entryPoint) or not self.entryPoint.endswith(".py"): raise Exception(f"Invalid entrypoint path: \"{self.entryPoint}\".") if path.isabs(self.entryPoint): raise Exception(f"Abs path is unsupported: \"{self.entryPoint}\"") - for file in self.this.options["--files"]: + for file in self.options["--files"]: if not path.exists(file) or not path.isfile(file) or not file.endswith(".py"): raise Exception(f"Invalid file path: \"{file}\".") if path.isabs(file): raise Exception(f"Abs path is unsupported: \"{file}\"") - for dir in self.this.options["--dirs"]: + for dir in self.options["--dirs"]: if not path.exists(dir) or not path.isdir(dir): raise Exception(f"Invalid directory path: \"{dir}\".") if path.isabs(dir): raise Exception(f"Abs path is unsupported: \"{dir}\"") Log.Info(f"Entrypoint file: {self.entryPoint}") - if self.this.options["--files"]: - Log.Info(f"Included files: {self.this.options["--files"]}") - if self.this.options["--dirs"]: - Log.Info(f"Included dirs: {self.this.options["--dirs"]}") + if self.options["--files"]: + Log.Info(f"Included files: {self.options["--files"]}") + if self.options["--dirs"]: + Log.Info(f"Included dirs: {self.options["--dirs"]}") - methods = f"{"hashdata, " if self.this.options["--hashdata"] else ""}{"fernet, " if self.this.options["--fernet"] else ""}{"aes, " if self.this.options["--aes"] else ""}{"chacha20, " if self.this.options["--chacha"] else ""}{"salsa20, " if self.this.options["--salsa"] else ""}{"base64, " if self.this.options["--base64"] else ""}{f"recursive<{self.this.options["--recursive"]}>, " if self.this.options["--recursive"]>0 else ""}"[:-2] + methods = f"{"hashdata, " if self.options["--hashdata"] else ""}{"fernet, " if self.options["--fernet"] else ""}{"aes, " if self.options["--aes"] else ""}{"chacha20, " if self.options["--chacha"] else ""}{"salsa20, " if self.options["--salsa"] else ""}{"base64, " if self.options["--base64"] else ""}{f"recursive<{self.options["--recursive"]}>, " if self.options["--recursive"]>0 else ""}"[:-2] if methods: Log.Info(f"Obfuscation methods: {methods}") - options = f"{"follow-imports, " if self.this.options["--follow-imports"] else ""}{"no-protect, " if self.this.options["--no-protect"] else ""}"[:-2] + options = f"{"follow-imports, " if self.options["--follow-imports"] else ""}{"no-protect, " if self.options["--no-protect"] else ""}"[:-2] if options: Log.Info(f"Additional options: {options}") - Log.Info(f"output dir: {self.this.options["--output"]}\n") + Log.Info(f"output dir: {self.options["--output"]}\n") def ObfuscateFiles(self): - self.obfuscation = MainObfuscation(self.this.options["--hashdata"], - self.this.options["--fernet"], - self.this.options["--aes"], - self.this.options["--chacha"], - self.this.options["--salsa"], - self.this.options["--base64"], - self.this.options["--recursive"], - self.this.options["--no-protect"], - self.this.options["--enc-exec"]) - - for file in self.this.options["--files"]: + self.obfuscation = MainObfuscation(self.options["--hashdata"], + self.options["--fernet"], + self.options["--aes"], + self.options["--chacha"], + self.options["--salsa"], + self.options["--base64"], + self.options["--recursive"], + self.options["--no-protect"], + self.options["--enc-exec"]) + + for file in self.options["--files"]: with open(file, "r", encoding="utf-8") as pyFile: context = pyFile.read() if not context: @@ -165,9 +164,9 @@ def ObfuscateFiles(self): context = self.obfuscation.Wrap(context) self.SaveFile(filename, filepath, context) - self.obfuscation.ProtectFile(self.this.options["--output"], filepath+sep+filename) + self.obfuscation.ProtectFile(self.options["--output"], filepath+sep+filename) - for dir in self.this.options["--dirs"]: + for dir in self.options["--dirs"]: for dirpath, dirnames, filenames in walk(dir): for filename in filenames: @@ -187,7 +186,7 @@ def ObfuscateFiles(self): context = self.obfuscation.Wrap(context) self.SaveFile(filename , dirpath, context) - self.obfuscation.ProtectFile(self.this.options["--output"], dirpath+sep+filename) + self.obfuscation.ProtectFile(self.options["--output"], dirpath+sep+filename) with open(self.entryPoint, "r", encoding="utf-8") as pyFile: context = pyFile.read() @@ -205,7 +204,7 @@ def ObfuscateFiles(self): context = self.obfuscation.Wrap(context) self.SaveFile(filename, filepath, context, entrypoint = True) - self.obfuscation.ProtectFile(self.this.options["--output"], filepath+sep+filename) + self.obfuscation.ProtectFile(self.options["--output"], filepath+sep+filename) def SaveFile(self, filename, filepath, content, entrypoint = False): if entrypoint: @@ -213,7 +212,7 @@ def SaveFile(self, filename, filepath, content, entrypoint = False): for module in self.imports: imports+=f"import {module}\n" - filepath = self.this.options["--output"]+sep+filepath + filepath = self.options["--output"]+sep+filepath makedirs(filepath, exist_ok=True) with open(filepath+sep+filename, "w", encoding="utf-8") as file: @@ -224,20 +223,20 @@ def SaveFile(self, filename, filepath, content, entrypoint = False): Log.Info(f"{filename} saved in {filepath[:-1]}") def FollowImports(self, content): - if self.this.options["--follow-imports"]: + if self.options["--follow-imports"]: modules = GetImports(content) - if not self.this.options["--no-protect"]: + if not self.options["--no-protect"]: modules.append("hashlib") modules.append("os") - if self.this.options["--chacha"] or self.this.options["--salsa"]: + if self.options["--chacha"] or self.options["--salsa"]: modules.append("Crypto.Cipher") - if self.this.options["--aes"]: + if self.options["--aes"]: modules.append("cryptography.hazmat.primitives.ciphers.aead") - if self.this.options["--fernet"]: + if self.options["--fernet"]: modules.append("cryptography.fernet") modules.append("sys") diff --git a/scr/commands/obfuscation/obfuscatelegacy.py b/scr/commands/obfuscation/obfuscatelegacy.py index bf1c027..03706a1 100644 --- a/scr/commands/obfuscation/obfuscatelegacy.py +++ b/scr/commands/obfuscation/obfuscatelegacy.py @@ -21,9 +21,6 @@ class Obfuscatelegacy(Command): "--output": "" } - def Handler(self): - PerformLegacyObfuscation(self) - help = f''' Usage: py-shield obfuscatelegacy [options] @@ -47,37 +44,39 @@ def Handler(self): --files * -> files for obfuscation(required files or/and dir). --output -> output dir.''' -class PerformLegacyObfuscation: - def __init__(self, this: Command): - self.this = this - self.workingDir = getcwd() + def __init__(self): + self.InitVars() self.CheckOptions() self.ObfuscateFiles() + Log.Success("Legacy obfuscation compleated") + + def InitVars(self): + self.workingDir = getcwd() def CheckOptions(self): Log.Info("Legacy obfuscation.") - if self.this.options["--loops"] < 1: + if self.options["--loops"] < 1: raise Exception("Invalid --loops value.") - if self.this.options["--mode"] < 1 or self.this.options["--mode"] > 4: + if self.options["--mode"] < 1 or self.options["--mode"] > 4: raise Exception("Invalid --mode value.") - if not self.this.options["--output"]: - self.this.options["--output"] = "obfuscated" + if not self.options["--output"]: + self.options["--output"] = "obfuscated" - if path.exists(self.this.options["--output"]): - Log.Warning(f"Output directory already exists: \"{self.this.options['--output']}\".") + if path.exists(self.options["--output"]): + Log.Warning(f"Output directory already exists: \"{self.options['--output']}\".") responce = "" while responce != "y" or responce != "n" or responce != "ignore": responce = Log.Question("Override directory? (y/n)").lower() match responce: case "ignore": - rmtree(self.this.options["--output"]) + rmtree(self.options["--output"]) Log.Info("Directory overridden.") break case "y": - rmtree(self.this.options["--output"]) + rmtree(self.options["--output"]) Log.Success("Directory overridden.") break case "n": @@ -87,29 +86,29 @@ def CheckOptions(self): Log.Fail("Invalid response. Please enter 'y' or 'n'.") print() - for file in self.this.options["--files"]: + for file in self.options["--files"]: if not path.exists(file) or not path.isfile(file) or not file.endswith(".py"): raise Exception(f"Invalid file path: \"{file}\".") if path.isabs(file): raise Exception(f"Abs path is unsupported: \"{file}\"") - for dir in self.this.options["--dirs"]: + for dir in self.options["--dirs"]: if not path.exists(dir) or not path.isdir(dir): raise Exception(f"Invalid directory path: \"{dir}\".") if path.isabs(dir): raise Exception(f"Abs path is unsupported: \"{dir}\"") - if self.this.options["--files"]: - Log.Info(f"Included files: {self.this.options["--files"]}") - if self.this.options["--dirs"]: - Log.Info(f"Included dirs: {self.this.options["--dirs"]}") + if self.options["--files"]: + Log.Info(f"Included files: {self.options["--files"]}") + if self.options["--dirs"]: + Log.Info(f"Included dirs: {self.options["--dirs"]}") - Log.Info(f"loops amount: {self.this.options["--loops"]}") - Log.Info(f"obfuscation mode: {self.this.options["--mode"]}") - Log.Info(f"output dir: {self.this.options["--output"]}\n") + Log.Info(f"loops amount: {self.options["--loops"]}") + Log.Info(f"obfuscation mode: {self.options["--mode"]}") + Log.Info(f"output dir: {self.options["--output"]}\n") def ObfuscateFiles(self): - for file in self.this.options["--files"]: + for file in self.options["--files"]: with open(file, "r", encoding="utf-8") as pyFile: context = pyFile.read() if not context: @@ -122,13 +121,13 @@ def ObfuscateFiles(self): context = RemoveComments(context) - obfuscator = LegacyObfuscation(self.this.options["--mode"], self.this.options["--loops"], LegacyObfuscation.GenSeperator(12)) + obfuscator = LegacyObfuscation(self.options["--mode"], self.options["--loops"], LegacyObfuscation.GenSeperator(12)) context = obfuscator.Encrypt(context) context = obfuscator.Wrap(context) self.SaveFile(filename, filepath, context) - for dir in self.this.options["--dirs"]: + for dir in self.options["--dirs"]: for dirpath, dirnames, filenames in walk(dir): for filename in filenames: @@ -143,14 +142,14 @@ def ObfuscateFiles(self): context = RemoveComments(context) - obfuscator = LegacyObfuscation(self.this.options["--mode"], self.this.options["--loops"], LegacyObfuscation.GenSeperator(12)) + obfuscator = LegacyObfuscation(self.options["--mode"], self.options["--loops"], LegacyObfuscation.GenSeperator(12)) context = obfuscator.Encrypt(context) context = obfuscator.Wrap(context) self.SaveFile(filename , dirpath, context) def SaveFile(self, filename, filepath, content): - filepath = self.this.options["--output"]+sep+filepath + filepath = self.options["--output"]+sep+filepath makedirs(filepath, exist_ok=True) with open(filepath+sep+filename, "w", encoding="utf-8") as file: diff --git a/scr/config.py b/scr/config.py index 57361c4..a5831d7 100644 --- a/scr/config.py +++ b/scr/config.py @@ -1,4 +1,4 @@ -from abc import ABC, abstractmethod +from abc import ABC NAME = "Py-Shield" AUTHOR = "ByteCorum" @@ -12,11 +12,9 @@ class Command(ABC): options: dict help: str - @abstractmethod - def Handler(self): - pass - # class Name_of_the_command(Command): #note: only first letter should be capital +# def __init__(self): #command handler that will be called when the command is executed +# pass # # #May be left not initialized if your command has no options# # exclusiveOptions = [["--install","--uninstall"], ["--up","--down"]] #groups of options that can't be used together @@ -36,7 +34,4 @@ def Handler(self): # # #Should be always initialized# # help = f'''I\'ll help u''' #help message for the command -# -# @abstractmethod -# def Handler(self):#command handler that will be called when the command is executed -# pass \ No newline at end of file +# \ No newline at end of file diff --git a/scr/pyshield.py b/scr/pyshield.py index 142a528..38031f1 100644 --- a/scr/pyshield.py +++ b/scr/pyshield.py @@ -10,7 +10,6 @@ class PyShield: command: Command = None - commandName: str = None helpCmd: Command = None def __init__(self): @@ -27,21 +26,19 @@ def ParseArgs(self): if len(argv) < 2: raise Exception("missing command name.") - self.commandName = argv[1].title() - self.command = self.GetCommand(self.commandName) + self.command = self.GetCommand(argv[1].title()) except Exception as error: - self.helpCmd.Handler(self.helpCmd) + self.helpCmd(self.helpCmd) Log.Fail("Command parsing failed: "+str(error), True) try: - parser = OptionsParser(self.helpCmd, argv[2:], self.command) + parser = OptionsParser(argv[2:], self.command) parser.Parse() - if parser.ended: + if parser.helpCalled: + self.helpCmd(self.command) exit(0) - parser.Validate() - self.command = parser.command#get filled command object except Exception as error: Log.Fail(f"Options parsing failed: {error}", True) @@ -53,7 +50,6 @@ def GetCommand(self, name): return command - def SearchCommand(self, name: str, path: str): for dirpath, dirnames, filenames in walk(path): for filename in filenames: @@ -72,8 +68,6 @@ def SearchCommand(self, name: str, path: str): issubclass(command, Command) and command.__name__ != 'Command'): - # if (self.helpCmd and command.__name__ == "Help"): - # self.helpCmd = command if (command.__name__ == name): return command @@ -99,7 +93,6 @@ def SetGlobalVars(self): def RunCommand(self): Log.Info(f"{NAME}\n", True) try: - self.command.Handler(self.command) - Log.Success(f"{self.commandName} successfully completed.") + self.command() except Exception as error: - Log.Fail(f"{self.commandName} failed: {error}", True) \ No newline at end of file + Log.Fail(f"Command failed: {error}", True) \ No newline at end of file diff --git a/scr/utils/optionsParser.py b/scr/utils/optionsParser.py index 741488f..ff2e740 100644 --- a/scr/utils/optionsParser.py +++ b/scr/utils/optionsParser.py @@ -2,19 +2,17 @@ from config import Command class OptionsParser: - def __init__(self, helpCmd: Command, argv, command: Command): + def __init__(self, argv, command: Command): self.argv = argv self.argc = len(self.argv) self.command = command - self.helpCmd = helpCmd - self.ended = False# handles the --help command with the highest priority + self.helpCalled = False# handles the --help command with the highest priority def Parse(self): if "--help" in self.argv: - self.helpCmd.Handler(self.command) if self.argc > 1: Log.Warning("--help found, other options ignored.") - self.ended = True + self.helpCalled = True return skipNext = False From 89ce410f295000dc60628e9112b5419408bcf279 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Sat, 6 Sep 2025 02:18:30 +0300 Subject: [PATCH 21/28] fix: enlarged size of separator to avoid conflicts --- docs/Todo.md | 2 +- scr/commands/obfuscation/obfuscatelegacy.py | 4 ++-- scr/utils/obfuscation.py | 6 ++++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/Todo.md b/docs/Todo.md index 217adda..8b00152 100644 --- a/docs/Todo.md +++ b/docs/Todo.md @@ -12,7 +12,7 @@ ### Low Priority -- [ ] Refactor self giving to cmds +- [x] Refactor self giving to cmds - [ ] Add precompilation of cmds dir - [ ] Publish as python lib diff --git a/scr/commands/obfuscation/obfuscatelegacy.py b/scr/commands/obfuscation/obfuscatelegacy.py index 03706a1..f0092db 100644 --- a/scr/commands/obfuscation/obfuscatelegacy.py +++ b/scr/commands/obfuscation/obfuscatelegacy.py @@ -121,7 +121,7 @@ def ObfuscateFiles(self): context = RemoveComments(context) - obfuscator = LegacyObfuscation(self.options["--mode"], self.options["--loops"], LegacyObfuscation.GenSeperator(12)) + obfuscator = LegacyObfuscation(self.options["--mode"], self.options["--loops"], LegacyObfuscation.GenSeperator()) context = obfuscator.Encrypt(context) context = obfuscator.Wrap(context) @@ -142,7 +142,7 @@ def ObfuscateFiles(self): context = RemoveComments(context) - obfuscator = LegacyObfuscation(self.options["--mode"], self.options["--loops"], LegacyObfuscation.GenSeperator(12)) + obfuscator = LegacyObfuscation(self.options["--mode"], self.options["--loops"], LegacyObfuscation.GenSeperator()) context = obfuscator.Encrypt(context) context = obfuscator.Wrap(context) diff --git a/scr/utils/obfuscation.py b/scr/utils/obfuscation.py index dfa64b9..e9bc87e 100644 --- a/scr/utils/obfuscation.py +++ b/scr/utils/obfuscation.py @@ -242,7 +242,7 @@ def _(self): makedirs(outputDir) if self.encExec: - obfuscator = LegacyObfuscation(3, 6, LegacyObfuscation.GenSeperator(12)) + obfuscator = LegacyObfuscation(3, 6, LegacyObfuscation.GenSeperator()) context = obfuscator.Encrypt(context) context = obfuscator.Wrap(context) @@ -396,5 +396,7 @@ def LiteObfuscation(self,content): return f"exec((_)({enccontent}))" @staticmethod - def GenSeperator(length): + def GenSeperator(length = 32): + if length < 12: + raise Exception("Too short separator") return ''.join(choice(ascii_letters+digits) for _ in range(length)) \ No newline at end of file From f8beec75ace5af7ebcd1c4fc669ca06874f2e0c6 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Sat, 6 Sep 2025 02:31:18 +0300 Subject: [PATCH 22/28] style: added assembling indicator --- scr/utils/obfuscation.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scr/utils/obfuscation.py b/scr/utils/obfuscation.py index e9bc87e..655d72f 100644 --- a/scr/utils/obfuscation.py +++ b/scr/utils/obfuscation.py @@ -280,6 +280,8 @@ def AssembleExecutor(self, dir: str): cur = getcwd() chdir(dir) + + Log.Info(f"Assembling executor...") result = run(["python", "assembler.py", "build_ext", "--inplace"], stdout=Log.logFile if Log.logFile else DEVNULL, stderr=PIPE, text=True) From af9d5b7cd2cfc60b0f2cfacfcb3b303b2a38b47f Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Sat, 6 Sep 2025 02:44:27 +0300 Subject: [PATCH 23/28] refactor: minor changes for pylance --- docs/Todo.md | 3 ++- scr/commands/obfuscation/obfuscate.py | 2 +- scr/pyshield.py | 8 ++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/Todo.md b/docs/Todo.md index 8b00152..0c8abbc 100644 --- a/docs/Todo.md +++ b/docs/Todo.md @@ -13,7 +13,8 @@ ### Low Priority - [x] Refactor self giving to cmds -- [ ] Add precompilation of cmds dir - [ ] Publish as python lib ### Suspended + +- [ ] Add precompilation of cmds dir diff --git a/scr/commands/obfuscation/obfuscate.py b/scr/commands/obfuscation/obfuscate.py index 066d068..c320993 100644 --- a/scr/commands/obfuscation/obfuscate.py +++ b/scr/commands/obfuscation/obfuscate.py @@ -207,8 +207,8 @@ def ObfuscateFiles(self): self.obfuscation.ProtectFile(self.options["--output"], filepath+sep+filename) def SaveFile(self, filename, filepath, content, entrypoint = False): + imports = "" if entrypoint: - imports = "" for module in self.imports: imports+=f"import {module}\n" diff --git a/scr/pyshield.py b/scr/pyshield.py index 38031f1..89c4e75 100644 --- a/scr/pyshield.py +++ b/scr/pyshield.py @@ -43,14 +43,14 @@ def ParseArgs(self): except Exception as error: Log.Fail(f"Options parsing failed: {error}", True) - def GetCommand(self, name): - command = self.SearchCommand(name, f"{path.dirname(path.abspath(__file__))}/commands/") + def GetCommand(self, name) -> Command: + command: Command = self.SearchCommand(name, f"{path.dirname(path.abspath(__file__))}/commands/") if not command: raise Exception(f"invalid command name: \"{name}\".") return command - def SearchCommand(self, name: str, path: str): + def SearchCommand(self, name: str, path: str) -> Command: for dirpath, dirnames, filenames in walk(path): for filename in filenames: if filename.endswith(".py") and filename != "__init__.py": @@ -62,7 +62,7 @@ def SearchCommand(self, name: str, path: str): try: spec.loader.exec_module(module) for cmdName in dir(module): - command = getattr(module, cmdName) + command: Command = getattr(module, cmdName) if (isclass(command) and not isabstract(command) and issubclass(command, Command) and From 45666cb249aa4be751487013e7ad5f578e91af77 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:32:12 +0300 Subject: [PATCH 24/28] style: renamed project renamed project to `.pyguard` --- .github/workflows/linux-tests.yml | 36 ++++----- .github/workflows/mac-tests.yml | 36 ++++----- .github/workflows/win-tests.yml | 28 +++---- README.md | 76 +++++++++---------- docs/Todo.md | 2 +- .../executor/{PyShield.py => DotPyGuard.py} | 3 +- examples/multifile-legacy/obfuscate.bat | 2 +- examples/multifile-legacy/obfuscated/lib.py | 2 +- examples/multifile-legacy/obfuscated/main.py | 2 +- examples/multifile/obfuscate.bat | 2 +- examples/multifile/obfuscated/lib.py | 6 +- examples/multifile/obfuscated/main.py | 6 +- examples/onefile-legacy/obfuscate.bat | 2 +- examples/onefile-legacy/obfuscated/main.py | 2 +- examples/onefile/obfuscate.bat | 2 +- examples/onefile/obfuscated/main.py | 6 +- scr/build-linux.sh | 10 +-- scr/build-mac.sh | 10 +-- scr/build-win.cmd | 10 +-- scr/commands/basic/dependencies.py | 4 +- scr/commands/basic/help.py | 4 +- scr/commands/basic/info.py | 4 +- scr/commands/obfuscation/obfuscate.py | 4 +- scr/commands/obfuscation/obfuscatelegacy.py | 4 +- scr/config.py | 4 +- scr/{pyshield.py => dotpyguard.py} | 2 +- scr/main.py | 4 +- scr/utils/obfuscation.py | 18 ++--- 28 files changed, 145 insertions(+), 146 deletions(-) rename examples/executor/{PyShield.py => DotPyGuard.py} (99%) rename scr/{pyshield.py => dotpyguard.py} (99%) diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml index f8a6c17..252e372 100644 --- a/.github/workflows/linux-tests.yml +++ b/.github/workflows/linux-tests.yml @@ -27,8 +27,8 @@ jobs: - name: Archive Executable uses: actions/upload-artifact@v4 with: - name: py-shield-linux - path: scr/py-shield + name: .pyguard-linux + path: scr/.pyguard obfuscation-multifile-linux: needs: ["build"] @@ -50,20 +50,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-linux + name: .pyguard-linux path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv py-shield multifile/ - chmod +x multifile/py-shield + mv .pyguard multifile/ + chmod +x multifile/.pyguard rm -rf multifile/obfuscated - name: Obfuscation working-directory: examples/multifile run: | - ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py + ./.pyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py - name: Testing working-directory: examples/multifile/obfuscated @@ -100,20 +100,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-linux + name: .pyguard-linux path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv py-shield onefile/ - chmod +x onefile/py-shield + mv .pyguard onefile/ + chmod +x onefile/.pyguard rm -rf onefile/obfuscated - name: Obfuscation working-directory: examples/onefile run: | - ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py + ./.pyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py - name: Testing working-directory: examples/onefile/obfuscated @@ -150,20 +150,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-linux + name: .pyguard-linux path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv py-shield multifile-legacy/ - chmod +x multifile-legacy/py-shield + mv .pyguard multifile-legacy/ + chmod +x multifile-legacy/.pyguard rm -rf multifile-legacy/obfuscated - name: Obfuscation working-directory: examples/multifile-legacy run: | - ./py-shield obfuscatelegacy --no-input --mode 4 --loops 6 --files lib.py,main.py + ./.pyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files lib.py,main.py - name: Testing working-directory: examples/multifile-legacy/obfuscated @@ -200,20 +200,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-linux + name: .pyguard-linux path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv py-shield onefile-legacy/ - chmod +x onefile-legacy/py-shield + mv .pyguard onefile-legacy/ + chmod +x onefile-legacy/.pyguard rm -rf onefile-legacy/obfuscated - name: Obfuscation working-directory: examples/onefile-legacy run: | - ./py-shield obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py + ./.pyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py - name: Testing working-directory: examples/onefile-legacy/obfuscated diff --git a/.github/workflows/mac-tests.yml b/.github/workflows/mac-tests.yml index 183fa1b..3751181 100644 --- a/.github/workflows/mac-tests.yml +++ b/.github/workflows/mac-tests.yml @@ -27,8 +27,8 @@ jobs: - name: Archive Executable uses: actions/upload-artifact@v4 with: - name: py-shield-mac - path: scr/py-shield + name: .pyguard-mac + path: scr/.pyguard obfuscation-multifile-macos: needs: ["build"] @@ -50,20 +50,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-mac + name: .pyguard-mac path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv py-shield multifile/ - chmod +x multifile/py-shield + mv .pyguard multifile/ + chmod +x multifile/.pyguard rm -rf multifile/obfuscated - name: Obfuscation working-directory: examples/multifile run: | - ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py + ./.pyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py - name: Testing working-directory: examples/multifile/obfuscated @@ -100,20 +100,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-mac + name: .pyguard-mac path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv py-shield onefile/ - chmod +x onefile/py-shield + mv .pyguard onefile/ + chmod +x onefile/.pyguard rm -rf onefile/obfuscated - name: Obfuscation working-directory: examples/onefile run: | - ./py-shield obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py + ./.pyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py - name: Testing working-directory: examples/onefile/obfuscated @@ -150,20 +150,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-mac + name: .pyguard-mac path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv py-shield multifile-legacy/ - chmod +x multifile-legacy/py-shield + mv .pyguard multifile-legacy/ + chmod +x multifile-legacy/.pyguard rm -rf multifile-legacy/obfuscated - name: Obfuscation working-directory: examples/multifile-legacy run: | - ./py-shield obfuscatelegacy --no-input --mode 4 --loops 6 --files lib.py,main.py + ./.pyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files lib.py,main.py - name: Testing working-directory: examples/multifile-legacy/obfuscated @@ -200,20 +200,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-mac + name: .pyguard-mac path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv py-shield onefile-legacy/ - chmod +x onefile-legacy/py-shield + mv .pyguard onefile-legacy/ + chmod +x onefile-legacy/.pyguard rm -rf onefile-legacy/obfuscated - name: Obfuscation working-directory: examples/onefile-legacy run: | - ./py-shield obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py + ./.pyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py - name: Testing working-directory: examples/onefile-legacy/obfuscated diff --git a/.github/workflows/win-tests.yml b/.github/workflows/win-tests.yml index 2bbb807..c86deed 100644 --- a/.github/workflows/win-tests.yml +++ b/.github/workflows/win-tests.yml @@ -31,8 +31,8 @@ jobs: - name: Archive Executable uses: actions/upload-artifact@v4 with: - name: py-shield-win - path: scr/py-shield.exe + name: .pyguard-win + path: scr/.pyguard.exe obfuscation-multifile: needs: ["build"] @@ -54,19 +54,19 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-win + name: .pyguard-win path: "examples" - name: Preparation For Tests working-directory: examples run: | - move "py-shield.exe" "multifile" + move ".pyguard.exe" "multifile" Remove-Item -Path "multifile\obfuscated" -Recurse -Force - name: Obfuscation working-directory: examples\multifile run: | - .\py-shield.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py + .\.pyguard.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py - name: Testing working-directory: examples\multifile\obfuscated @@ -103,19 +103,19 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-win + name: .pyguard-win path: "examples" - name: Preparation For Tests working-directory: examples run: | - move "py-shield.exe" "onefile" + move ".pyguard.exe" "onefile" Remove-Item -Path "onefile\obfuscated" -Recurse -Force - name: Obfuscation working-directory: examples\onefile run: | - .\py-shield.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py + .\.pyguard.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py - name: Testing working-directory: examples\onefile\obfuscated @@ -152,19 +152,19 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-win + name: .pyguard-win path: "examples" - name: Preparation For Tests working-directory: examples run: | - move "py-shield.exe" "multifile-legacy" + move ".pyguard.exe" "multifile-legacy" Remove-Item -Path "multifile-legacy\obfuscated" -Recurse -Force - name: Obfuscation working-directory: examples\multifile-legacy run: | - .\py-shield obfuscatelegacy --no-input --mode 3 --loops 6 --files lib.py,main.py + .\.pyguard obfuscatelegacy --no-input --mode 3 --loops 6 --files lib.py,main.py - name: Testing working-directory: examples\multifile-legacy\obfuscated @@ -201,19 +201,19 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: py-shield-win + name: .pyguard-win path: "examples" - name: Preparation For Tests working-directory: examples run: | - move "py-shield.exe" "onefile-legacy" + move ".pyguard.exe" "onefile-legacy" Remove-Item -Path "onefile-legacy\obfuscated" -Recurse -Force - name: Obfuscation working-directory: examples\onefile-legacy run: | - .\py-shield obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py + .\.pyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py - name: Testing working-directory: examples\onefile-legacy\obfuscated diff --git a/README.md b/README.md index 38b631c..69c2110 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@

- + @@ -15,7 +15,7 @@ --- -### 🛡Py-Shield🛡 +### 🛡.PyGuard🛡 Tool/Library for Python used to obfuscate and protect your code in static and runtime from decompilation, reverse debug, etc. Also, can prevent detection by antiviruses. @@ -23,10 +23,10 @@ Tool/Library for Python used to obfuscate and protect your code in static and ru ### 💻Supported Platforms💻 -- Python 3 up to latest -- Windows -- All Linux distributions -- Mac OS +- Python 3 up to latest +- Windows +- All Linux distributions +- Mac OS --- @@ -34,62 +34,62 @@ Tool/Library for Python used to obfuscate and protect your code in static and ru > **Static & Runtime Protection** > -> - Total advanced static and runtime protection from decompilation, reverse debug, etc. +> - Total advanced static and runtime protection from decompilation, reverse debug, etc. > **Hash Variables** > -> - Hash all variables and constants values in fragment of code. -> - Protects variables and constants content. +> - Hash all variables and constants values in fragment of code. +> - Protects variables and constants content. > **Recursive obfuscation** > -> - Recurseve encrypt fragment of code using base64 and zlib n times. -> - Best way to decrease/prevent antiviruses detection. +> - Recurseve encrypt fragment of code using base64 and zlib n times. +> - Best way to decrease/prevent antiviruses detection. > **Best encryption algorithms** > -> - Fernet, AES-GCM, ChaCha20, Salsa20 -> - Symmetric cipher which offer strong confidentiality, and provide authentication and integrity to protect against tampering. +> - Fernet, AES-GCM, ChaCha20, Salsa20 +> - Symmetric cipher which offer strong confidentiality, and provide authentication and integrity to protect against tampering. > **File Integrity Protection** > -> - Protect files against modification. -> - Advanced file hash/content integrity check and comparison. +> - Protect files against modification. +> - Advanced file hash/content integrity check and comparison. --- ### 🏁Quick start🏁 1. Clone repo - ``` - git clone https://github.com/ByteCorum/Py-Shield.git - ``` + ``` + git clone https://github.com/ByteCorum/.PyGuard.git + ``` 2. Install requirements - ``` - pip install -r requirements.txt - ``` + ``` + pip install -r requirements.txt + ``` 3. Usage info - ``` - py-shield --help - ``` + ``` + .pyguard --help + ``` 4. Example - ``` - py-shield obfuscate --hashdata --aes --chacha --follow-imports main.py - ``` + ``` + .pyguard obfuscate --hashdata --aes --chacha --follow-imports main.py + ``` 5. Output - ``` - #Obfuscated by Py-Shield 3.0.0.0 - from PyShield.script_55958136 import PyShield, _ - _(PyShield(b'x\x9c\x05\xc1\xc7\xa2k@\x00\x00\xd0\x0f\xb2P\xa3,\xdeB\...') - ``` + ``` + #Obfuscated by .PyGuard 3.1.0.0 + from DotPyGuard.script_55958136 import DotPyGuard, _ + _(DotPyGuard(b'x\x9c\x05\xc1\xc7\xa2k@\x00\x00\xd0\x0f\xb2P\xa3,\xdeB\...') + ``` 6. Example legacy - ``` - py-shield obfuscatelegacy --loops 3 --mode 2 --file code.py - ``` + ``` + .pyguard obfuscatelegacy --loops 3 --mode 2 --file code.py + ``` 7. Output legacy - ``` - _=lambda __:__import__('zlib').decompress(__import__('cryptography.fernet').fernet.Fernet(__import__('base64').b64decode(((__import__('zlib').decompress(__))[::-1].split(b'eY3NTTr:S|dD'))[1])).decrypt(((__import__('zlib').decompress(__))[::-1].split(b'eY3NTTr:S|dD'))[0])[::-1]);exec((_x)(b'x\x9c\x15\x97U\xce\x86\...') - ``` + ``` + _=lambda __:__import__('zlib').decompress(__import__('cryptography.fernet').fernet.Fernet(__import__('base64').b64decode(((__import__('zlib').decompress(__))[::-1].split(b'eY3NTTr:S|dD'))[1])).decrypt(((__import__('zlib').decompress(__))[::-1].split(b'eY3NTTr:S|dD'))[0])[::-1]);exec((_x)(b'x\x9c\x15\x97U\xce\x86\...') + ``` --- diff --git a/docs/Todo.md b/docs/Todo.md index 0c8abbc..7bd561e 100644 --- a/docs/Todo.md +++ b/docs/Todo.md @@ -3,7 +3,7 @@ ### Height Priority - [x] Test imports manager on challenging cases -- [ ] Rename the project +- [x] Rename the project ### Medium Priority diff --git a/examples/executor/PyShield.py b/examples/executor/DotPyGuard.py similarity index 99% rename from examples/executor/PyShield.py rename to examples/executor/DotPyGuard.py index 4010521..01d7fbf 100644 --- a/examples/executor/PyShield.py +++ b/examples/executor/DotPyGuard.py @@ -1,4 +1,3 @@ - from hashlib import sha256 from os import path, getcwd from Crypto.Cipher import ChaCha20 @@ -12,7 +11,7 @@ _ = exec -class PyShield: +class DotPyGuard: def __init__(self, code, file): try: self.__codee812520fd9c71232668515d6bd91bd4c386f014e41cc3bdd9262101759019196 = code diff --git a/examples/multifile-legacy/obfuscate.bat b/examples/multifile-legacy/obfuscate.bat index fb13979..c5d6eba 100644 --- a/examples/multifile-legacy/obfuscate.bat +++ b/examples/multifile-legacy/obfuscate.bat @@ -1,2 +1,2 @@ -py-shield obfuscatelegacy --no-input --mode 3 --loops 6 --files main.py,lib.py +.pyguard obfuscatelegacy --no-input --mode 3 --loops 6 --files main.py,lib.py pause null \ No newline at end of file diff --git a/examples/multifile-legacy/obfuscated/lib.py b/examples/multifile-legacy/obfuscated/lib.py index 4ef9303..590cf35 100644 --- a/examples/multifile-legacy/obfuscated/lib.py +++ b/examples/multifile-legacy/obfuscated/lib.py @@ -1 +1 @@ -_=lambda __:__import__('zlib').decompress(__import__('cryptography.fernet').fernet.Fernet(__import__('base64').b64decode(((__import__('zlib').decompress(__))[::-1].split(b'1('OHh4qk>YR'))[1])).decrypt(((__import__('zlib').decompress(__))[::-1].split(b'1('OHh4qk>YR'))[0])[::-1]);exec((_)(b'x\x9c\x15\x97\xb7\xca\xc6J\x0e@\x1fg\xb718\xdb_\xb1\x0b\xce9g7\xc69\xe7\xec\xa7\xbf\xff\x9df`\x8aA \xe9\xe8\xe8\x7f\xa0k?\x12\x98\xa1\x03\x123\xbf@\x83!\xd8\xfdz0\x8f\xf8)\xb7\x07.\xc0\xa2)\x9e\x07\xd8uc\xd1\xe1\xe2@\xdfe\xaf\xd0\xe3\xce\x824\xc8\xb7\xa3\xff\xf7+\xda\x88\xc6\x7f\xfe\x0b)~\xdf\x9c\x15Zm*\xf4U4i\xe0\xee\xcb\xf9(b\xd5\x9c\x9d\xa3dq\xaa\\iF\xdf\x05\x89w\xbe\x9e\xa7\xca\x7fY\x1f.\xacb\xfa\xab\xdf\xa3\xe8\xaf\xda\xbf*\xa9\xcc\x0e7\x99\x00x}i\xe2\xd4\xdc0\x91\x03\xbd\x01\xd2\xa5\xdc\xeaY\x12\xaf\xa9\xee\xa7%2\xc3_~\x0e\x93\xa6A\x9c\xc0\xd7\xdd\xeb\xda\x10\xc0\x1b\xef=\xfe\xd8\xa0+\xd8\x96\xc4\x0c9\x85\xfa\xb3\xd1\xad\xee\x9e\xf9\x96\xaf\x83\x08\xca\x8d\x01\xea\x06%\xaaN\xaa;I\n[g\x14\xa7\xfb\x9a\xe6P\x83\xaa\xbd\x19.\xf5\xe1-\x8a\x86\x82\x86c.\x1db\xaf,a\xa1\xcc\x90\x96){\xe3!ts\x9a!\xc9\xa8\xe0\xf2D\x99\x83\xed\xf3v#\x8e\xea\x1f\xfe\x15y%\xf3\xe9C;\xbd\x93\x9d.~\xef\xd0\xaa\x90\x16\x11\xdd\xcf\xc2D&\xce3b\x83W!U\x1b\x87\x03\xac\xd6\xb5\t\xd2C*{fN\x9a\xfd\xf8\x80\xceW\x92{\xc6\x91\xc9S\xa6\xda\x07\xeeU\x05\xa1\xd40%lWf\xa0\xa8\xbaJ\x95\'I\xeb\x9a\xa9\xff\xf8\x1d\x97a\xb9\xec4\x91\x15-\xe9y\x83\x1b^\x18\x0fE7\xb0\xfd\xbc\xa0E\xd5\xa9\xa8=3\xd1j\xc0\x89z\x04\xa4\x83\x161\xb6-J\x98\xd3)\x89\x015\xd2\xe6(d\x8a%[OVI]\x16n\x8c\x0c\xc1\xb2\xf3\xdb\x1f\xa2o\xcf(\xc0\xfb^@\xac\xbd\xb0\xc1\xa61\x9d7\xd1wi\x826h\xbeV\xa3t$*D"\x15\x91\x96\xa9\x0c\xde\x9c\x16\x96\xe2)\xb1\xbe0>\x8a\x1fa\xec{n\xfb\xf3\xa0:\xa3N\xed\xf3L\x8d\xcd\xa9+\xb4tF\xb43q.G\x8d\x1cS\xde\xbb\xba\xc3\xf6l\xbb\x13\xcf\x10\xf5\xb3\'\xd6\xd0\x0c\xde\xbb3\x8b\x84\xc0\xe0\xd7\x92\x07f;)\xd1[a\xfb&Y0}d\x0e\xc5\xa1\xa9A<>\xb1\xe1\xddQ\x07"\xe7\x98\xbb\xee\xa7\xb4\x0e~\xbd[\x0fw\x1d\xfd\x88\x87\n\xacW\xd6\x90\x13<;f\x98y^,?5\xc4}\x9f\r~\xbf\x1d\xa6\x89Y%\xe8TM\x82\xaeE\xd4\x12\x9a\xffj\x18\x15\xc5[k\'v\xda\xab\xc2\xe9\xef\xf2\x98G/\x8f\x01\xf2\x93\x02\xf9\xc5Hn\xa5C\xafS\xf5PC\xa2\xe4t.\x8ck\xc1\xce\x12\xa1\x86\xad\x13G\x80\x16\x95\xf3\xc3\xc8q\xa9\x11\xa7\x903.I\xbd8\xa8\x81&\\\xa4\xf0\x88fh@\x16&Z\xdeL\xa2\xdbz\xf0\x91tS\x1eX\x16\x92.U_\x0f\xb6\xa1\xef>\xb3\xf4\x9d\x1a]\x8d\xb4\xaa\xcc\xdeoW\x9d\x0fQ\xf9\x83Zd\xa61Q\xad&\x14-b\x0b\xf2\xa6\x13Yp\x16L\xc1v\xa7\xb7P\xf4&\xad\x1d\xce\r\xf4W\xf0\xf0\xbf\xcd\xa1\xa6\xa5\xdbP\xba\xab\xb4<\xd7\x08\xadw\x98\xda\xaf\'i|\xce\xbe5\x1b~\x9f|cw\x1b\xc0}\x8e\xae\x8dt\xa3\xcc\x12\xd8\x04\x04U$ hjI\xe0W\xe8\xeb\xdd\x06\xb7\xa6e\xa3Ne\xf0T3\xa2\xb464\xdf:\xfa\xc4\xc3z<\xee\xe8\xdf\x0f\xf0\x8e\xd8\xfal\x93\xc2\xda\xa5\x8f\x9am\xf2u]\xf8\xbe\x1d\x87m/+y\x117\xac\xd1&\x1e\x93HY\xcdC\x07\x85@\xc3u\xc4\xf3\x1e\xa4\xd6\xd4\xf0\xc6:zJ9_\xea\x12\xack=\xf1j%\x04\x8e\xad"\xec\x8f\x9c\xfc\x00*\xaf{B%\x94$$u\xc1\xb8\xa95\x88\x9d\x10\xba\xbfV\xe51ZO\\\x11SDI\xe2\x95\x02\xcb?\x07)0\x0c\xbe\x99\x84\xf9\xf5\xb1\xd1\xc6\xf6(\xf0\xb8\x93\x8f4\xa6\xadA\xed\x90\xc3\xed\x85)\x891\x11N\xdc\x01PC3\xc0\r\xb1\xb9\x81jk\x81\x8b\xf7\x10\xd0\xcfE\x17\xe7\xf0\x1f\xa6\xe3>\x1aJ\x17cJ\x08\x03\xb7\x14\x95\xa6\x81\x90\xcdD\xfc\xad\xfd\x1fr\xeeH\xdc\x925\x1ex``{B\x07\xfbi+\x04u\x93M*\xb6\x90\x80\xed\xf9\xeb\x15\xf2\xdf\xacA\xa3\xc6~\xc1\xbb\xb20\xd7.\xb5\xa1ZP)\x17\x10\x00k\xbc\x93\xe9\x9bn;\xd7\xcb\xfd$sg\xb8\xbf\x14/)\xb3\xbf\x8bF \x84\xe0\xac\xfcX\x92\xe0|@O\x0c\x0cEa\x12\xeaEyr\xd0;J\x9f\xd6\xce\xafB\x92\x82x\xf32J\x9fO\x8e\xd5Sm\xae\xba\xd8\xd6\xfd<\xbbu\xbf\xa1W\xa8[\x127\xfa\xfc\x1aRa\x05\xb4\xae\xc2\xcbi\\\xd1y\xef\xe5B\x80H\r\x1a\xb0\xa7\\\x06#D\xc8d8G75\x02\x19\x17\x89\xa8\xc1k\x00/\xfc*\x0c+*\xe6\xb3\xbcl\x93\xac\x92&\x87\xbbC\x0bY\x9a\xd9\xf0\x89\x0c\xe0Xb\xde\x12\xe8\xee\xb86C\x1c\x9a\r\xb2\xce\xed+w-K\xc7|PcJ\x7f\xc1r\\\xb5\x8ds\xbdLiq\xe5\xc7\x1e\xbf\x0b\x1b\xd0o\x0e\xd8\xae\xe9A\xa8\x00r[\x1b\xc2\xf3\xc4\x1f\xea/\xf3}\xa7\xd6\x08D\xacO\xdf*\xa4;\xf3\x04\x9a\xa8\xfc\xdd\xbfk\xf7mSq\xcad\xfd\xc3x7\xad:3N\xa2\xa3\x92?uJ\x86\x9d5\x9e\xadm`\xd2\x7f\xb9\xd9!\x19\x10\x1e\xbee\x9a\xb1Q<\xb9\x02F\xda{\x85\xa0.\x80\xc6\xccfT&\xf4\xd2\xdfT\xd8V\x89\xb1.`\x94\xef@\x0c\x9c?SF\x8c\xcd\x15$\x0cy\xe2\xda\xb8\xd7\xd0U\xce\xba\xbf\x1f\x884y^\'i\x06\xb8\x1a\xb7[+\xcfE)\xe4\x18\x8c\x14\xa1B\xd5\x9dh\\\x8f\x84\xa9\x81g\xf3\x01r\xd5\xcc#\xe2\x1cQ\xaf\xed\xfc:00`\xab\x13\r\xe43d\x1d|_\xa8\\C\xec\xd2xW\x19X\x16\x95\xfa\x002\xaaK\x08\xb0\xb1\xc1\x1eG\xba\x85O\xe5\x93\x1b\xaa$\x1b\xa2\xc9\x193\xa3ww\x11\xd6\x7f\xd1\x92\xbfT\x8d\x1e/e\xee\x02\x90{\xc2\xf5\xd8jL\xe9\xd3Z\xc23bu3Y\xc8\xec,\x14\xb0t\x93$\xd4\x04\x9f\x1b\rp]K\xc7FZ\xf3\xa5\x81\xb8\xc6[\xd2R\xaah\x1c\xdc\xec\xf6\xe6~?`\xea\xa6\x9aI\xe2\xdeX@\x9c\x16\xfcq\xe7PL#\xf2\xe1\x92\x96.w\x8e\x98\xca\xcd\x15=$B\xfd \xdc\xd7\xcf\x00>\xaeE\x13\xfd\x90\xf0\x0f\xd2\x06\x1e\xd7\xb0\x88e\xecM\xad\xa0\xec\xca#m\xc2\xe48&\xbb\x18\\\xcf\xde\xa1\x83\xbf\x84\xb3\x96\xb9t\x19\xd2\xb7Vn\x89\xa8\x1b\xb1\xe37\x8d\x8f+-u\xfc\t1\xf47\xd1\xbe\x04F\x11\x9d\xfc\xb5\xe1`\x9d\x81\x97t\x87d\xabWe\x91\x7f\x81\x1c_4 \xc0x\x15\xb9{\xce\xf6\xa4u\xa4\xbd\x110\x89z\x99\x81\xc3\x8ca\xc4+E\xf3\xac\xb5\xcd\xe3|_\xd7[\xc4Q\xfb\x821\x8a\x06%\xb8s9\xdf\xaa,b\x92w)\xeby;\x99%\x1a\xa6_\xa9e\xed9\r\xb8\x82L{\xddT\xac\x07\x04\x8b\x17\xa9t*\x10\xbf\x99\xe8,^\x01I\x0e\xb2Rn\x13x~\xae\xa7R!~\x8b)\x8c\xbb\x8b\xd8@\xb3~SEI.\x8e#\xb59\xedd\x022E\xf5.x\xda\xcd\xf9\xf8\x94yB\xd7\xb8\x06\xf6\x01KXT\xc1\xcc\x90\xdb\xb2\xbb\xed%4;\xdd-}In\xeff\x7fI\x8f@.\xcf\'Z\x8b\xd2b\xe1\x06\xa6\xf9B\xdd\xf7&\x1cP\xd6\xe9\xf4\xecARc\xe8\xd7H\xa4\x07\xf4\x02s\x0c\xfc\x06J\rxj\x0c\xdbD\x0b;=\n\xfa\x9438\xb9\xd9\x18[Q\xf8C\xda\xed\xdd\xa6e\xec\rYA\\\xb1\xfbm@a\xf51l\xfc(\x97\xbf\xba\t\xe5\x8c.M\xdd\xb1\x88QP\xcd\xf4^]\xd8\xd4\x8a\xbf\x82T \x1a3\xa8pg\xf9\x97\xdd\xea\x0c\xd0\x91\xc8\x80:\xe3\x9ek\xde\x7f\xca\x02\xc0\x16\xc2\x94oj`\xb1S\xa4\x87\x05\xf4\x99k(\x94\xa3\xdc\x16\xa7\xaf\x84\xbcb\x03\x94N\x82\x1e\x84\xd6\x94\x89sZHFD\xc5]_\x7f\x84\x03G\x9aZCH\x95\x0c\x9c\xc3\xea\x8f\xd9\x07[\xfaLYZ\xb6\x12=\x13\x14\x8a\x04\xa4\x0e\xbcc\xc9\x82N\xbb9}G\xe0#\xce\xe3\xed\x0c\xf6!\x96RvUf\x93/[\x1f;\x08t5\xd7;\xf1\x00\xae\xa7=\xaa\x8c\x9c\xfb\xfc\xd8\xc1{\x08\x02z\'\xfb\xe7\xcf)\xec%5k*~\x80\x12:\xfam\xbcS\xf0\xd1\xa4\xd1\xac7\xc5^\x93\x10\xfa\xfd\xbcYP\xbb\xc5\x0b ~t\xdb\x8bG\x07\xec\xd4X\xa1\xc1\xd1\xd4\x82$7w\x82\xd73\xa9\x9f\x03\x10\x93\r\xdbX\x00\x11\'\xb1\xbb\x04\xb1\xe4\xc9:]\xcd\xb5\xb6k\x05\xbaJ|n\xe3/#\x88\xed\x94fd.\xc0\xa9\xecf\x0f\xafD\xd2\xd1pJ^\xd9C\xfe\x99Qz\xc7\xb1\x1c\'k\x1c\x1c\xb3\x1b|N=\xb94\xe2\xe9\xc1\x17qJx*\xeeL\xe7\xf6\xdb)\x1d\xbdz\x96a\x9a\x86\x8816\xbd\xb4\xaft<\xc0\xf3\xd5\xdec6+\xab\xcf2!\xf6U\x85t\x1a\x17*\xb4\xeeI L\x18\xc2\xab\x04\x05}\xd1\x07\xab{\xe3@\xf1\xb8O\xd9\x80\x1e\x0b_\x89\xfbL\xac\x02&\xff\xa3\xb7\xd7\x8bVe\x01\xde\xac\xc2\'?\x03\xf1h.\n\xb5\xda\x94\xa5\x05\x00\x08\x86/P\xe6M\xa9JQ\x87>\x88R\xf0\x0bpA\x17.\xcf\xd6\xdbDI\x98d\x90\xce\xb2\xb4\'pw\xf5\xa3#s2\xd4?a\xccQ\xc3S`\xd3C\x05\xffo\x10(\x8e},\x82ME\x0c$\x16\xe4\x97\xe7\x1d\xe5\xe4\x05B%l\x8f9|\xbcv\xd1;@!\xa7x$_0\xe3\xf6\xd7\xcf-"TD8\x91\xaezKy\x8a\xd5\\\xb70\xf1o\xb5\xbaO\x00\xb9V\xb9\xf8\r\x90,\x11\xaf\x00\xb0\xacaL\xa2\xb4=YI\x9f\xc5\xc9f+U\xfc\xe7\xbd\xc03\xbb@{{>\x17\x1a:\x1c\xea/}\xfe\x95>\xe8w\x88\x8fEr\xdfqO!\x99\x94\xae\x86\'?[\x95f\xd0\x81\x81|\xf2|0\x13s\xc1\xd9\x19\x8c\x02\xfd\xfd]\x1e\xe5\xfe\\q\x83\xa9\x8a\xfcq\xf50u\x9bM,B\'t\x8aq\x1c\x81y+\x03\x07\xa1\x825\x99S\xdf_Hm\x8bi\xcc\xdb\x1b\xbbbxyE\xab\xfa\x80\xa8\x12\rI\xb5\x1d\x01\xbe\xe9\xfe{v\xce\x1b%\xad\xe3_:\xbe\xbe\x05"q\xc4\xd1y\xabRc\xaf\xf1\xe7\xf4\r\xd56\xd35\xa5lX\xf1!!\xc9\x93\x81\x1fYC<1\xa11\x0b\xa1\xbc\xb2\x15\xbe\xa7;\xc8\x85\x94\xa5\xafb\x9fR\xd5\x86I\x8a\xdb\xb6\x17or-\xc6\xb58\x9b\xa0fOy\xea\x97\xad\xdc\xb8b\xbdQ\xc9\xd6{\xfa\xb3\x8c\r\x0e\x0f87\x1ep\xd0\xc7\x9cg\x83\xca\xc7Y\xe3\xf3\xe7\x06 \xe9\xcftu%\x8e\x82\xc1\x9e\x82\x89\xe4e\xcf\x04D\xbeN%L\x94\x9b\x84L\x8e\xf0\x1a\xa4(l-\x96\xa9\x9b\xa1n\xed^\xc9\x98`\xfc\xc5\xc6f\xe2\xe9\x7f8U\xfb\xcb\x14\xcf\xa8\x85\x9a\xf0\xf4w\x94\'\x0f\x82[qHhk\xe0\xb5P\xbc*&\xc8\x1eQ\x95\xbe\xa5vtF\xf2_\xef^\xb5\xb22V\x14\xb7\xdd\xc8\xec"\x02:\xf4\xd0\xf0\xaf\x07\xd9=2<\xc8\x9e\xf7\xfd\xd3\x9d\xb6C\xdas"Fa\x8e\xad\xcfU<\x8c\x0e\xc2\xbbO\x13\xe7M\x99i\',\xd1\xc6d\x1b\xd0k^R\xe6\xc9|\x82\x84\xb5\x85\xff@\xe2F\xbf\xf4\x1a\xf6\x1e\xb8\x83\xcd\xda\xbd\xfb\x0fo\xdf02\xac\xdasY\xbd\xb3\xe0\xcf\x99:\xdf\xbd\xa3\xcaN\xa7c\xfa\xc5{\x07\xe2\xb9H\x18\xb3#]qC\xd2\xab\xc1`\xdb\xc9\xc4\x15\xecT\xabl\x15\x7f\xcfo+\x08\xfc\xf9\x93\x92\x90}\xfc%\x90\x00\xc8\x9b?\xd7\x00\xff\x04\xcf\xc3\x89\xcd\xa8\xcdc\x10\x0eS\xba\x8e\x98\xe7\x7f\xef\x8bOp\xe2m\xf4 \x85\xd9\x02\x96\xd8E+\x0e~\x10?\x87[\x7f\x87\xb3\x87\x17\x18/\xa0c\xc2\xb6\x10t\x03\xe7\xb2?~\xb3\xe9\xf8\x95Yn\x97\xe5\x96].\x03\xdf\xc7\xeb\x86(\xf0\x06\xb3\x0c\xee\x12\xb2\xc2 (\xbd\xe5\xed\x13\xb1\xb9\x15>\xc5a\x8b\x82\x04\xd4\x8a\'+\x19%\xd9\xcaB\xebm\x86r\x01\xd4jS\x9bu\xe9vXd1\xaa\xc44a\xfc\xcd\xf6\xd9\xb8j3\xd4\x92\x81\x99H\x0f\xec\xd9$-$JT\x1bJlL\xb4x\xf2C\x07(\xc4\xa5r:\x95\xb8\xa8t\xe9\xfa\xe0\'\xaf\xe0\xf9<\xa8\x8aJ\xfb\xd7\n\xbc\tz\xec-\xad\x86\x13\xbb\x18\xd5\xae\xc8\'W\x98\xeb\xd3\xd8\xe684\x12N\xa4\xf4o1^\xc4\xaf\xcb\xa1Z\xfd3\xd9\x85CU\xef\xfct\xaby\xf0.|\xa3\xa8\xbd\xeb]^\xb0R\xeda\xc1\xf3\x00\x13\x04\xca&\xe1\xeeqm\xfe u\x04\xaaQ\xf8\x99r\xad\xe3\xc7\xf6\xf7\xdf\x9c\x06\xa2[\xa5\x8fjx\x02\xfa\xd8\xccQ\x06\xcfSK=\x0e\xefF\xff\xf2\xc2\x01\xea\x9eh+\xbdn\xd3\xc3-{\x04l\xa6\x8b\xb2#\r\xa9\x95\xf2\x05\x0fQ\xcf5\xc4\x061\x04!\xda>\x83\xa1\x0b\xff\xf1\xfe\xba\xf7\x90\xaa^%\xd4\xcf\x1d\xa4\xd7\xb2%?\x89fsm\x1dM\xb4w;\xec\x7f\x9c\xfa\\\xc7\x05`T\xf3P\x1e{\xee9M\x8f\xf07a\xbb\x00GDY\x89 D\x1c\xad ad\xbc\xa3\x8fG\xb4A\x0e"\x91\xce`\xd0\x0f\xf7\xbb\x04\xa7@\x02\xd7\xe9\xe8\x0fmw\x08\xf1\xfc\x1d%\x1e\xd2\xf5=\x83/\rk\xd4\xad\x91\x19\xec\x0f\xbf\xd5\xad\x96\x17[\x92E\xb7C\xa0\xba0,`r\xe2\xdc\xf1\x90\xd8\xbd_\x08}\xdbY4\xf8\x1b\x8c\xff\xba\xb4\xc7\xc6\x0em\xd2k\xb7\x07h;\x0f\x16\xb7\xfa_\xc2\xec\xa61\xce\x16\x89\xe5e6\xdd:?\x94t\x15\xd6\x98\xdf\x15\x14\x11\x93\x18\t:\xd5\xd0=pW\x10\t\xe0o\\f\x0e\xea\x9a\x04\xbe/\xec\xfb\xdf\xdc\xd8\xdb\xf1V\x8d>\xed\x17\x03U^5\x92\xd7\xbf\xdd\x1aq\xe7\xddN\x82y36{\xfe5\xe8\x1f\xe6\xb6%/\x1b\x95\xae\x92\xdc\xf83\xe0\x17%~T\x9c\n\xbe\x0c\xb1;8\x13\x14\xf6\x01\xe5\xea\xca\x84smt#\xcc\n\n\x1b]\xaf\xca\x16u\x0b\xe0`\xa8\xb1\xbd\xe5|&\xeb\xf1\x8bv\xe2\xd2\xef\x10K\x96\x9ex\xa5\xa2n\xf5kX$s\xdf\xf3\xa8\xdc\xec\x1c\x0b\xe6\xe6\n\xa3$\xe5U\xb77\xe5\xa6\xd2\x9bh\xc0\x0b\xdb\xd3d\xab\xa2\x9bCF`\xdf=^\xd2\xba\xfbV\x83_}\x16nj\x95\x9d.\x947T\x0f\xbd$\x8e\x0b\x80\x82r\x80/<\x04Q*\x0bu\xdc\x03\xc7\xdc\x13_r\xda\xd7W\x1f\xbf9Rv\xd8\xe9\x0ej0\xd6"z<\x8e\xc2Aoo\x94E?\xb7\xd2+/\x8e\xf3:\x8d\xc5>tX\x11\x9fL\x04\xe4\x8c\x80\xae\x19)Rvz\x91\xe57\x1aB\xf7\\\x02\xfc\x83\x98\x9b\xbeZ\xccn*8\xa50\x8a\xb8\xee\x85\xb4L{\tv\xfb\x80\x05\xfa\x14a\xb3\xce\xd6E\xa4\xaa@\r\x9fbU\x84\x0fH\xd6V\x04\xad\xe2N^\x9f\x95\xf8\xf8\x14\xb5"\xf1\xf8\t\x8f\x7f\x95\tO;#\xebk\xber\xd7\xfb\xdc\'m\xc2\xeb|\xfa\xc6l\xa3/\x16\x1a!\xba\x82\xde\xd3\t\xdc\r\xd3M!\n_\xe3\x86ag\x8f\x9d+\xcd\x99\n\x99\xdb\xa9\x8e\x83k]\x0f\xa2\xdf\xc4\xdf.#\x9a\xa1a\x97\x17\x94\x01\xd7}#\xe3\x9cS\x14\xae\x9e\xa3_\x9a6*\xc0x\xbc\xc5\x0f\xe5\x98\r\xf96\x9dr AB\xc8\xad\xc1(pNjYn\xa0NN\xfd7\xfc\x16\xe8}\xa6\xe3\xc16B\xf4\xb7\x89\x99\xe7z}\x17yc\xb1\xc4\r\xb3r\xb7\xbe\nt\xb5~+\xc7\xeeg\x88st\xd3\xfe\xa9\xcd\x1d\x8e\xfa$\t\x83,\xfe\xdb\x89FA\x84\x033\xe10\xc5\xdb\xfe~0\x12\xf4\x02\xcaWL\xa0[o\x1e\xbe.H\x04\x8f\x10\x0e\xc4I8T\xcbl\xd8 *b\xde\xd8WQ\xc7\xf9g\xbc\xf3\x918\xb3\x9bD\x8bZ\x97\xc4nf\xb3\xde\x9eS\xad\xbb>\x91\x88p\xc0q\x8c\xa4U\xbd3\xd8\xcd\x06\x1d\n\x10i\x13J\x9f\xd7R\xc4\x89\xeb\x1d\x1d\x9b*\x93t\xe03a2\xfa7m\xfa\x8e\xd2_0/;8\xd3\x91\x9a\x07\xae\xeaQ\x01\x8cN@f\x88\x14\xaf\xd1\x85a\xe1\x83\xd2\xc9\xbd\xb2\x1b\xa3Q\xd2\x9f\x9c<-\x85\xb4\xf0\x0fD\xd9\x1b\x93s\xd5\xda\\\x7f\\\xf6\xc3!l\xea\xd2.\x80\x8e\xd9\x9c\x0e\xa8\x88V\xeb\xddT\xee\x0b}\x92YfM!0\xa3\x07H\xad\xd7t\xd3IK\x15;b/h\xd1\xef\x9c5?\x02L\x1b`7\xa5\xfc\x02\xf7k\x92\xa8\xd3!md\xady%I\xfdd\x8dF\xcf&=\xd0\x96\x9d%\xdc?Vp\xfdS\xf4\x0c\x91\x8aU\xaf\x94a\x8c{\x82\xce<,\x82\xc5\xdc`/.\xb8]g\x93H\xcd\xd0\xc3\x87`G\x91\x16\xb4\xa7=\x0e\xec\xbf\xf3u\x7f\xb7\nQ\xeb\xac\xa6\x89\xca_\xa84\x08\xd1\x87]L\x08Z\x99\x03Y$\xc4\x12\x9d(@\x05\ra%\x9b\xe1\x9e\xdc\xc9;\xa7\xb8\x8ab\x03s#\x94b\x95\xe9\xf9\x83L\xe6\x03+d\x8e0U\xba\xfc\xcc*\xd6\xd4|\x9d\xae&~\xcd\xe7\xfa\xbc\xa4q\x04\xd1\xc5R\xba\x03D\x96O\xe3\x9bx\xe7\x8d\x02\xdb=\x04`\xb6z\xed\x0f\xb1g\xaa\x84\x13p\x80\xaa\xea\t\xf8\xd8\xde&\xf7\x9c\xe8,\xf1P\xb2\xc627ti\x90\xdd7\trN_Y\x05\x87!\x9dT\xa5v1~\x97\x94\x1d8X\r\x9c\xe3\x01\x97\x06\x04\xa1A\x19YQ^\xb6x\x8e3~\xe0\x1e\x07j\x93.\x0c\xe9f>\x88\xdetX\xfe\xb3 \x12\xd8W\x8f\xee{"\x93\xf6bsG\x0eTg?\x9a\x18\xb1\xce\x86\x9eg[\x90b{\xdf\xf33B`T5\xce\xff\x10\xd0\xf7\xb6\x8d@\xa8\r.\n\xca\xd9\x9d\xd7\x85r\xdd\xc6H\xb8\xe1\x18\x11\x96\xbc\xb7\xfd\xc9\x9c\x19\xdeP7\xf76\xed:\xf2\x19s\xbe\x90\xfe\xb0\xcf\xd5\xfc\x1a\xe4]\xe4\xf3\xd9\x93\xf6\xbd\xec\xa5E\x95t\x99F\xd2b\x03\n\x9d8\x1dhZ\x1e\x08:\xf4\xee\x14\x008\x8b)\x05\xa2>\x9c\xe5\xa7A\xdcS\xcc\xac\xa0\xe6\xe5Lem\x12\x048\xe44\xa0\x91F\xe3-\xc6mG\x9beg\x08R\xd3 \x99\xa8\xc4\xda\x8b\xd5qW\x99\xcd\xf5v\x18\x9b\x12t\nF\x7f?\xa8\x9e\xd6\xdba\xf6\xcc\xf1\x8a\xb7\xdd-\x19\x81\x1c\'m0P\x88\xa3\x7fy\xe3iqGe\xd7\x96+L\xab\x03Yw\x84K5<\x9e\xaa`\xb0\xe34S\x8a6K\xff\x8c\x8a\xa5\xf9\xeco\xa5\xfe\x16\x96~\x85GH_+\r\xd5\xaf\x1e\xc9\xde\xfb\xa0%\x13\xaf?OEI\xf0\x8b\xa6\xe2wc[\xdc\xab\x86\xf7\xe1+c\xc83\x8d\xf8\x8c$\xda\x89\xe0\x9a6T\x87%`fg\xd5z7\xa2\xb0r\x11TMO8;[\x88\xd4Z\xe9\x13\xcd\xad\xd5\x96\x7f]\xfa\x8d\xcc\x16uq \xcd\xc3.tA\x9a\x92\x93\x87\x8e\xb7\x16\x16\xcc@\xa3?\x07\xf4\x95:y\xc6aF3L+\xc6\xc5\x1bZ\xf6\xa1\'\xe9\xec\xd7$\xe9(\xedhW\xda\xfa\xcb\x80\\`\xb9\xe6O}\xf0x\xeb\x12\xa6\xa5\xa5\xba\x1bH\'\xd0\x03\xc8\x15\xb3\x0c\xdb\xf6l3\xf41p\xc7\tN\x1d\x97T\x01\x9f\xa3\x85\x9c\xc3em\xde&\x17\x00\x0e9\xdc\x1b\x97\x92\xf2\xac_\x9a\x87\xab\x7fW\x8b\x86\xc9f\xe8\x96\xf2a\x03c#\xc1\xb7u\xacR\xf4\xed^\x9c6\xb4j\x05\xeb4n\x15SCR\x8c\xa7\r\x0bK\x9f\x98\xc8C1\\\x9b\xe2=\xbaa\x9fz>\xefd=B\xbc*!\x95\xe0\x17d\xaaJ\x96\x90\xe6vk\x155\xa3\xd4\xc6S\x97\xaeD\x05\x0f\xdb\x9c\x15\xa9-\xb5HZ\xa8\xef\x06\xbb\x1e@\x15\x02\xf74a\x85\x81V\xc4\x84\xb2A\x88\xf5\x84C\x08#0\xde\xde$\x06\xca\x0e\x98LO\xc8\xccW\x0e\xf7\xae\xac\xb6#\xd9|_HF\x85\xf1G*t\xcf#\x90a1\xec\xf2\xdd*.\xf9\x1d\n+`D\x9a7\xf1\'\x7f\'\xb2J\x1f1\xce.?\x90i|jjo\x1bMr\xb03\xad\xfb[\xbc\xbd\xa2w|\x90YH\xf7\x8f\x92\xd2\xcf\x95\xd9\x87\xd7\xbdi\x98\xc7\x99\xd5\x8b\xbc\xd8\xabn\x96%\xab\xc1\x98\xf9w\x99,\xedJ-\xc6\xbci\xff\xd0H\xf4\x87R\xa6\xb1J\x88i}\x8f\x00\xbf\xe5Q\x9fh\x91)4\xcfkB\x94\xe8\xd9\xaa_\xac\xf1\x168\xf0\xfe\x12=\xed\x93\xe3V\xdd\x81I\x10\xa6|cR\xcf\xbf@\xa8\xaf]C\xcc\x90\xc7\x1d\xd9%\\\xb6\x9b\xc6\x99\xec:\xeeAm \x93\xcf\xda)0\x04v\x02\xbc\x82\x03\x1f\xd0\'|\x06:\xa6P\xa2\x11e%\x8d\xee\x91\xf3\x17\xd5\xf9\xc8\x1e\xc4\x98\x8dzCA\x80]i\x04\xa7[\xf8\x91m!\x00)\x8b\x0b=\xa5~\xff\xa1\r|\x8a\xb1\xfa\'IM\xef-\x88\rnt\xc6\x0e1\x1c\x1f\x8f%\xaf\xd5kB\x8c\xfbU\x08r\xc6\xd6\xcb\xdf\xd2\x9b,\xbc-\xe9\xd2/\xce\r\xff\x9e\x06}~\xc25\xa3\xaa5\r\xf3\x14\'\xe7\xed\x17\xba\xf2\xf8\xc1\x93x\xadN*\x0f\xc0N;\xc2v:<\x15\xb4c\x9d\xfc\xcd6J\x1e\x90\x95\xfdK\xc56\xfa\x9b\xceI\xa9\xf0\xf6X\x16\x9d\xc5\xc8\x18\xd3F\xde1PC\x1a\xe6\x15u\x80B\x97K\xc4\x8a;\x1a\xc1\xe3\x88\x05a3w\xbce\xbf>\x90\xe9w\x94\xb9\x1a\x06W\x8c\x1e\xf1\xbf\x8c\xbbR|Vr\x85\x08\xd9\xe8<\x94\xb2\xa8\xbf\x90\xf5k\xca\xbb\xe5\x9f\x10\xeei\xe2\xf4\xba;\x89\x04\x03\xfe\xc1d\xf9\xcd\xa7\xf3\x0b$QZ\xa4\xbb\x7f\x0e\xba\xcd\x90r\xc3\xb3\xe0}\x8eB\xc6\x93\xefU{\xe3\x0c\xdf$\x15\x81,\xc6\xb8\xbe\xf4\xc3\xe3\xf9\xb1~\xe308&\xe4\xfb\xac\x95\x04%\xfcp\x85L\x1e\xedH\xd4\xea\xfd\xd2\x06\xa3\xbc\x08\x8fq\tR6\xe8>\x9a?\xe0\xe2u\xa8\xbb\xdb\x12\x01fh\xeaS#\xea\xa5s\x82\xd2\xbb\xbcq\x9a/\xc1\x89Vd!\xde\xcc\x97i^\x7f\xa8\xa1tX\xb7\x1dRc\xee\xf8\xfe\xec\xe5|K\x0b\x1f\x8e\xf9f\xbd\xbfmx\x1f_\x01\xc3\xdf\x8b\x19\x1di%\xa8S\xed\x80\xd98\x86\x87\xa6\x86\xc5N\x0b\xa7\xcd\x82%\x9e\x92\x0f\xbd^\xad\x95\x06GZ\xbd\xfc\xdeQ\x87\x7f\x1d\rI\x92\x8al\xb6\xe8\x01\xfcL\xc9\xf2\xfb\xc2y\xc6\xf3p+\x1dM\xe7\xedc\x8f\x18=\xd3\x14`\x7f\x06\x08\xce{\x1d\x18n\x98\x96\xcc,\xf9s\x1c\xde\xdd&,!\xfa+7\x15\xf8\x1a\xc6\xa8\xe8E\x9b\x8d=UX\xecg\x05\xc5\xb1\xdd\x89\x05\xbf\xec\xe9eg\xe5a\xf6C\xd7\x17\x88\x00e3\x07ei6\xa6\xa9\x03\x17\xc6\x80\xd1S\xcc\xf7\x06\xd0\x9f\xdf\xaaJ_\xc8\xa4\xcc\x82\xa9\x1bF*\xa3P\xe9\x18\x7f\x0b\x8c]3o\xa6C\x15\xb2\xa5\x92\xa9 H\x90wR\xeaw\xe1\x1ep\xb6?%\xae\xfe\xc9\x00\xa7\r\xa4\xaf\xb5\n\x11\xcc\xeb\xf83\x17\xc0H\x03<<\x1dE-\x82\x8e\xd1\'\r\xbb\x12\x06\x81+1\xed\xac\xba\xcf\x03U\xd9\xc7$Qs\x15\x06C\x01d2%\x19\x8b\x82\xe3u\xa96#1\x00\xcf\xe5\xd7 \xcd\xd9\xe2\x85/\xec{\x80\xaf\x1aK\xa0\xea\x9cI\x05,\x82^\xd9R\xb4\x1f\xd1\x9c\xa1\x13v"\x97\x955\x84\xc3\x11Xm\xb8\x8a\x9d\xf6\xc7/r\xff\x90\xf8\x0c\x96dtq\xd7^)\xa7\x87\x06W\x88p\xb1\xc8E\x18\xc0\x95\xf3R\xd4\xb2y|3\xb6\x95\xf0\xd17\x9d\xc4x\x04\xa53\xf76\xee\xfbg\xa2\x82Z}\xf3uR\xf2\\T\x90\xcc\x16n\x87\xe2\xc8\xebOA\x0f\x98\xab;\n\x91\x8a\x81Bu\x99\x8f\x85\xa9\x80:\xb2{\x80\\\x87\x13\xb5\xd4vY:)\xdd\x80\x04h\x88\xfc\xfb\x05%\xde[\xb7\x04\x98\xc0od\xffP7\x08\xb2 \xa3\xc5\xfa\xe7\xdc\x16\xf3\x91\x9b\xda\xec-\xa3\xb3\x08E\xe3\xc5\x8a2\x8d\xf8\xb2\x7fEB\xb6\xe1\n\x94\xafA\xd6d/v\x88\xbf\x96d\x07\xca\xf4\xd9d6\xa2<\x80?\xb6\x18\xc2q\xfe\xc2\xd4>B\xf9\xb4g\xc0\x90\x13|\xc8\xc9\x9d\xdew\x84e.\x16\xc2rqP8E\xa6\xdd\x7fT\xd9\xe0\x95J\x95\xd3\xe8\xb9\xac\xbd6\xe5\xc3Z\xb1X\xeb12\xab6g\xbf\xbb\xd2\r\x9e]\x1d\xadA\x07\x15IQ\xe2_\xb5\x1b\xf1\xfa\xb0\x80\x0f\x9a\xb5o\xacx\xfa)n\xa7\xac\x9a!\xc4\xe3\xde\xce\xd9\xfa\xcc*\xc8\xa1x\xe9\xde\xd7\xa4U\x8e.\xaf\xa8\x17y o\xec\x87\xc0\x83\x16:H\xde\'\x10I\xd4Q\xc3Es\xda\xb9W`\xf7].\x1fh\xc2\x97\xdf\xc9-\x95\xfb\xbfB+M\xdf\x89\xb3A\x9b\xeao\x0f\x1c\x84\n\xbb\x83\x03\t%r\x14/N\xe40}g\xe3\xea\xf7\x8f\x8eR\x02\x1b\x9a\xfcYN\xb9\xc2\xdb\x88V\xdbg\xc2Ux\xff\x06\x17\xff\':-\xb1\x86\x91\xba\xc1\x8e@\xb0`\x1f\xb5\xf5\x89\x87\x89\x1bo\xd6\x87.\xd9\xef\xe5?\xd4\x83\x8e\xf5/kx\xde\\#\xb2\x82\x89\xd7\xa3\xa6_U@\xea\x9fc\x90\xd6\xfa\x902\xc3\x97#\xf2\x12g\xb5\x1a\x1fX\xdd5\xa8\xe9s+6\xdc\xe4\xa5Z\x91Y\xdb\xad\xc4\x04\xfb\xfb\x1b\xf6\x84\xa8\xd0\x0e@v\xee_p\xde\x84Y\xcf\x95{\xefD5)\xbb\xc5\x97\x054\x11\x91\xac\xabx\xfa\xd7&-\xce3\xdb\xd1A\xb4\x05\xca\xc0v\xb1l\xd4V4vM,&\xcah\x01\xb1\xea\x86\x1c\x86\x05ha\x9d\x8c^\x9e\xd8\xf4\x17c\xdb\xbfn\x06\xe4\xd1\xcf$\xca\xfa }\xf5\xa2\xb8\xc4\x7fk\xa2\xfe\xad\x0e\xf9\n*Mi,\xa5\xecE\x02uZ\xa9\x1b*_\x1c\xbd\xf0\xd4u\x91\x86e\xd9=\xc0j$\xd2\x9e\x10\xb74\x04\x01\x9cp\xc6ja\xd3\x12v\xf8Z\x852\xfc\xb2\x04\xb7\xb0S\xa1\x01\x1c\xe5\xfc\x01[\x02/\xdd\x02\xfe\x80\x837<\x9d\x0f\xd9\xa0z`2\xd7\x83\xbb)a\xbe?\xaaM\x8d~HL\xb6!\xd1JO\x8e\xecv\x1f\x81\xe7\xc9\xce\xf4k?\xb2\x91\x156@{2\x94\xba\xfd\x9a\xfb\xc8M7Tdc\xb6\xfc\n\xf8S\xc4\xdf*X\xacY\x18\xc2>$\x02j4J\xf5\xfd=\xebw$4\x05\x10\xb2\xcd\xa5N\x05}7]\r\x05\xed\xc0K6\x10\x13h\x91\x9f9\xa0\x91\xca\x8f\xa9R\xd4!\xe8\xaf1?\x8e\xa8\xb5\x14.%M\xcf\xfbi\x93\x1b\x81\xd8\x89\xfdp\xc7\xf5j\xf0\x98q\x9b\xaaz\xb4i\rN\x14\t\xaa.a\x83\xae\xdet\x89\x16@j\xc7%\xa6@A\xab\x9a$\xdf(6G\xd9Mn\x12\xee1\xc6\xbf\xday\x0b\xa8\xcd\x8b\xdd\x08\xf0\xdf\x92)\xf8\x9b\xd3\xc36\xe4a[r)\xd0\xa2 \x84\'\xa8\xdah\xf5n\xd8\xa7\x8a\xbe\x12\xe6v\xa6E3\xfb\xb8\xff\xa1\x03\x03\x10\x881]\xa1\xc0\x01\xaf\xe7\x9b7\xfc\xc8\x8eQ{\x84\xce\x99\xb2l*=9\xc3k\xd4\xf63D\';\x99H\x7f\xb9!?\x01\x1e\xe5[]U P\xda\xa2\xae\x17\x9b\xdet/h\xc4\xd4\x805_\xce\xdc;\x1d\xb3\xe8\xbeK\x04\xb1\x15.\xd8\x14\xbc\x81;\x81|:\xb2\x11o\xc2\xc2\xb2\xe6\xd6\x88\x90\x9a\x1a\xb0\x8b\xf6\x83\xad\x10}\xcb\xfcb\xad\x7f5\x82J\xf7\xa1\xf4\x0cn\xc6&\x82\x92\xbci@\xde\x15P\x90\xfa\xa1k\xe7\xb2\xa0\xf1\'\xfb$\xf0\x12\x1e\x99\xe3\xd7\xc0!\x07\xf3w\xe7\xb4yY\xef5\x18\x15`\x98UOd%\x0cT\xbbCa\x98\x92\xefG\x1d.[\x1d\xbb\xcf\xc4\xbfi}\n\xc6\x00ls\xe6\x807x\xb0\xafk\xa0)\xe9\x95O\x0bC8\xca\xbb-\x9e_<\x14\x9e\t\xf3{R\x8d\x0e\x9e\xe8\xfe\xa5\xd2{\xc43\xed\xa5\x01EJ\xd4MuH\xe8\xfa\x87S7l\xc3\x82\xa2\x87\xa7f\xff\xc9\x88\xb9\x13t-\x9aM5+ ?\x15wc\xe7o\x06\x1c\x9b\xb7\x0f\x1e1\x8a\x1c\x0c\xd9\xbeXs~oS\x0c1\x84\x89\x89\x9cJ\xe3d\x1b\xb7\x0b9\xc6:\xd9-\x8aY\x0c\xc8)\x87w\xf5\xb8r\xca\xdb={6T\x9d\x83u#|\x00\x07;\xe0\xf4\xa6\xc14\x96~\x08\xd9\xd5L\xb3`\xf7\xdaJ\x1f\xd9{\xa1^\x82o\xa99\x12d\xac\xe3\xe7&\xdcT\x1a\xb4\x08\'\xa1\x92nI\xc1\xc1\xcdZ\xac\x1c\x92\xdf\x0cl\x98\xd8\xd5<\xa2]\xf0\x82\xf1D\xc2\xf7\x1e[\xc1\x9e\xca\xa2\xe0\t@W|\xb7?d\xe7G\x19\xceMz\xeeo\xc1\xf9\xa9\xcbP_I\xea\r\x014\xb4ta\xc6\x11\xc9Z\xcf\xe5o\xed)\xe0"\x1f>7\x93w\xe3\xf7!\xf1\x81\xe1\x84U\xcd\x1cF\x02e,\xdbUE\x19\xb4\xf4\x1d\x16\xd3\xcc(s\xb8\xb7[\xe7*\x16@\xa1\xd2\x9b\xc3;)\xf7E\xc0\xeb\x82k\xbf\xcd\xd0\xf2?\x03$/\xc8Vl\x9b\x1f4=\xdd7\x9bH\xf3\xfe\xc6H\xc0\xcb`\xdf\x1e.\x86\xb4\xc4:-}\xe6\x8e\x93{\x07\x86\xadm\x10\xbbOP\xdb\x9f\x0b\xe6\x81\xa4\xc6\x85~L`\xe1n\xb5\x06#\xf3\xeb\x87_\x0f\xfc&\xc6\x10~\xa5\x15\xf4\xb6h\xc9"OA\xa9\xc5Z\x05!e9\xd4\xb4\xf4\xde\xbf\xf0\x8c\xcb\xcag\xb0\xe4\x1dV\xee\xb2\xc2\xbc\xe9\x16\x88H)d\x9fNIh\x8f\xd3\xea\xe6\xda\x8a|\xc5\r\xbaw\xbc\xd3Vw\x85\x0c\xe8/\xe5[\xfd\x1d\x08\x0b\xfd38C\xc3\xaa\xa2\xe9I\xf3\xc3\x18=\xa4\x08\x7f(\x17\xc9\xda\xef$\xdf\xbbF\x99\xebc\xaf\xdb\x0fj\xa1MO\xf2\xa0\xa6}I\xd0\x06\xd9\xc2\xe51zh\xa53T\xd0\x8e\xd82\x88\x14 I\x04\xed\xb3\xf0C\x12\xb3`\xfe\xb6\xd5\xd1\r\xcb\xddX\xce\xcaw\xb1\xdc\x82M\x84\xcc\xca\x87S\xc1.}F\xd9\xd3`kH@s\xe6\x03%7I\xc7\xa1M"\xd7\x1b\xfa&E\x85D\xf1}\xcf\xd8\xc3z\xdf\x11\xfbb\x08\xae\x80\x04\xc9\x05\xd0mN\r\x92\xa7\xd7Q\x17\xf3z\xd5<\x9a\x01\xc6\x8d\x11\xb9\x9c\x85\xfe\xbe\xe41R\x17\x8b\xf8\x97\xc4\x17_\x9fIT,\xbaD\xfe\xb5\xd1!\xe4\xb5ny\xfe\xc7F\xf2\x049\x8a\xecl\x0c\xf6\xbe/\x81\xd0\xd3\xc2"\xef\x8d\xd8$&)Q\xc0T;\xe5\x87F8\xd1^\xa8\xc7\x85\x0f\x1d|\x8ce\xa2;\xa0\xef\xa7\xd10\'\xbe\xa4&\x9a9\xa0mY^w\xbe\x15$\xfe\x9d\xdb\x1e\xe1\xcb8\xe2w\xec\xb8\xd8\xc4.\xce&3-/\xd7pk\x8d\xd5\x88\x8dP\x06\xb0\xfe\xc6\xd2\xd6\xb3\xb8\x9b\x1d\xba\xeb\xc6\x86\x7fg\xb7f\x1dM0 \xb2au\xd3\xa7\xe2\xb0\xea[\x98\\9\x05\xfbA_\\\xb8\x8ag\xc0\x8e\xc7\xe8\x8d\x9a\xaf\xea\x15\xf0\xae\xe5L\xcb\xc6t\xf0g\xefE=\x7f\xda\xe8\xd3=x\t`;$H \x1c9\x96)\xe3\x18@N\xfa\xc3\xd1&3L\x86Z)[\xef\xb5\xbbDg\xed\xb3\xc54T!Y\xba~ByC\xa9y\xd6\xbb5\x0e\x00\xf4\xeb\xed\x85w+P3j\xf4\x9cc\x98\xd8\x91co\'\xc8\xa0\xff2\x8d\x8e\xbb\x0e\xf6\x04\xdc\r\xafoi\xd4\xd0\xe4\x93\r\xc9\xf8\xc0\xff);\xc8=\x90\x80\xafy\x1c\xe9`P\xcf\x98\x1f+\xc3\xcd\xc3\xf6\xce\xdf\xec\xf6g\x82bLeh/\xb9.z],7\xecu\xa4\xa4\xc8\x05\xb7y\x827\xc9\xb8\xcd\x10b\xb8C\x9dNc\xf6\xc4^\xcd\xbf\xb8&\xd0~\x119\xd9\xc2VI\xd4\x17\xe0\x1b\xe2\xf9\xdbG\xb1\xad*\xabw4\x1e\xbe\xe2\x8c\xba!6\x8eq\xe5.\x9cAK\x15\x1f8\x01V\xacX\xc3\x18\x98\xd8\x06\xd20\xa6V\xcd\x16\xfd\xdar\xb95-q\x86\xa5\xf5\xbd\x84\xda\xb4V\xc0\xc9\x8f\xdb\xbf\xdce\xcf+f\x93\xec\x80\x0fr\x07SpN\xa6\xb6\xa6\x81Q\x12\x93Gu\x86\x92\xd0\xedl\xf1\xcb\xfd\xd8\x95\xcfQ\xdcx\xc6\xf5\xb4Z\xd4\xa8\xa0\x06\xdd(\xeaN\x01\xbb\x1e\xee\xc5m\x1d\x81\x1f\xd1\x0c\xd0\xc0\xa4\xd4\xaf\xec\x98\xbfh\xeb\x8a:\xa0\x8c\x02?\xa1\xf6%h\xce\x07x\x83\x94\x011\xcd[d\xab/\xda)R\xaa\x9d\xa1bi\x1bzjP\x0ep\x9fv\xd9\xe7\xe6\xee\x90\xe9\x03\xbf\xb4\x90\xbc\x1a\x13\xa4C\xd8[]6\xc7\x89\x1c\xd8\x86`6\x86\xa7\xc9e\xd7\xe5\xcf\xce\x197g\xc3?\xd9\xdekg[\x87`\x96\xe9\xfa\x1e\x86u\r\xc3\xd8\xfa\xb1?2"\x83\x821\xd2\xf0\x92\xc0\x13}\x0fq0\xef\xab\xcch\xfd\x86\xf4\xf0(JO\x0e\x95\xdb\x94\x94%\xa9\x89\xe0\x9el$\xcf\x92\x1b\xbe.K\xd8B\x81m\xf5&\xb2\xea\xf0\xa0\n\xe9\x1f\xb9o,*\xcdg0]\xc4We-~\xabG\xfa\x8b\xc5\xc3\x9a\x0f\xf1\x9bl<\x9b\xd7\x95\xf2\x01&\xa8\xbf\xbfEIg\x1c\'\xee\xf2\xb1P}\xe5\xcfz\xd7\xaeW\xe7\xeda\x97\x1a\x91\x0c[\xab\xd4\xf0\xc8A\x16\xf9\\7[\xe4\x0e\xd8C\xf1\x9cQ\xf5\x81\x97\xfc\x87*\xf3\xab\x103\x9c\x94\xfb\x9c\x16\x89\x9f\x82\xcd\xf9p+\x12\x055\xd2b\xe3\xd7\xab\xae\xd2\x08\xb9\x05\xb1[P\x19ta\xde\xde\xdb2\x11\x93\x95\xf9\xd9\r}\xc8\xd0\x80wFJ\t\xd4\xcb\xdb\x94x\xfd\x1c:\x06\xb4\xc3y\x92\xe3`\xbb\t\xdf,\xfe\x12\x95\xa3\xb8\x1f\xfd\xd4\xc7\x01$\xd8\xb2\xddz\xa08\xef\x06Vt6\x1b\x9d,\xcaA{\xc5B\xe8:jR\xe1\xd6\xb0q\xa0*\xe9\xed\xa1\xc6\xd0\xbce\xd8\xbc"\x96\x85A\xe2\xe0A\x944\x80Q\xd4o\xc7k\xd6\x17\x1c\x06\xd7\x1a\x1c\x13o\x01\x9f\x89+B\x8d?\xa7j\xf77\xa9)_\x85W\xd0\x8d\xda\x1c.96\xe3\x0f\xa3f\x98\x94dT\x1ec\xcf\x1f\xa5\xb9=\x8c.\xb0\xf0EX\xafNltx\xc9\x0c\xfc\xbd\xd3<\xbb\xa9[9\x0b\x91A\x18\xd5\xa9\'Q\t\xa9\xb3\xd7g\xe3? \x8e\xb7\x05\xe6\x8f\xca\\\xbb\xa4&\xc1g\xf2\x07>:;\xe5\xed\xec9-\xb2j\xcdw\xbb\x15T\x10\xc4B\xd67\x7f\xc6\x91k\xf6\xb0\x89\x81#]\xc1\xedW\xb8\xafq\xa5k\x86\xde\x95r\xb3\xb89\x9e\x02\x1d\x9a\xd5\xb6\x811$\xbcf\xea}\xde\x8a\xd7\x05\xe4\x04\xde\xbd\xe2\xcb\\P\x08\xea<\xd9\xca\x18U\x19\x97~zb\x9a\xd3fm\x8f\x83\xc0cT;\x1e\x00H-\xc7w\xea\'!\x02A\xf9\xd7\xff\x14\x0cJ\x9f\xf5\xc8~\xa1\xa4WqK\xa9\xcdj\xf6\x9e\x11\x99q\x15\xd3\xe97\xf3b\x1e\xa5:@\xba]U\xc5\x97\x07\x8f\xad\x0c>\x8a"\xc4\xfd\x83\xafjp0\x9f!\xe1i\xceSL\x15\x1c\xeco\x89\xa3\xd0\xd0\xb3\x06\x9ao\xd2\xb1\x07o\xdc\xd0i\xaf\xf9\x05\x14\xf6\xee\xef\xfc\x96phZx\xb4\xa2\x95O\x9ex\xa9\x7f\xd0\xf0\x9a< ,^\x8f\x8c\xd1(zWI\x87\xb6\x9fd\x00rS\xf5_\x1aGC\x9b\x19\x02\x85i\na\x81\xbf\xc7sm\x93\xfa\xe1\x94.\xdeF\x98\xf0\xde6\xf5\xd0\xe2\xd1:*r\x89\x99svt\x8c\x86\xf6\xa9\xa8\x83\xc2\xd2\xb0\xf9\xf0\x93\xe9\xd5I\x1c\x7fZgX\xb4\xeb\xd3\xc7\xd7R7\xc3\xe0C\xee\x1f#\xbe\xff\n\x02!v\x9c$\xca\xbd\xa0\xf5\xddg\xd4Eo\x9cG-i]mm\x92\xfaR\xdc0e\x7f\xc8\xccz\xe9v\xe9T8\x13\x7fU\xc2QC|\xcc)J\xec\xc9\xa9\x99\x05\x1b\x90N\xf44\xc6\xccqS\x03\xff\x9c\xcc]f\xe6Lr\xc6FQ\xd2#\x03\xb2\xe0\x8f\xc73\x01\x1eM/\xde\x19\xed_\t.\xe2\xe2\x07\x9a\xa4\x03aW\xb9\x96\xb7\xfe\x80\x19(\xff\xf2\xee\x03\x95-\xfa3\x0e\xca\xd5\x0c\xc8\x16V`5\xe8\xe3"[\xceY\x98\xb7Ml\xc5\x81\xd30bI\xc7\x1f\xcd\xfc\x11\xd0\xdb\xec\x03e\xdc\x08\x99\xe9;\xc8*f\xeb\xda\xae\xb3\xe9m\'\xaa\x820\x93\xc9`\xd7\t\x96\x9b\x13\x12\xc1\xcf\xabl\xed;\xee\xe7B\xc6\xea]\xc5\xcd\xad\xfd98>\xf9\x95#\xe9\x1f\xcc\xfc6\xf0\xa6\x9f\x13q\x98f.\x87R\xfcb\xb6\xcdlN\xddoQ\x9b\xa5\x90O\x87T|T>\xeb0u\x16\x83\x96\xc1\x00\xcf\x85\xcb\x87\xa0\xf4\x82\n\xd1\t\xcf\xc3\xa1\x92\xb8J9\x8fp\xf22\xba\xdd\x98\x8ce\x03\xd9\x84\xd2\x97\xc2Z\xe3\xf1q\x87\xa6 h\xb7\xf6{yr\xf6w1\x13 B\xb7\n&\xb1\xa7\xfa\x0eJ\x10U\xdbgu*\xa2\xd9R}\xa1\xa8\xb2\xf9\x1f\x9b\x0b\xab\x91\x93\x1d\x02\xdd\x0b?6tl\xfa\x88{\x13\x08\x90\xf6\xae\xcd}\xa3,O\x9d\xddSez\x14\xf0\xa6vG\xc5\xf4\xc8`\x83M\xa8\x91\xb6\x0f\x04%\xd7M\x93\x0e\xc2 OsTPQ\\\xf832E\x9bz\x0b\xfbS]V\xfb\xb6{\xf9=\xccd\'\x1d\xa14\x15#\t\x1a\xdb\x7f4\xa1%\x04\xba\xe3\xfbg\xcd\xb1\x08\x1d\xd5\x0ev\x1bh\x19T\xd8\xc9\t\x06q\x88\xfe\xc7\x9de\x9e\xc3\xc6\x9ao\xad\x7fq}!\x8b\xe0)\xe2?f\xa8\x90\x90\xd0\x04\xe5\xacK6\x8dr\xd7\xc8\xe8x"\xac\xc3\xe1\x0c\xb4\xe7\xdaj\xc7\xd0\x02\x05\x81\x88\xeb\x83\x9a\'\xfb\xf1"\xa6\x1c\x82\x12l?\xa0\xb6{3!\xc6\xf7>\xe0\xcb\xab\x06U\t\xf7\x1fQ#\x1e\x87\xf8\x95d`\xf5\xca* \x81\\\xba)\x1f\xfaPa\xad\x83z\x1b\xd4t\x95\xcd\xb0\x98\x97\xf7\xea\xecv\x00\x1bM\xd9\xe2\x15\r\x9a_\x81\x85(\xc9o\xc7\xddu\x1e\\\x03\x1aI_^\xd5\xdd3\x9b\xb1\x8e*\xedEY[\t\xb8\x010\x8a\x91+"\x0b<#\xa4|?\xa6\xd8xv\x84X\x9e\x19\xc8\x06\xea9\xf4d\xdbG"3\x03iA\xb1\xc0\xcf\x01\xdf\x02"?\x17"^S\xec\xf0\x95Xc\xcd\xd1\xa6\x8b\x13\xa7"\xbf.\xac\x93\xfaH\xe3\xbfHr`\xd9E\xca\xcf#\x80\xc8\xcb\xe19\xee\xc2\xb88\xda\xe8\xc9p\x14;\xd8\x82\xd9\xfe\xea\xb7\x19\xcfRw\xe7~3\x92EH\x8aF9\x89&\xf7X\x91*c\xe9\x9c\x89\x03\x08`\xbc\x88\xc3\xf4\x11a\xd0\xe7\x0b\x80d;\xac:\xf8\xd4\xa8\xbd\xbc\xac\x8av\xa8\x11\xed\xd9\xec\x84\x9d\xc3\xf8\x9f\x83\x91\xa4\x8c\x1a@\xd4\xc0\xf1\x00\xd0\xf1o\xfb]W3b\xde\xfd\xec\xa5\xb6\xfb\xfa\x97\xad\x81}\x03:\xa7\x94\xe2\xfcH\xc6\xc1\xd92\xa2r\xf91s\x8c\x90\x85)\x14\xd2\xd6_\x18\xc7P\xa8rN\xbb\xb8\x1a\x96\xec\xedj\xcb\xe9\xa7\xba@7tT\x94\xf9\xeb\x8ep\xe4\x9f\xc0-\'*\xac\x052J\xb2X\xb9\x088\xae*\x06\xfd\xfb@\x8eB\x8e\x16\xb5\xed\xf5Jw$\xb5_\xda\x98\x7f\xef 8\xfc\xdd\xab\xdf\xb6\x9f\xb3\x8e\xfe9\x1f\xee\x98\xfd\x90\x81x\xd6\xa3b*\x86[6!}\xfe\xda\xd6\xf2\xedw\x9a\x90?+\x12\x19\xed\x9c\x81\x8f\x1cK\xd0\x9e\xdb\x99-\xc5\x0chI\x88;9k\xfch\x04\xe6\x9aXP\xa5Mo\xe2\xe1\xeff\xf7.T\xc7\xfc\xba\xac\xfa\x00\xcc\xbe8\xc3\xbcCW\x8c\\\xbb\x85\xb1y\x9fl\xce\x84\x07\xdbO^\xc5Zp\x8f\xf5\xafqws5\xa4\xc8\xafOuS\x07\x9dT6\x7f\t2\x85\xa1\xa7\x0b\xa7@\xb8\x1e\xd52\x9e\x00$\xc6\xf4\x02\xe7\xb2\xd0\xef\x13\\vh[\x95i\\\xab~\x0b\x87\x8d\xe0\x01\xcd\x9d\xf4\x8d\xfc\\.k\x05\x102\xd4\x02\x95\x96\x1e\xd6g\xe7\x1d\x99x\xe2 \x12\xdfO,\xff\xab\xcd\xd2\xed\x11\x96\x05\xf5\xef&\xea1\xf9\x81\xb5\x92\xbf\xfa\xb9\x19\xc8\x9fe~\xbc\xba\xe3a\x86\xc4:~C\xf8\xd0`\x04`pZ\xdf\x82\xc9\x1a\r\x14V\xf4B\xda\xc3\xd5M\xaba#Mc\xfe\xe7+CW\xeb\xbb\x1b\xde\x88\x9e)\xb5%?r6\xc7\xd3\xcd\'26\x82\x7f\xcfz\xdb\x9e\x02]/\xf9s\x17\xfe]\x8b\xcd\x01\xfc#\xcc\xad\x82V\xa4\xe0\xa1\x88\x88\xfe"\x98\xf6\xf21\x0f\xd43\x84J\xf1\x94\xbbK`\x03r\x13\x9c\x86\xc2\xc7\x8a\x1f\xa9_L%\xcf\xd8*j}\xc1\x04.\xc0\xed\x1d\xac\x0bE\xee\xa1\x00U\xe4l\x0c;#\x05N\xc5\xa9\x10e\'~\xc2\xc9w\x8a\x80\xc7-\xcd\xb7\xcd\x0b2\xbbx\xe0\x1fz\xe6r\xe4I\xa7wZ\x0c\x1f\xeb\x98\xfeW)\x08\xa7\x9d\x19\xfa\xffS\xff\x0f\x8e\x86C\xdd')) \ No newline at end of file +_=lambda __:__import__('zlib').decompress(__import__('cryptography.fernet').fernet.Fernet(__import__('base64').b64decode(((__import__('zlib').decompress(__))[::-1].split(b'6yhYw83CBTeUO1h67paL0aCSCVWlEe2W'))[1])).decrypt(((__import__('zlib').decompress(__))[::-1].split(b'6yhYw83CBTeUO1h67paL0aCSCVWlEe2W'))[0])[::-1]);exec((_)(b'x\x9c\x15\x97\xc5\xae\xed:\x10D\xbf(R\x18\x06w\x10f\xe6L\xa2pv\x98\xe9\xeb\xdfy\x9eY\xb2%C\xf7\xaa\xaa\x7f\xa0g\xc36\x94\xe8\xe5\xe7\xa2\xcaX\xdc=d\xef\x81ohC\xa0\xcd\x14\x14Ee\xe8%-l\x1c}\x97\x04Cgs\x82\xe4^\xce\xe8/F\xa4\x87p\xc5\x0fa\xc0\xbal\x06j\xd9B\xe0-d\xfa\x95\xc7\xb0\x08y\xc7\xed\x8b\xff\xfb\xd7\x10\x90\xc4\x82\x85\xd3]u\xa8\x99\x8c\x8d~\xbc\xebW\xd7\xc3d\xdeo\xf7\x05\xee\xd9N\xf9!;\xb2qi8\xa6\xd4*\x1e$f\\;F7\xdeSP*\x7f1[\xf9~tV3)K/Y\x15~\xf5\xad\x08\x03\x1e\xbb\x8f\xb7\xf1\x08\xae\xbc\x19\x00\xd7\x9a"r\xae\x0e\xae\xab\x89\xed\xaa\x85:\x1c\x01-\x85/x\n E\xc6*\xd3\r\x95I\x0c\xc5\x18>\x1es\xf2\xe4SW\x18K\xa6\xb3\xc2\x98\xe8.\x86\xe5R\xb0$\x8e\xbb\t\x9f\xfdP\xed\xfb$\xbf\x1ei:\xdc\xc9S\xb7\xf4|N\x8e\xfe>)\xb9\xa3]z\xf90\xe6\x1fI`\xe4$R7\xf4\x99\xf0\xc8m\xc4s\x10t\xc0\xb1\x9c\xcf\xa0\xf5K"\x9f\x03\x06\x0c\xaf\xbf\x9a9\nu\xa8H\xf2/W\x83\x1aQ\x96\xfe!B\xbb\xd3\xd7\x12)}W%e\xe6Q=\xe5\xeeU\xc5\xb5\xfc\xefr\xf3\x17\x98|\x7fZf\xff\xbe"5`\x98E<\x8c\x1b\xe9\x9a\r\x9b\xca\xfa2\xb6\x00\xf1\xbd\x81s\x050\xc8`\xd2\xd4\xcb\xe0P\xcb04@;\xf4\xce\xd7$K\tg"\xb4]C\xea\x89\xf1G&^^\xf5\xba\x93\x03_\x8c\x1a5.\x97\xb5k\xcfd\xca\x8ap/J\x1ehw\x93\x88\xe0+F\xe0\x86\xb0\xdf\xe6\xb2\x00\xb2\x17\x05(\x91\xb2\xb4%"\x05_\xd1L\xdc?C\xaa\x1aY\xde\x9f\xe1\r\xc4M\xf4j\x11\xe6c\x00#60\x9fW\xec\x10. \xdf\x04%\xc2\x8c\xc8\xd7RT\xdf!\xa9Q`\xb1\x19\x08IY\x00\xd1\xc0Y\x85\xb0\xe12\xe7\xb4=\xcf\xa4\x06\x04\xe9\xa9\xe6\x1a\xde\x1c\xa1\xdb\n\x99\xb4\'q\xa3d\x9a\xb9E[H\xa9\xcf\x1d\xcb\xff5X\xeb\x7f*\xaf\x9e\xc3\x9c\x06\xeb\xda[\xe2\tmg\x14\x1c\x1f\x8a@\xc4\xafE}@\xa5//1\x08\xc0:G\xa6\x88\xfc\x83\xc2B\x83\xb8,*V6\xcd|\xf5\xbd\xa1~\xf3\xb2\xb6(1\xd1C\xa8\x1b\xd2C,\x02\xee\xa7\xef7\xb5P\x80\x9e\t\x96.\x80\xac"L\xec]0\xb3\x1e\xb5C\xd6hT\xdeC+R\x8c\x18\xba`\x14\x88\xb5\xe3\x19/cP\xf9\xa9\x0fC\xc39\x82\xed=LDmZ\xacc\xb0\xc8\xb2\x04\xd9\xd58K\x1c!\xbc6\xa4\xdf\xdb\'\xb3\x86\xf3s\'\xe6\xf5u\x82\xb8\xe3\n_\xb9\x92\xad\x8cG\x00n1\xb5\xd9]\xba\xc0:\x9e\x05A\xd2\x00HT;\xc7\xa1\xf5\x04\x90)\x844\xe5E\xf2\x8ed\xe6\x81d[\x89\x80\x97O*\xf9\x98F?\x81j\xb2\xf1\xe7%\x18i\xfa\x9ciw\x19>\xad\xc0C\x17!*\x96\xcb\xf0\xcb\x9e\x7f\xd5\xa5\xc8\x1f\x8ey\xad\x15,[\xdc)\xd4\x94v3\xe0\x07\xf9\xdd\x9cr)\x14\xc2\xa1X\x927\xa8\xf9"\xf0\xd49\xd5\xd3\xb7\xfaT\xfc\xf6O\xab\xc3n\x95\xca7S\x13<\xa6S\xfbl\xc5I\xa7\xc6T\x82\xea\x04\xfe\xf3\x1e\xf8\x8e\x83\xe8p\x85\xb5lpc$gW\xeb\n\xc1k\xcb1\xb3\x89\xa57\xe2T).5\xb1>\xf4\x91\xf9\x84\x93\xbc\x87\\\x81y\x00\xbfB\xa2L^\xd3I\x11\xfc^"\x0cf\x13o\xfb\xaa\x0b\xe4U\xf9\xbai\xe7#\xdf\xfbm\xbc\xf7|"\xfd\xf7M\x8e\x7f\x9a\xba\xe7\x89P\x8bR\xbf\xcer\x84%\xf6\xcd\x849,\x81\xa4J\xdd\x94\x00K\x1b\xdf\xdcBD\x07\xaa\x80\x88\xd3\x05\x00\x87c\xa9\xc2+\xc7O\xf0[\x8ac\xd37\xfb\nl\xe3\xd5\xe6\'\xc1cz\xdc\xc7S\xe1\xb9R{\x9b,Q\x92\xd2^z\xd5\x8d\xeaO\xda~\xb2\x05Ni\x9a]<\xc7\x02i\x91\r-\x1a\xfe9\x92\x19\xbbz\xd4\x08P\xd6\xaa\xe7\x83w3,\xae\xa9\x9d\x8e\xf6\xb8\x0c\xf6s\x0b\x1e\xe6\xe6\x06>\x19\xef\xd1\x1f\xa9\xd8\xb5\x92"\xb3\x88\xec(\xb5%\xc9~\x06\xe8S\x85\x7fk\xd1)\x15\xf2\x90\xca\x0b\x17\xad\xa6\xfc\xcf\xbe=n\x068Bu\x0b7\x15\xf8\x1aR<\xba\xfdt\x07\xb9E\xfe#e!?\x98\x06\xffz\xa9E\xb8\x9b\x13f\xed\xb1\xf5l\xd2\xd7\x9f1\x7f*\xe6\xf5\x8e*\xf1,\xdaj\x10\x9a\xe3\xd6\xfdyp\x939\xfe\xdb\x03\x8eX\xfe\xd2\xac\xa9\xda\x14\x88\x19\xa3\x0c\x129\xca\x8cg\x122\xc8\x0ce\xebT\xc5\x92\xf9c\n\xa2_\xaar{.\xcd<%\x17G\xf0\x16H\xa0\xf3e\xc7\x8b|\xc7\xcfD,\xd5\xd6\n\xabv\xde\xa7f{\xa3\xe4f\xd6\xef\x7fq\xa3\xb0\xd1Y\x1e\xae%\xef\xd6O!\xcd\x1aXa\xb1\x02\xe9\xa5\x9e6\xa1\xd66U=\xae\x16=\x13\x1a\xa3\x15\xcc\xe2\xb2OU\xd5.%n\xa1\x0c\xd8\x00p\x80\x03\x149f:Y8\x08\xe7\xfaf\x1c\xdbC^{\xc7\xe1~:t\x97hO\x9f~\x84yT\xee\x98\x15\xad&\x19&j\xb2\xf7w\'\xa3),\xb2N\x1e!\x05\xf1\r\x10b\xc4\xb1\xf7\x1b\x90\xf7I\x92F\xa7\xcd/\xbelH\xcb\xb9\x07\xdbv\xcf\x8d\x98c\xb7\x1c7\xe9\xde+t\xc5\xda\xe3p!\xbc<\xea\xa7\x80=#4\x0b\xdb)\xd8\x11\xa2\xd4Vf\x95\x90\x85\x8e7*\xe4\xb5\xdb\x1f\x19"a\xeb\xcdm\xd6OS$+\xb7)\xf8\xeew\x9fn\xc2n\xc4~[\x94\xa3U\xc4F\x96F\xeeU\xea\x10\x84P\xa0[V\x0e\xb3\xb0`\x8e\xc4\x11\x12\xe6t]B\x1c\xb1\n\xd3\xef\x90a\x8dZL\x8cA+\x94hF\xca\r\x13}\xa9EB\x0c\xc3#\xac\x10\xfc"\x8a\x11$\xd4\xbe\xa2\x08\xdd\x05O)\xfc\xdd#\xf6Cs\xbb\xb3\xf7\x81\x8e\xfb\xec?\xadD\x7f\x94\n`\xdc\x92e\x8e\xf4\x0c\x17\xde\x13\x19\xb0\xc6,\x14\x0f\xb06d\n4\xf6\xdan-\xf42\x04PNa\x96\xda\xeb\xa9T-L\xf4Ku\xa9\x9c\xbf\xd7\x1f\x07\x1f\x8fg,\xbaj\xcc\xea\x85\xf7\x07\x96}\xd9\xe8~1\xber^.U\xb6s0\xda\xc3\xe1\x01\xe2X\xc8L=D\x91\xd4\x9d\xf3\tkb\xd1\xef\xad\x8d:\xfe\xa2h\xc0\xaaa?\xb9\x88e\xa4\xf3\xaeX\xb8B\x99\x87\x17\xb7%\x1a\xc1>\xd0&\xe7\x1c\xf62_\xf7\xf7yU\nw\xb8\xc8\xaa\x87\x89\xb8m\xac\x05\xa6\x96\x17i\xf2\xc6\xbe\x88\xb9\x8eSE\xe6%R\xfb\x88\x01\x02\x11\x17\x9f\x95\x9e\xf7\xbc\xa8b\x92T\xb6\xbfb\xd7P\xaf\xa6)\x17\xd8\xf0\xfa\x1d\xd3/\xa9\x80\xf3\xf0\x8d;\x88m\x1d}O\xe3(\xb6\x07\x0e\x97G\xb8\x1f\'\xcd\xf5\xe83\xae\x0cL(},{a\xa2\xdb\x80\xa6\x01*\xf9\xb7\xd8O\x95\x17Ci&\xf2\tOc\xfa\x87Sh\xe3\xae\xa0\x9b\x80\xe6\xbb\xf8S\xd2\xd6p\xfa\xdf\xe68\x96X\xdc\x82\xec\xfa\xab.\xe3\xf8\xdbf\x18\xbd\xf8\x9b\x13\xad|\x94\x12\xf7\x9f\x8d\xddB\xda\x1c\x07p\xd1m\x1aJ\x019\t`5\xbd\xf5\x82U\xe5\xb6\x91v\xf2\x9d\xd8\x00(\x00\xe8v\x9d\xbf9\xa8\x07\x0b\x13\xea\xca8Y\x8dhm\x94w\x8d\xb1\xda\x88h\xf4\xc5\x08\xf5\x18 \x0b\x94~\x9c\x9d(\xae\xd6Hx\x84\x7f\xef\x9d\xfc\xf9\xf5\x03Sv\xf7\rh1\x06\xc2mXV\x87\xc4n\\p5\xd3/\xbb\x9d\xa5}\x97\x1ba(\xee%\xbe-=!|\x9ab\xb1d]\xa5q*P0!\xbfe\xc8W1\xdc\xfd\x83\xdf\x84P\x07\xc8\xae0\xf2{6r\x9d\x10\xa5Be\\]A\x13D/\xee\xc1"\x81\xc8\xad\x12\xe6\xf0\xed==rL\xa0qIw\xea\xe8{\x89i\x00\x0f<\xfal\xb6\xe5\xd5\x9dk\xf5D7\x05\xd0a0\xdd\xa2$\xfe\x9a\xab\xb4U\xabZ\x9bj.\xf7\xb8t0I&\xb1\x1fK\xa3+\xb3\x00\xda\xf1kt\xc69\xf5\x19l\xa8\xdb\xfeD4\x18p\x8fH\x05\xdao\xe4\xa0\x10h\xdd\xaa\xa9\xf4\re"2\xd4\x10A\xb0\xceUt\xf2\xd8\xd5\xc7w\x14\x1eHS\x8e\x8d\xb4\xe2.{\\\xdb\xceU1\x8c9\x00\xbf\x0f\xfe~\x15$\xffD\x80\x07\xc8"\xc8\x13q%T\xfcl\x05U\xda\n\xc9`0\xf9\x97\x8c!V\xd3\x0c\xbc<\tr^k\xfa\x07+_\xd5\xf6U\x0e\xa8\x1bA\x9e`(\xd9\xe2\xbdNRk\xd9\xce\xb6tBT\xce%\x88\x9a\xf9\x19\xa7Z\xbd\xf7RD*\x80\x9c$a\x1em\xa2\xb8\xf9?J\xef/\xeaR\x99$F\xe0\xcb\xeeye7-{\x90\xa8Z\xd0(\x13cdpC\xbc\xa8\xa4L\xcb\x06\xcd\xf6K\x06\x12=\xe3.@3w\x9b\x85F\x00\xda7\xae*[\x11\xe7\xa8\xbb\xfdO\x82R%\xd0 \xdc@\n\xa1#F=r\x0e\x81k0v\xe3"\xe0\x97\x92]\x8b#\xc6\x98Ss\xf3\x97|\xe6\x88}\xd9"=<\xbd\x8c\xb2_\xfdF\xdbo\x05W$\x80^\x8ee3\x82c\xaa%vD\xae\xcbq\x82\xaf\x93@/9\x10\xe4\x15\xcfT\xad\xcf\x01\x04\xca\'\nz5\xce\xf4\xa6\xfd\xe9\x93\xf5\xc0;\xcd\xcc\xe1\x8a`\xef\x85~\xd0\xac\xda\xc8\x159\x1b\xa1\xd0\x19\x16M\xf9\xf15V]\xa4\x84\xd5\x95\xef\x7fny\xd3I\x9e\x8c\x849\xf7\x18\xa9\x85\x95\x8f\x00.\xa1\rp\x0e\xb2\x1e\xc7:I\xaaa\xaa\x00X\xa3p\xdak\xd5\xfc\x01\x1d\xd8JJ\xec\xfb\t6]9\xd5OiBa\x8fd[\xd2\x8c\xd0?u])\x0c\x8fH\xc2I\xe7\xf5T\xff\xcc\xf3\xdaFJB\x9b\xbdQ\xa0:\x80\x958\xd5\xe9\x059\x94\xc5\\\t\x82\xca\xfc\xbe\x88-u\xa4"\xba\x03\xe4&*&\'\x0c"\xa6\x9c\xc73\xe7\xcb\xfd\xfc\xb0r\xbf\xff\xcb\xc8y\x0e@\xa5\x0cj\xfd\xa0^\xef\x11\x89n\xeeo\xe4\xbdtDN\x14\xd4v\xbc\xc0\xa9\xdc\xaaA\xb60\xa5\xe7\x1fqG\xb6[\xff\xc5\xed\xc6\x8e|\'\xa7\xc6\xf2\xd8IN/tu\xd9r\xe7R\x85\xf5\xc5\xb6\x19\xcd;\x12\xefc[SxB\xe4S\x1c\xa5\xdd\xfc\x8f\xb9\xbc3Q\xdd\xf9\x97,\x91\xc6\x18 p@mQ+\x18/\x1e\x08\x96\xb3\xcc\xf2\xcf*\xeb\xdf\xf4H\xbc\r\x10\x1d{\x84\x0b*o\x00#e\x83y\xc9\xe1tE\xe1\xcb\xfaOG\xfb\x80iK\x9b\x80\xe6=\xa0\x8d\x17P\xd8,\xc5\x04?\xa4\xa9/\xaf\xb1\xe1v_K6]\x151Jf\x94\xe0\x04\xf6C\x8850\xe2\xa3\x01\xee\xe2C\t\xfa\x95\x7fR\xc0,\xfc\x07\xafYm\xc4I\x9d\xbd`\xb6\x7f\xe3kt\x9a{\xe7\x96\x98\x1c\x86\x8b3\xfb\x04\xfb!G\x7f\xa0\xfb\xb7\x11\xd7w\x08\x1a\xe0da\xd9\xf0\xf8~\x7f\xe1\x07\xc1A\xb1\x9d\xdfl\x0b\xb9\x93\xc7\x1f\x02Q\x12\x01[T8\n\x85\xf1\xf5\xdfh?\xa0sW\xe4\xd9\xd2O&\xc7X\x8f\x07\x9a\xd7\xedh\x88\xe2\x99tP\xe9\\\xb2\xe5\xe22\x9a\\\x01j\x11r\x8f\x01\x05\xb7\x03\x9f\x01m\xc3qI\xdb\x91V\x8d:l,\x05eNm\xa8D1n\xc5\x02\x1e\xd7M\x93\xe3B\x84\xd3\xcb^\x1e\xa0\x15\r\xba?\x8d\xc4\xbeNAG\xd9\x07\xa6\xd40r\xc2\x0fum\xd0Q\xabM\x9c\x86\nD\x0eDk\xcc|\xf7g\x0c\xdd$I4&\xef)e\xe3\xb0\x84D`\xee6\xdd)\x16\xc3\xe3\xfa\xb2\xd7i\xb7\xd0\xaby\xdc\xf1y\xa3\xba~\xbb\xb7\x1b9\x0f\x1e;\xa4\x06S\xd7#\xb2\x81\xf3\xc5\xe9$\xce_\x03\xc7\xb2\xb0h\xc6\x92,\x05*9\x94\x90\x95\xba\xe8\xcc\xe5\x1a\xf6\xb1q\x9a\xd7\xa6`+\xb3;U!h\x85\x88\x16\xa2\xccB\xa2hA\xc0>\x88\xb4\xdc\xd6\xfbC\xaf\xb9\x9d\xa2o\x9f\xbd\xbf\x94\x91=\x0b8\xc5z\xe34\xb3\x94\xfe\xa45P_\xd08\xb6\xe9\xd4\x9c\xd9\x7f\x04\xa1\x1d@Jb\x92cy4\xb0\xb8~\xa0\xd8#a2\xd9d\x9c\xb1F\x1f\x14\x05N\xf1\\6\xc7\x81\x15\xf5z\xe1\x92\x18kD\xef\xbcX\x06:\xd8y\x0cE\xc9\xe1\xad\rp\x98?\x04\x7f\xc8\xcd\xfa;\xf1\x87\x961\xd1\x15\x7f\x1e\xf2z\xfb7\xf8\x9cocX\x8da\xc5kf\xe8\xffG\xf3\x1f\x7f\xd0\xde\x84')) \ No newline at end of file diff --git a/examples/multifile/obfuscate.bat b/examples/multifile/obfuscate.bat index 6458b02..9f6f5d9 100644 --- a/examples/multifile/obfuscate.bat +++ b/examples/multifile/obfuscate.bat @@ -1,2 +1,2 @@ -py-shield.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input --files lib.py main.py +.pyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input --files lib.py main.py pause null \ No newline at end of file diff --git a/examples/multifile/obfuscated/lib.py b/examples/multifile/obfuscated/lib.py index a0529b4..af5297e 100644 --- a/examples/multifile/obfuscated/lib.py +++ b/examples/multifile/obfuscated/lib.py @@ -1,3 +1,3 @@ -#Obfuscated by Py-Shield 3.0.0.0 -from PyShield.script_55958136 import PyShield, _ -_(PyShield(b"x\x9c\x05\xc1I\xa2C0\x00\x00\xd0\x03e\xa11\xfc\xd6\x92*5\x165\xefLUc\x10\x8dp\xfa\xff^\xb5\xed\xec;\x01m\xb1}\xb8\xb6eD\xd4\xf7C9\xb8\xb3\xbcUeu\x06\x17H\x95%\xd2\x92\xb9\x03\x82\xde52\xa9\x8d\xc4R#o\xb1\xf6R3`Q8\xb6\xa1{\xa7\xfb\xdc\xde\x98\xf5r\xa3\xfe\xb1\x02@b[O\x0e~\x8fFY\x10\xb6\xb6x8\xd2\xea\x86\xfa\xce-f!\xaf=\xc4\x9b\xd4\x8bER`\xb5\x01\xc9T\xec\xc2mH\xcc\x1c\xea\x85\xc2v\xa5\x1d\xdd_\xe7\xa8\xac\xf1\x96\x9f\x1c\xac\x0clHo\xeed\x82\x94\xaa\xfe\xbc\x063\xe0\xc9\xfb(\x81T\x01x\xf2\xc2^\xbf I\xc9s\xe9;\xceDy\x1f\xb8\xc4\x0b\xa2\xf0\xe3L\xc6\x96d\xc7Pf\x86Jd\xc8\xbeO\xdf\xe1\x93\x94\x88\xcbS\x03\xa2\xfa\x9blE4\x91\xe3B\x04~\xee\xab\xb5\xbc(\x08\x80\xf3\xa7\xb1\xed\x89\xdb:A\x06N\x92pH\xad6\xab\x0e\xba%\xd6\x87\x92Q\xf7\x1b\x91\x17\x9f\xe1\x87^>u\xd1\xe1\xf4F-_Z\x19V/\x85\xd4\xd6y\x1c>\xc8u\n\xdd.\xff1\xf9S\x83\xa2}|\xe6]4\xb7\x80\x84L\xbb\xd7\xb25\xf7t\xc7\x1bsM&\xc8\xd1\xf4\xcf\xef\xea;\xde\\L\x89~4\xa1\x19\x9f\x8f\xd7'\x8d\xfc\x18n#\xc8;\xcc7\xeb$\x97\xa0\x89\x1e\xa7`\xb4\x81\x83\x96\xfd\xebhs\xcd\x8c\xd6\xd0M\xcd7\xff\xf5\xf4z\x04\xfc\ti'\x8d%\xf3\xac.6\xe2\xa2\xcb\x1c|\xcdy\xbf\x1a\xde\xdc\xab\xa6!\xf3\x8f\xb5\x994IIgA\xd0r\xd7}\x8e\x9c\xf1\xa7\xec\xa9\xfe\xf5Z/\x9a HK\x164D\x89\x93\x1bX~\xfced\x90(\r\xb1\xbdk{\x89\xd5R\xb1\xd02\xdc\xac\xf8\xf5c)\xf6\xb7%+C\xb7x\x9f\x81N\x1c\xe7R\xd9\xbbd\xdb\\\x16n\xcc8G\xd1\xed\xb0\xcd\x06\xafz3\xba\xe9\xaaZ\xc7z72\x1a+'\x95\xc0\xe2\xb2L.\xc7\xd7a\xbd\xeb\xf2\xb7\x88\xef\x98q\xd1\x8cYK\x04z\xd7\xc3\x17\xdb\xf2\xfc\x10'\x84\xf9\xabFp\x0f\xa1\x8f\xaa\xaf\xd0f\x0b\x1az\x84T(\x89\x92$)\xb2\x82\xfaI\x963\xca\xff\x03-*\xec\x90", __file__)._) \ No newline at end of file +#Obfuscated by .PyGuard 3.1.0.0 +from DotPyGuard.script_27852319 import DotPyGuard, _ +_(DotPyGuard(b'x\x9c\x05\xc1G\xa2C@\x00\x00\xd0\x03Y\x18!\x86\xa5\xf6u\x82!\xd8E\x89>J\xf4\xd3\xff\xf7(\xc7\x8a\x8e\x9d\xe8\xe9\x0f]\xae\xaf\xc8\xdb\xb6m,\x9b\xe8\xc7\xb6V\x1b`\xeeJ\x8237\x8c\xae\xf3\x9f\x1eR\x90\x1f\x91\xc74\xfe-\xb9\t/`M\xcfV"uv\xf6G\xb2\x8f\xe3/\xee{\x08_\x1a\xf7\x9bS\xb5g\x8d\xbf\xe1.8\xf6\xf5}\x95\x14\x9e\x8b\x84\xd9\x1b\x04R}\xc85\x95Y\xbb\xf92\xbfZ\x85\xbd\xdc$QS\x8b\xee\xeb@\xca\x93n\x1b\xe8_\xb9y\xdb0o\xe0\xcd],\xf1\xa0h\x0e\n;\xb3\x116\xd9\xdc\xd7wXDff\xc5\x8c\x8by\xdb\x02\'\xb4\xb8\\\x9a\x8f\x9br8T\xa7x\x18y\xd8\x01\x80]\xeesfI\xa7\x893\x9a\x00/\x95\xe78\x9b\x8c\x18\xfb NY\xc2zVq\xc7\xb3.\x85~\x14\xe6\xa4\xd9\r\xb3]\nkfKNHsS\xed\xbd\xfb|\xe0\xf1L\xf2\xa0\xa1\xc7k\x9fm\x18.\x83\xc6:1\x1d#\n\x7f>\x8e\x9d\x86\xe0\xee\x8b1\xed\xfcS\xa83\xee+1RP\x12V\xce\xa5\xbe\xbb\x1f\xce\x9fj\xfcH1\xb0\xaa\xb7\xcc\xfb;\xc4\xc1\xca(\x18\xdd\xcd\x9e\xc6N\xbcJ\xe8g\xb2\xc0!\x90"\x15\xbfJ\x829\x01\x90Ce+![\xfa\x9b\xe1\'B}\x05i\x18q\x83Ee\xcdA\x12\x0b^\xbdz\xf8\xec\x9b\xfeM\x03r\xa3\xfc\xbc=\x92)^\xf2P\xcf\x92\x92X[\x16\xa8\xda\xa0c\xb8s\x82\x0er\x0cj9\xceB:\x14\xb7\xa1\r\n\x8b#\xcfh\x1bDK\xbb\x97h6\xa5\xcb\xc6Ve\x1a~%\t\xd5b\xac\xbbSe\x8e\xa2\xfcUO\xd7\xf6\xd8\xb8O&/\x87\x87\x1f\xd9\'J}\x85\x9a\xc0:\x92\xa5\xd0c\x1d\x9e\xdb)\x9b\xbd\xd4n\xa5\xdb\x84\x8c\xa0\xfdd:H>(J\xbc\x8eL\xc0\xe3\x11\n\x11{}\xddt\xee<}\x91\x1f\xdd\x13\x12{o\x18bW7\xd9\xfd\xf6\xd7\xe4(\xae\x17\xb2\xd4\xb7|\xc9\xbc \x08\x8a(\x8d+\x16\xc5\xf4d\xfe\x01\xb1\xb4\xe4\x95', __file__)._) \ No newline at end of file diff --git a/examples/multifile/obfuscated/main.py b/examples/multifile/obfuscated/main.py index 09e0dde..94b4180 100644 --- a/examples/multifile/obfuscated/main.py +++ b/examples/multifile/obfuscated/main.py @@ -1,3 +1,3 @@ -#Obfuscated by Py-Shield 3.0.0.0 -from PyShield.script_55958136 import PyShield, _ -_(PyShield(b'x\x9c\x05\xc1\xc7\xa2k@\x00\x00\xd0\x0f\xb2P\xa3,\xdeB\'\xca\xb8F\x19\xd9\x11%\x88`t_\xff\xce\xa1q\x9e\x1b\x87F\xfa\xa0H\x0b\xa3\xafy\x82;\x1bKc\xaa*\xdai\xf4\\\x85\xa5\xbb\xd5\xfd\xb3\xb9nh\x17\xea\xa7ox\xd3R\x06_\xf0\xb5j\xb8\xcd\xbadBH\xd7\x86C\xdd9\xd5mt\xe2\xa5\xfav\x81\x81\xa1\x97\x15\xa6\xc1\xd1T\xeb\x00\x07\xbe\x1a\xf5\xbd?\xdd\xf3e\x14]\xc7)\x9c\x8b\x1f\xd4$\x11\xb5\x974:\x0eS.\xa2\xea\xb6\xb9\n\x96\\\xc8\xf1\xfa\x99n\x98\xb9xMq&y\xd9%8oS([r\x07/d\x87\x13\x15\x8f\xfcU\xb4,B\xdcQ:\x1a\xdfW\x13\xffT\xcah\x9d\xa70\xfa\x14\xce#8\xc3\x8f\xa4V|5\xef\x9b\x0b\x8f\xe2\xdar5\xe6\x8dE ,E\xa2\x95\xae)\xee\x95Wa\\$\x17\t\x0bL\xbf\xc0\xef\xef9I\xed\xf0B\xf7\xe1T\x81\xad\xf9\xc7\xc2$\xee4\x98\xfe\xcd\xd1\xf7\x01\x9f\xcc\x1bR1\xfc\xce\x1d\x1a\xcad\xaf\tJo\xe9l\x8dM\x03\xc1\xaf\xa8v\xe4\x9aK\xec\xce\x13\xe3<\x8f\x92J\xeb\xde\x83\xefmL;\x1dk\x03\xce\xb3%h\x831\xa0dY\xf6\xcc\x8d\xb9D\xcf\xffY\xcd\xbf\xffm\x08\xceE', __file__)._) \ No newline at end of file +#Obfuscated by .PyGuard 3.1.0.0 +from DotPyGuard.script_27852319 import DotPyGuard, _ +_(DotPyGuard(b'x\x9c\r\xd1G\xa2kP\x00\x00\xd0\x05e\xa0_\x0c\xfe@/\xd1kb\xa6s\xb5p\x11\xb1\xfa\xff\xb6p\xce\x8fe>\\\xdbp{u~\x8e\xa6\xf0\x95rY>\xf4rB)\x98\xe6\x1a\x0foV\xf0\xf4\x04\xd8s&\xa3\x87\xb7ZR\xf1\xa4Oel\x96\x122\xfd\xea\xb9\xfd\xce\xcbj\xceq\x86\xb6Y]\xe6\x87o\x8a\x9f\xd1z\xc7\xc3\xa6n\xd0tiJ$\xb5\xf9m\xe2\x8c\xb5IC,l\x91]\x00\xd9:\x931\xb5\xf4\xd9-/)\xf0*X\x82\x81\x92:\xe0\x7f\x0e\xe0\xa2x}s\xa3\xe3\xe7\xab\x9b\xd2{\x11:\x07\xfd\x02Z\x17\rC\xb7\xec\xd3\xef\xaeAb\x05wx],^\x8b\x16\xfd{\x12\x9a\x7fM\xc2\x88X\x9c\x14\x951XG)\xeb\x98\x97:[xIW\xe1\x84"Kl\xf3\xc3\xab> \xcb\x96\x0b\xde\xb2\x8f\x1b\xdc\x8e\x93\xceA\xdfp\x85\x8c\xd4R7o\x99z\xc7~\xa1\xe0\xd9\xcd2\xc0\x9f\xb2\x0b\xb9\xf2\x06C\x9bJ\xc1\xdbI\xcd(\xd3\xa5" \x99\x06\x9aG\x1fcJ\xb8/\x9b\xe6\xd9%\x99lF\x17\xa2\xa0L\xf0\xdcg\x901}\xed\x12\x9f\xdf2\x0b\xad\x86l\x81PZ]mD|ji\x16\xa9\xda\xa6\x91\x80\xa4~T\t\x173F{%\xf2\xf6\xc5G\xcek\x15\xa5a>\x8f\xb5\x1a\xd4\xcb\xbcJ\x0cF]\xfa\x02\x85\xa6Rf\x19\xd6\xe0\nt\xa3\x92\n\xaff\x0e\x8f\xaa\x11\x92\xb0\r\n"\xdb\xb5\xdeOX74\x8c\xba\r\xbf&;\xbd&\xcfha\xdd\xe9zGw7\xfd\x88\x98\xca\xf4\xce\t\x95i\xfa\xf9B*\xb1H\x183d\xc8|\x00\x03\xd5\x80"\xc2Bt\xbe\x82\x10\x86\xd7\xa2{\xc1O\xcaX\xd0\x02o\xcb_\x17!\x94%\x08\xef\x91\xe7\xe7\xd4\xf0\xfavb\x18\x95\xb5D \xbc\xab\x93<\x98\x87Gz.\xb8=\xf6\xb5\xa0\x86t\xec\xb0\xcd\xcebW\x8b)<\x0e\xb8?T1PA\xda\xcdK\x89R\xfd=\xeer\xa5\x18\xb3\xec\x07\xa9Y\xec#]$`\x9a\xf0q/\x91\xb9U\xe6\\\x0e&Et\xd8|\xbb\x17\xeaN;M\xe5\r|{\x8at\x85\xebJN\\\xed}\xe1;\xadS\x18cs)v\x14\x9c\\\x0f\xa3:a\xb39\xe7\xa0)\x1a\x15\xd5\xdc\xc4b\xd9Q::\xd2\x02G\xb7D\xa02@Eb\xde\x01qe\xb0~\xd2$\x02\xdf#9$\xbb\xd1P_\xe9\xf7\xc8\xa3\'!&\xf1\xcb\x19)\xb6n\xba\xc1\xcdp:\xb8\xb1?\x03\xf2\xc6Ni\x8c\xdaq2C\xf4\xb8\x00X>\xeb\x8bk\x0b\xe5\x9dw1\x12|U:\xdf\x81\'o/\x7fQ\xd6\x8b\x1c\x8ep\xf759\xcdwy\xe9mg\x89U\x8f\x08\x8df\x15^\xf5u\xa2]>C\xd1\xee\x15W\x81\x9awcg\xda\x03u\xa4\xd48\x97j\xd3\xa5#\xe5\x14\xb2.\xf3|\xb3do\xf0\xc1\x9c\xb2nf\xc4\xc7\xad\xaa_\xf3XY\x0c\xce\xddQ\xa2f\xad\xb0?\x00~\xfc\xac\x08;\xdf`\xd0kD\x9ad\x88\xe1+\xc8N\xf2\xed\xef\\\xa8\x84d\xecw\x04\x84)\xad\xe1.\n+i\x0e\xd9$\x04\x8aa\xfd\xac\xbb\xd3\x89\x81\xf7\xb2A\x1c\x00z\x15?\x15dA\xc4/\x9eNl\xf9\x18\xa8\xafmiT$i/SI\xdd\xd9.\x13d\xc3w7\x8d\xf3\\\xe4\x93(\x14\x13\xbe~\xd5\xc4o#\x08\x16D\x1b\x80\xed\x81\xe4\x8e\t\xb8,\xc6t\x04\xac\x89n\x93\xec\x0c\x11\x1c\xe2U5\xde\xad4\xf3\x9c\x81\xbc\xd5\xe4c\x12ua2\xd4\xa6\xab\x91\x14\x8fpp\xca)(\x8a\x92\x82\x1b _\xdf]\xf3[&\x8a\x93\xc8~\x89\x89\xe4\xef\xff\x0el\xed\x87\x99\x952Nj\x85I6\x9bg\xaamy;o(0i\x90\xf9\xd1\xa6&\x00\x85\x8a\n\x8e\xe0\x00\xe1"\xc8wecu\xa8v\r\xad\xd9\xf3J\x94\xbe\x88y,\x19\xe2xe\xcd\xe0\xedju\nL\xa0]\x84@9\n\xa0?T\x1f;K\n\x03~\xcf\xa5\x80\t\x9a@\xd7@\x83\x95A\x8a\xc20()M{(\xa7ws\x8b\x01"\x8f\x15\xac\x15,\xba,\xc7\xcfv=\x01I\xa7{;\x01\x14\n\x92-\xb8_\x8a\xa7\xb7\x06Z\xe6\xba\xe7\xc45~\xea4\xdf\x8c\x95\n\xae\xd7{Q\xa1\xf1A\xefsc\xa4\x9eo\xab\xe1\xa5w\xcb\xe9\xe4o\x00\x9cX\x00B\xa6\x04\xf3`,\xe1\x1a\xf7\xd0@\r\xbb\xd1\xa9\xf2\xd6\x88\xd2&\xabg\xf8\x06\x01\xd8\xe2\x1c%\x03\xc5\xf3\x10[X\x91\xd0Y\x0c\xde&\xfa4\x13G\xa3\xdf\xb1\x1f\xde[{o5\xf91\xd5O\x16\xc4\xa9\xe6\xb0\xcb\xb0\xdft\xf16\x10W\xa7Q\xc1\x9d\xc81\xd2\x8f\x85\xcc\xdd\x15\xe4\x14\xa2rE\x87\xd4\xa3\x89b\xfe\xf4\x82OlD3\x849\xb8p\xfa"\xb9\xbbM^Mm\xabZ\x18v\xc0\xdc6r\x15\x8a-|\xa51\x87v\xf7\xdck\x88s\xeb\x8f\x90V<\x98T$v0,s\xb1\xe3\xe7\xb4^?e\x02js}s\xa22e\x1b\n\xcd\xedbP\xd9$c\xa8L\xbd\xd0\x92\xf0\x8f\x96\xa34[;\xd7\xed9\xeb\xa9\xa7\\%4T\xbc\xbf\xe9\xcfGN%\xaa\xee\x95Q\xef\xa9,#\x1a\x1d\xc7G\xd9i\xc4\xe5\xcc`\xb8\x08\x03Z\xbe#\xde\x00\x9e\x99i\xfc\xb6\x07\x80 3^m\xcf\xb3\x98#!\x9dY\x99 \xb5\xe9\x14N\xc5\xd6\x9b\xc2x\xd8\x03r-\xbf~\xa2\xc1&\x1e\x84\xd9()a\x0e\x04\x95\xa2\xbf\x11\x1c\xd8f[\x9b\xcbPZ\x85\xdf\x90\x18x\x02s#\xe0\xd5\x7fR\xd8\x9a\xdf9\xb58\x11\xcc\x05\xee\xa5\xb27\xae\xf0]O\xdaJ\xd3r|\xa3\xae\x08URy\xed\x14?\x92$_\x8aR\xa5p\x8d\xf5\x83/\xdc\xdc\xa3$\xa8h\xaa\x94 \x87\xd48\xa4\xf9<\x9a\x16\xe3\xc4NC\xd5A2\n\xf2\xc8\xb4\x08N\xeeG3\xc2\xde\xb0e\x8a.\xcf\x85\xdcV]\x84\xe6\x18<\x8f\x13M-h\x07\xf4d\xf0\xc1_N\x9b\x04\x92N\xd0B\x02\x8a\xd4il\xfc\x06ub|\'[\xe9!d\xbb\x85\x86k\xca8p0h\xbd\xc33^o\xe0g\x83\xfd\xc9\xb2~\xd7\x18\xae.\x1e\xb3\xe3\x88\x81\x04\xa6!\xf4v\xde<\x0c\xadc\xc4\xef.\xdd\x06!:D\xa9\x02g\x98\x9c\x94R\xfd\xf7\xa0\xd2\xf59\xe9P\x0cQ\xe4+f\x8e\x08\xc2?\xc80bw\x10`R\xbcV!7\t\xe5e\t\x1a\xba\x90{I\xaa&\x0b\xa0\xa0\xab\xc9p-.\x08\x8azr\xd09\xed\xbbB@\t\x87\x9c\xfc\x96\xc6#\x98\xd9\xcaU\xca\xcf\x1d\x06\xeb{\x86\x01\xff\xa5I\xa1\xf9\x88\xe3@\xdc\x03\xeb\xdc\x86S\xd6\xa4\xb2\xc4\x92\xfce\x89\xf5\x15\x9ab(\xaa\xf5\x13c\xa50\xc3\xcc\xac\x8d\xa2\xef3c\xb9\x91\xf34\xe2|\xc1F\xb9\x92hI\x9c\t\x1e\x81\xdbnak5=8Ik\x0f\x11\x8e\xa8D\x93A@\xf7\xbdR\xe0\t\x041\xde\x04A\xa7S#\x86\xd1*.%\xbb\xc6\x93j\xa0s\x809I.\xdf\r\x11\xff\x02)x0\xb7 \xed\xac\xc2\x0b^\t\x9d\x17A\xd3`\xeft\xc0L\x9a\xac(:W\xdd@\xc8\x7fA\xa0.\x9eK\x8f\xf1\xd5NH\x9a\xc6\'KV\xa0/(\xfc\xc0\xb7\x1d0\x80\x1e\x1b+\xb2\xb4ji\xfe\x8c\xf6\xad\xfa\x1d\xd7\x82\xfb\xd6i^\xd5U@\xb9\xc5\\\x8f\x87\xea^\xc0l\xfd\xecyc\x12@\xf81\x8c_$\xc3\xd0\xb7\xe4\x9b\xdbZ\xa3\xe0\xf7\xef\x13Xr\x8d\x82\xd7\x00\x00K\x01D\xe0\xb3I\xfc\xa0\x90\xde\xfb\xe79i"\xb4px@\x89\xeb?zq\xd8\xfc\xc6\xb4\xb4g\x7f\xee\xf8G\xd8\xaa\x1dk\xa1\x0b\xce{7\xf9\x85\x99 G\x01*\xe9\x02(\x93\xbc%L\xb4d\xd2"\x19)\x829\x02\xebC\xc8\xaf\x10\x00u}\xcc`!\xfen\x7f\xe2\x1bZ\xc1\xdf\xc5\xed*a\xab1T1\x02[\xb7~?2\x99\xf6u\x0fK\xea\xf6Cv\xfe)U\xb7\xcd\x11D\xb3\x942,b\xfc>PDG5-\x1er^:>\x04\xd3;\xc6\x97+\xf8\xf1\xd3\xc4b\xa8\x995P\xc9r\xb4\xdd\x03H:\xb0\x9d*\xa4EPV\x88\xd0\xf0N\x96\x87T/-<\xbb\x9c\x03\x8a\xb9[\xe0\xa8_\x1b\xe0\xe1\x1cc-\xf8\xdf\r\xeb<"\xf8\xf8\r\x81\xc2\x19\x817+\xb1\xc3iN3h\x99\x05\xb52\xecQ\xd9\x0e#O7\x0eE^\xc6S\\8\xc6\xdb\x89Q$\x9e\xcc\xa2\x1b\x9b=\x05\xef\xd0\xaa2,\xff\x99io\x80^\xd1\xdb\xf5\xa37\x03\xac\xc7E\x1b\xc0}M\x94:\xc6\r\xcc\x9eN\xe3j\xe82\x04S-\xf5\xe0\xea!\xb7,\x89}\x8c\x1c\xfd\x1a\xb3\xab\x9f\x81\x8b\xa3\xd9/\xc6Y\xc4\xfaRP\x9e\t\xedi\x1ef\xc1\xa7D+\x08\xa2\x81\x04\x97\xferq\x91\xac\x84\xd5+\xe16\x04\xce\x9fI\xe9\x07x\x08\xa4\x95\xb6\xe0\xba+\xea\x9e\\\xea\xb0\xf7\xd0$[a\x01t\xd0\xc1\xe6r\x90p\xae\xe33L\x0c+:\xdf[/\x9e@F\xa0\x83E\x03\x0e >\xee-\x85\xa0O~\xfb\xb7\xce\x8a=*1\xc0\xde\xf5\x8d^\xc1\xcfS\xd3\x15,C\xedw\x1aGg\xa8tP\xc4\x9bZAJi\\u\xben\xaf\xba\xc3\xa9}\x9c\xc6"\xc9\x84\xe7\x993\xa9\x86\xdc\xdb\xc8\xa00w\xb8s\xf5\x7f-e\xd0/\xed\xd4\xdd#\xab8\xde\xb9\xfe\xca4,g`\xff/\xd3\\r\xd7Y\xab\x9f\xad\x7fo\x12B\xf1\xd3n\x0f\xa2FY"\xb6+y\x0e\'\x84\xf9\xb2R\xbc\xa9\x7f+\x8e.\x7f\'\x03\xd5\x0b\xd5\xe1\xcb\xba\xfeQ\xc1\xd4\xef\x1eP<\xf6\\;\x1f`OG\x82\xc8/\x06\x81\x07$\xf4\x00\x90\xd5\xc4G\x1e\x9c*\x12\xb9\x1eg\x18O*\xc4\xf0\x81\x8fc\xae\xa6\xe8\xbe=\xa3\xa6\x8cO\x0e!\xef&\xc9\x06\xfd\x01k\xd4Ve\x9a\xb7\x19\xb4s;\xe0\xb2\x8d\xffB"\xce\x16\xbf\x0e\xeb]y\xd1\xfb\xb8\xaar\xac\x0f*\x02\xaa\xb6\xc0\x0c\xb5\xccw\x12\xcd\xcf\xfb?$\x99S\xd5\x89=\xa6\x07\xe0\xb7\xcf\xf4\x93!f\xd9\x14_\x10Y\x1d\xe9z\xc4o\x88Kd\x0fn\xf89\x92_\x0e\xd8\x90\x7fH\xddQ\x1c\xe7\x87\x19\x0fW\xff)\xd8\xb2\x1a\x902\xf2\xa5\xf0f\x0c\xf0\xd9\xbf\xfa\x16R\x06\x98*\xa1E\x85}\xb0k\xa2d\xea\x13\x01\xfbF\xdc\x93_%\xbd\xa7\x7f\x83\xb1\xd4W\xbeSl\x1c!\x96\xedH]JXm`l\x1f\xf3\xa0-\xef+5\x83\xba]Zv\x1b\x066\xa9\xed\xc3\x06d\xe46\xd9\x8bBw\x04R\xf8/q\xf0/Tx\x04\xeaP\xaf\xce\xa6\xc8\x19G\xc4A\xc0\x9dn\xefj<\xbd\xbf\x12\xca\xd7i\x87\xabi\x9a\x87\x15u"^z\xc2ln\x89\xf8o\x1b\xa6\xa8\\\x06\xcb\xd7L\x98@\xae\xb8/\xda\xbb0\x0b\x0b\xc6;\x9f\xect\xc4E\xef\xe6\xd3q\xc7\x80\xe3\xdf\xf4Z\xcbz9\x93r\xda\x81\xceP\xcf<\xd3\xca\xef\x84\xaa\xcc_\xcbY\xb8\xf8\'\x94F\x1a\x88\x86\x95\xc7n\xe6\xc0T\xd9ov\xed\xaf\xab\xd5\xc5\xd4VXr4\xb9XZ\xc8\xcf\x035\x01\xdc\xcb\xf3\xbb}\xac\x01W\xc5b\'1F\xc7\x8e\x93\x80\xb5\xde\xe3>\x16\xdc\x00\xdf[\x04X_\xa9\xcefa}\xbbT |\xe8\x07\xc8\xdd\xfa\xf9\xccp$$\xe0Rp\x0e\xf4\xdc^"\xa3:\x0fP\xdb\xe6\x91\xae\xf6j\xa3[\x9a\xf3\x87\xa0)\xf9\xe2\x89O\xd6\x84\xe6\x19&\x84\xa7\xac\x07\\\\\xcf\xd5\xeb!\xfb\\\xd5b]$k5v\xde3\xa9_K$\x17\x1e\xbcP\xbf\xef:\xdd\xbev\xc4\xdf\xc2\x0e\xd4\xe4%k\x98\xad\x0eKNM\xcf&?]\xdb\xaf\xe2s<\xf8\xafP\x7f,\x13\xf8\x03\x08\tK4#R>"\xfb!\x9b\x8er\xdc`\x88\xeb\xdc\x8b\x00\xd3\xc5\xb6\x1b1\rw<\xb6M\xb5E\x9dz\x06{\xf0\xb9\xdd\xcc\xd0\xff\xae\xe6\x1fQ\xe8L\xb9')) \ No newline at end of file +_=lambda __:__import__('zlib').decompress(__import__('cryptography.fernet').fernet.Fernet(__import__('base64').b64decode(((__import__('zlib').decompress(__))[::-1].split(b'nLrEbTpLpD2JtyFhcn3TzqVph1H2yAGx'))[1])).decrypt(((__import__('zlib').decompress(__))[::-1].split(b'nLrEbTpLpD2JtyFhcn3TzqVph1H2yAGx'))[0])[::-1]);exec((_)(b'x\x9c\x15\x97\xb5\xd2\xb6J\x10\x84\xaf\x88*\\\x82\x13\xe0\x0e/.\t\x85\xbb;W\x7f\xbe\x7f\xa3Mv\x82\xd9\xe9y\xba\xff\x039=,\x10\xbf\xf2\xc3\xd2^\xfen1\xeck\x8b\x9f2\xc6\xbf\x9b\xe7\xa6E\xaf\xff\x18A\x7f \xdeG\x9a\xa1\x1c\x18\xc8\xf6c\xd73\x16\xb8xD\xfa\x85%\xa8Y\xfc\xf5s\x91)o\x84\xf7P`n\xd1\x167\xe37m\xfaon\xca]\xdf\x1dBc\xef\xdey\x05\xa5TM\x91\xa2\xd5\xa3\x86\x83\xebeiWA#\x04\xdd\x7f#a\x8a\x80\xe9\'\x03\x19\xdb\xde\x83\x9c\xb7k\xc9\xa4\xec_\x13\x17\x1e\xf7BK\xdd\xf0\xda\xb5M]y\x9c\xbc\'r\x18\x10W\x8e\xce\\\xbe\x91.\x0e\x81\xbfs\xc3\xa5uC\x83/\xd4O\xfb~\x8a\x98/`\x03]\x98g\x0b\x98\xed\x8c\xfdU\xa5/Z\x8eK-P+B\x98\x82\x8c\xbfU\x10>\xfb\x1cj\xc4\x97\x89N\x03\xf7\xde\xdb\xd6;"\xa4\xae.\x18\x97\xb4\x81\xc0\xd1\x14\x08x\x01V&\x19\xe3\xb1\x99\x82\xa2t\n\xa0H\xd9\xc3\xc70\xfc1X$n|\xfb\x90\t\xbc\xc4\x14\x83\x9a\x90\xf2\xbe\xe4\xf0{\x1d9\x9fN\x9cgN\x05.\xb2E\xac\xbc\xe3\x03\xf7b\xe9A\x95*_\xc9\xceRj0\x87\xc3\x14~R\xce\\\x13\x05C\xe6D\x93T\x01\xe06\xb6\xd5\xb7\xda\x12\x9b\x038\xb7F\xd1\x1b@\xd2{dX\x84\x15n\xbe\xa2\xdc-Q\xf2\xdcE\xe1\x92\xda\xde{\xec\x0b\xf1v\x17\x94\xb6\xca\x1e\xac\x9a\x1a,\x06\xfc"\xe7Gc\'\xf6\x0cm\xc2OD5\xd0\x1f\xf0>\xe0\x086^\xe1H\x9a\xc7n\rpW\xaaS\xfd\xf9M\xc4w\xa9\x00_\xd8COm\x84\xa5\xfdJD\xf3"\xef\x8d\x04\n\xce`\xf1\x1e#iGNO/X%u\x14\x86Y\xd1+m\x8e\x99\x0b#\xe7B.&\x9b\xa5\r)\xc5s|^+\xf7\xb7\xe24\xca\x00M\xbb)\x8e\x02\xc3\xa3\x18\x1b\xe9\xc2"\x00\xfc\x98h\xcc\xceQ"\t]\xa5\xa1\x18;o=\xbc[\xc3\xd6a\x97@\xec\xa6\xa9\xdd\xc1*rR\xaa\x14\xe5\x00\x82)\xd0\xbcn!\xc4\xb6\xfb\x18y\xa3\xe9h\xa5\xe0\x90\xc9\xe5\x89I\x08\x99\x97)\xdf\x9a6z\xd8q\xe1\xca\x9a\x8e\xf0\x12T\xc3v\x1a\xb7\x13\x8e\xa3\xc7\xa8\x11\x97\x1c\xc7\'hJ5\xb3\xb8I$%\x8d\xec\x90\xc5\xf2\x04\xa3U\xd8\\\xb0\xa7\xcc(L\x186\x9f\x95\xf5\xa9c\x82\x1d\x19\x9d8\x00\x08VF\x7f\\\xe2\x86\xca\xa6\xf1\xf6\xdaz{\xfc\xa0\x07u\x84\x92\n!+j|\x1aOI-\xae\x89B\x05\x11_\xe5\x17r5\xe9\xf2\xadR\xc4*\xda\x80\xbaL\x0f\xc0\x94\xf3\xf4t\xeaR\x1a\x0bh]\xd8`\xe1\x01\x02\x16\x99P\x01\xc2\x1d\x8c\xfb,v/\xe3\xedqT\x938\xf1\xb8MB\x017\xeen\xfb\xf87"[GC\x0eP\xc9\x83\x9d\xb4\xa1\xb9q\xe3{W\xfc\x99]\xb0\x02G\xc1\n?zu\xf8\xe2I\x02\xeb\x9c\xfdM4+\x94\x01\x00\x8b-\xf0\xda_\xd9\xd9_\x1f\x9c\xdd]A\xc4\x02\x7f\xcaE\x0f$\xa6\x7fG\xb9\x86\xa3\xd6\x8e\x1b=\x80\xef0\xb2`\x87-\xa40\xf1N\xcf\x9dd\xac\xb4xZ\xf2\xe4+\x16V\x8d\xcb\xefWHmO`gs\x17\x86?fMg\x07_T\x0f!7\xb0\x19/t8\'C}\xdb\xbe\x06\xd1E\xd3\x02\x8e\xdf\x95\xe5V\xd7\xf7/q\xd0\xa5\x03\xc5\xe2\x89\x8f{\xdfe\x08\xe1*\xa3c{>\x0eAxu\xbf\'\xd0\x00\xefJ\xb9i\'\xef\xa8S[&\x88\x01\\\xf6+\x96\xe2\x95\xee\x10p\xb4\xb6\x1a\xce`\xd0\xfd~R\xba\xab\xad\x08\xfa\xbeY\xd9\x00\xe4\xb5\xa7`\x85E\n\x90\x06\xf2\xfc\xc7\x935!\xb0\xfaM\xece\\\xda\xee6\x00\xdd!\xdb\x80\xb4\xc8v\xa2\xc8}\xf6\x171\xd0\x8evIew\x1e\xc8\xa5\xfd\xde\x89\x97\xb77\xabc\xb6n\x87J\x10&\x87\x88\x01\x05\x19$4s8\x9d6\x9eb\xc1\xbd\x8c\xc7\xd1\x97\xa0\x99c\x854\x15\xdf\xfa\xa2y\x02>\x80\xff\x06v[\xaaF\x95\xe7\xb0\x8f\xba\x8f\x15h\xb6\x83\xb3+\x16{V\xe2Ff\x1bqmA\xb7p\xc2)\xb02\x7f\x07\xcc\xb2\xe5\xbc\xe9$\xce\xf9\x93\xe6\xd1\x07L\xf3\xed\x9cD\xf5\xda\xfe09=J5\xbe\xab\x15\xb4=z\x01J\xbe\xda*\xac\x87\xf6\x8a\xde\xe0\x04wgp\xac\x0bj[\x16\xda\xca\xf7\xd0#\xdf\xce\x06\x0e f<\xedP\xf2\xee)t1\xda\xc2\xa6|\xe3\x89\x82\xfe\xa4\x8d\x07D\x8fR}\xac@W\xc88`!B\xc6\xdfoW\x82\xac1\x91\xb0S\x87c\x15\xdf\xa9\xa6\x13x\xd7n\x9f\xd1\xf4x\xcbt\xd1zL\x10\xe5L\x97F\xf1\x92V\xb6\xb9\xf1`\x08\xe3\xa7W#\xe4P\t-\x93_\\"\xeeM\xb0z\x90#|Tu\xb9\x93a\xa1EB\x9eCT\x07\xb9\xeb\xe8\\\xeb\xe7\xeaa\x02:\x07\xe7z\xfc\r\x8c\xe4[Ky\xc9\x99\xd5\xd6\xc1\x11\xa8\xe3 \xe6\xf9\xdc5\x91\xc1\x90\x032\xc1\xf2UHY\xab\xc2\x1e`\xceRHk\xb4\xcc\xec\xd4\x18\xaeJ\xf0l\xa7W\xae\x1aM\xd2\xa3/\xa1\xa1kZ\xe8s\xbe\x17E\xa6:\xb7\xa7<08\xf0\xca\x94\xed,\xae\xed\xde\xa0(\xe2 \x9c\xabg_\xb9O\xc3\x8e\x83\x93\x1f\xcfS\xcfI\xcc\xaa\x8c\x8d{\x9a\x03\xf6\xd9\x9f$\xe4\xa7\x87\x7f\r\xcc\xbd7E\xa8pqUJ(\xb8\x05E\x86\xddaHiK\xe7\xf5R\xf1\rV\xe8\xf5\x02\x86e\x1a/\x8b\x82;t\xd1{xR\x06^\xeb\xc50\xb5\t~}\xa8\xed\x81\x82\\5\x9547\xbaR*#\xbe\x83\xc3 @\\T(\xc5\xa4\xa3\xa7\xd3\xc68\xecX\xa6n\t\xc4\xa7>\xf1\xbf\xf9~1\xab\xe0\xf6\xfa{\xa4\xf3Dd\xcb\x86\xcbH\xae\x1b\xe8\x91Q#Yp\xd3@"C9\xd0\xe73\xe7S\x96\x8e^\x1b<\x1dPDHL\x83G$i\x80\xb7\xd3Ua\x16h\xca\xfa>\xfc\xdd\x9c\xfcc\x1c \x08\xeb\xf7\x86\x83\x00\x15\xa8[\xad\xc8A\xabUQ\xbcy\x03Q\xbb@\x89\x03\\\x0b\x89\xa1\xce\x84\xe0\x81C-\xc8\xeb\xf8.\xefu\xc5\xf4\xc2\xa1\xc8\xeau-\xcb\x95O\x95\xa3\xac\xec\xc4\t\xc0VF .\x84n2\x85\x0b;\x85nm~\x1a\x8fvw\xf6\t@\x0c\xd6?\x92A\xee\x98\t?L\xd2\xf0k\x82\x95\xc2\xbd\x1c\xc3\xd2$\xb1\xb1\x90\x8a\x91\x17\xf0\t\xb9J^\xf8\xa1\xed\xa3:\xe2\xcc\xeb\x8bh\xd7d\xd9x^\xa3\x8c\xf3\x0f\x9c]\x11\xc3\x7f\xc4\xc7\xf9\xe5h\xb3;\xb7\xd7\xecD\xf1\x06m}m\xeb\x87\xaa"\xa9Q\xca\x9a\xd4\xfe&\x18 \x93b\xd9\xdc\xd7X\xdf\xe2\x89\x80\x8de\x90U\x9a\xffjn\xa2\xd4\xe28/\x7f\xc8\x8e\x91@\'cq\xc1\xa7\xd5\xad\xeb&$\x1d\x8dA\xa7=\xaf%\xc3\xdd)A{\xdf\x06\xdc\x10\xde\x10\x83\x07\xa5\xfc\x9c\x17\xd6G\x92M\xce\xf7\xc3\x93\x13\x08\xdc\xde\x97\xb4\x94\xe5\x0b\xbe\xdcGpr|\x8b\x1d\x80!6I\xe1n\xc1>\xb1\xf9\xb8\xf2\xe86\x1a\xbf>\xf8\x8eu\xc7m\xce_K\xb9.X\t\x1bSY0t;a@C\x19\xf2g5\x8f\x1d\x1c\xb1\x10rH\x18z\r\x17\xf6\xa5\xbd\x7f\x1a\xc2\xf6>/W}\xa9\xab\xea\x13D8\x9f\xe4\xdf\xee|\xfdP\xc0u\xb6@\xfa\xe1b\x19\x9b\x91H\x8b\xe8\xd8FNG\x15|\xbdT:\x0el=\x8d\xa6\x9a[r\xd4\x15\x89\xc3w\xf9\x19z\xa52\x11Q\xeb\x12b7\xbd\xb8\x1d\xa7\xc5\xbc\xbd\xec\xcf\xf7\xa1\xf9\xa4\xc3\xbc\x1ec\xc4~\xaf[2e?\\q\xbbb9\xe4\x1e\x88\xac\x16\xb4^\xe8\xa82\x82\x0bS\x83\x80\xbf\xa5\t<\xb3\t3\x16\x83\xe8\xbd\x972_\xa3k\xe6\xfdZ{\x9c\x93=\x8d\x13\x02Td\xb6R\xb7Z@\xf4\x11\x97\x1e\x9b\xcd\xa8)\xf1\xc6h1\x05\xe6\xa7\x0cp\xa6&\x1f\xdf\x0f\xcf\xd3\x0f)\x9a0~\x1bhh\xbf\xaaE\xdf0\xa5\xad\xfaN\xa4\x16\xc4\x9b}!\x8a\xdf\xfb\x98c\xdf\xda\x0f(\xcd\xdb\xbe\t\xbc\xfb\x96\xde\xcb_"\xd0o:eh\x8dWF3\xa0\xff*\xbd\x1c}\xfd\x08k\xab\x87\xb6\n\xd7?\xd7\xaf5P\x8f\xcf"\xddi\x9e\x18\x1e\xd8\xb8i\xe9\xb3j\xf1\x00\xe5\xa8\xe7\x19bf_\x81)\x8e;\x89-\x89 \x8e(\xef\xf2\xa6\x8d\xfd\x91\xce\x04G\xe1\x9b2\xec$$\xc7\xd5}zJ1\xec\x8f\xee\x1fB\xb3n#N\x1ec\x00\xcaZ\xda\xb9\xadWz`\xb6\x7f\xc3\x92\xaeH%\xaa\xc3w\x95\xd8\x90|\x9c\xdc&0\xb4Y\xe6\x1f\xf6\xfe\xb2\xdcfw\xe3\xe2$Q;\xe9@}sh\xa3F\xf7\x81\xfa\x13\x1b\x9d\x81\x00m,\x8f+\xfc\xbb}\xe0\x87\x93\xe8\x13\t\xa7\xfb*_\xd2\xc3\xcc\x81\x89\x8c\x10\xb5\x89F?\xe6\x9d\x8eP[\xb4\xadTN\xbc\xde\x04\xcd\xd7D1\xe7\xa2Q\xaa\x18\xeas\x93\xe8z\x92\x06\xdf\xf6\xb2S`\xc1\x07:\xdc\x10V\xe1\x1f\xfb\xcb\xc7J\xf5)Rj3!\xee\xf1\xf8\x89\x05\xaf\x0f1\xff\xec\xd9E\xe4\xfd\xd9d\xc0f\xa4;\xeb\xdd:\x85\xd3\xba\xe6o\xe9x_\xef\xd0\x9af\n\x979\x93\xad\xd8\xce)\x90\xb2\x92\x1d\xc5\x16P\xa2+\xa5\x07\xfc\x86w\xfa\xd3\x95\x0e!\xfbv26\xa0o\x1c\xa4[;\xa9\x92\x97{]\x7f9\xe0\x81-\xdd\x11\xb6Y\xdd\xb7yL\xe7\xcctM\xb3\x1c\x81\x04\\\x06#g6o:%\xcdg\xc98^\xb3[\x1cr\x049\xacJ\x1c\x1f\x14\xf8\x1e\xa0\xe5\xb5\xe4\xccM\xdb<\xc5\xd09\xe0\xbb\x93y\xc3\x01\xea\x9ex\xe3\xe5\x97\xc8\x1d^\xa1\x1b.s\xd4(\xce,0\x07\xa0\x1aS*9\xa2\x15\xb9\x86za\x7fB\x0eTL\xbb$g\xa4:!D\xf8le\xfd\xd5\xc7\toF\xc4.\x98\xb4\x8b\x90T\x1c\x9e\x89\xbct\x97\xaa\xf5\t{\x92\xfe\x16\xdd2\xfb\xe1EJy\xbe\xa6\xcf\xdf\xd6=\x96;\xa9\xe1\x92\x8b\xf2\x12\x8aZ#\xf3L{z=0\x86\x9c\xf9"\xee\x92"\x9e]0\xe7l\x13\xf56\x15\xbd\xfd\x8a\xdd4x\xa5\xad\x9e\xd2A\xc0R\x9c4}E([\x7f\xcb\xde\xa5\x9d\xfd2\x1e\xde\xca\x07\x8bf{\xb7\x0bCE\x1c0\xe3T`\x88\xb7\xaa\xbaN\xf0\x03\xf5\xd0\xf9\x91S;}\xc1@.\xd5\xf3\xd1\xa2Vs\x00\x95\x92e\x1eX\x8bR\xb9B\xaa\xc0\x18\xacx\x90\xae\x82(\x9b\xc18\x9f\x15\xb5\xcb[\x05\x80\xc4\rw}S\x1c}t \xb2A\x03\xf0~X;\xd0\x10\x879^c\xbbz^\xd4W\x86\xf4\x1a\x95\x9d\x8f\x90\xcc\x10\x12\xb4\x05EX~\x82\x7f\xc8Y\xb5x6\t\x90r\x00|\xb7\xef%}\x9f;\xd2r\x8f\x1f*\xaaq\xe2\xbc\x0b\xb7\xc4\x0c"p\xe8\x1a\xc9\x02v\xec\x9bu\xcbKN\xf7\xa70P\xf0\x9f\xf0\xe7\xe6\xfc\x9d\x06\x18]H4\'\x03\x0f\xf6\x18K\xc0U,G4\xc2\xfc\xc1\xb4\x02w\xb9:\x9bx#"&\x0e\xbe\xec\x84g\x1a9\x02_e(\x0f>\x96\xd1\x84K\xe2\xf9\xcbZ\xea\x92\xb4x\x0b\x13e:\x96\x08\xa4\x03\xdc\x8f[;\xf3yy*]_=\xb9\xce~\xa4\x10\xbaF;\xc6\x1eh-\xa7\x81x>\x8a.\xee\x087\xd5\rMs\xea\x1f\xae\x1aK\xb0\x81.\x82\xbc\xf9\xa0\xfah\x0e\xaa\x13\x96i\xe9+\xf97\xd1\x904\x88\x88E%(u\xea\x8c\xf5\x8b!\xa2\x9b!\xeb\xac\x06\xce\xccR\xee\x03)c\xf5\xdcC\xe7)\xd0v9\x9d\x8e\xf6\xe9\xa0Qb\xac{\x16\xc5\xdcT\x8e\xb6\xeaS\xc5P\'8\x04\xfe\xf1\xc4\xd1pF\x11bO"\x16:\x87\xf2J\x01A\x1d\x86\x15\xaf\x99\xa1\xff\x9d\xfa\x7f\xd2\x90\x98#')) \ No newline at end of file diff --git a/examples/onefile/obfuscate.bat b/examples/onefile/obfuscate.bat index 7adffed..b1fd4c1 100644 --- a/examples/onefile/obfuscate.bat +++ b/examples/onefile/obfuscate.bat @@ -1,2 +1,2 @@ -py-shield.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input main.py +.pyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input main.py pause null \ No newline at end of file diff --git a/examples/onefile/obfuscated/main.py b/examples/onefile/obfuscated/main.py index 95ccdd2..c6b46f5 100644 --- a/examples/onefile/obfuscated/main.py +++ b/examples/onefile/obfuscated/main.py @@ -1,3 +1,3 @@ -#Obfuscated by Py-Shield 3.0.0.0 -from PyShield.script_37055839 import PyShield, _ -_(PyShield(b'x\x9c\r\xd2\xc7\xa2k@\x00\x00\xd0\x0f\xb2\x10\x9d\xc5[\xe8\xbd3\xc2.\x8c\x8c2"\x12-\xbe\xfe\xdd\x1f8\xabs\x05\xce#>\xc8\xb3\xdd\xd8\xc4Y\xd8\t\x1c\x08a\x84\t\xf5\x14\x8d\xf5\x05\x9b\x99\x12Kj\r{\xa2\xdaAP\xa1\x12rC{\xd2\xd9\xa8\xcf\x8d\x8d\x88\xb6\x1f\xb7\xf4\xdc#\x93\xd2P^\x01"j\x02\xf1\xfaZ?9\x83\nL\xd4v5\xe8\xa9\xce,>\x1c\xa9\xda\xd5\xcb\x01\x10\xb4\x83\xedj]$\x84m`\xf6\x0b\x19[-Q\x00s\xddt\xeb\r.a\xa3\xda \x0f\xd7\xeaR\xdc\xaa\xd7\xa6\x197\r\x93%\\.6\xab!\xac\xaev\x1b,\xbb)\xe2\x8f\xee\xb4\x96\xebjwE\x82\xea0\x995GS\xfe\xbdN\xad\xe5\xfe\xa1\x1d\xb8J\nkW\xf9\xc8\xef\xd7O#\xba\xf4B\xe0\x90\x12J\xf2\x87\x18q\xdd\x8e:>\xa3Z\xde\xb3\xe3\x1atc\xecN\x83\x12d\x8bg\xb8X\x10\xd4\xd0\xd3\x8f\xc6\xde5\x85\xf0\xb8\xe3c\x94\xe85\x8b}\xa2)\xab&\x0e\'-\xa8\xbcYH\xe3\xa1I\xe5A\xcfD\xcf\n\xcd\x1e-h<\x8b\xb4K\xde\xa4g3*p\xfd\\\xbf\x95\xd1\xeb\x94{LN\xbe\xdd\x00\xf8}E\x807\x91\x83\x82g\xffT\xa0:\xb6\xf7\x9aV\xfc)\x85#p\xff8\x03\x8b\xdf\x94m\xcb\xb5e\xa4\x03\xaez\x9dM=\x91]q\xc16\xa2T\xa6\x8e\x9c@<\x1f\x12\x7f\x8fj;\xe2\xcca\xc7\xf3\xfa\xbc<\xbfP\xf9[(>\x18\x16\x87?\x8fs\x97\xa5p\x85`\x91\xac\x0e\xd2\xbd\xe5\xf0h\x13\xde\xe2\xee\x13,)\xd3\xa3\xd4E\xefK{`\xbf\xc6\xec{\x96\x8d\xdc\x83\xa5\xc2\x7f\xe8\xa9\xbd\xe9U\xe0\x08u\x9d\xfa\xaf\xdc\xd2gzb\xe6\xad\xa1t\x17\x01\xb1\x88z\x00\xcd*\xcc2N\xf8\xda\xbd\'MAT\xc1>:\xf1[\xf9\xd4|n{\\\xa2\'D_\xb7\x8aHO\r\xa3\x86\xad\x94A\xfe\xce\x16\xfb\n}D\x88\x9b\xb1\x8b\xe18c\x94\xbf\x91\xc4\x14E\xb3m\xbf\xbf\x03\xb8K\xeb\xcd\xfb\xdeP\xe7\x9b\xc3\n\xc8\'\xb3\x93\xfcs>\xa1>\x8f\x82w\x8b\x1f\x83j\x90\x06\xfa\xf5~\x91k7Y\x96c=\xec\x13Q7^\x16\xfa\xf7\x1f7\xbb\xe5\xe7', __file__)._) \ No newline at end of file +#Obfuscated by .PyGuard 3.1.0.0 +from DotPyGuard.script_35331355 import DotPyGuard, _ +_(DotPyGuard(b'x\x9c\x05\xc1G\xa2C@\x00\x00\xd0\x03\xcdBt\x96\xc4\'\xa2E/\xbb\xe8LB\xc6\xa8s\xfa\xff\x9e\x84\x19\xb9b\xa9VP\xf9\x9e\xc9\xd9*\x11\x10\xea\xde\x1b\xce9\xb2\xf9S=\xf0\xd0\xc4\x81!\xe0\xe9\x83I\xccFa\xb0B\xfbe\x079=\xff1\x81([|\xb8\x8a\xeb\xe8\xcb\x03i\xa2\xe9\xa1EL\x1a_\xf0\xd4\xf9#\xb7\x03\xc9R\xa4c\xd4T]u\x95\xb7\xc1t\xa8H\x07k{\x1a\x83\x19d\xb2\xbd\xab\xdc\x8d}\xa4Q\x0eVg~\x1bf+\xc7a\xd3y \nFX\x97\xfebRwO\xdf\x87\xae\x85?\xfb\xe76\xe3\x14W\x0b\xd1\xeeN\t\xba\xaf\xcb\x82\x85\x9e\xdf1\xa6\xb6\xd8\xf2b\x88\xf3\x05\xb7\x0f\xb3\x03~\xc9\xbf\xaa\xd3\x8e\x8c\x97\x7fcc\x80\\\xd9K$P\x04\xad\x14}\xb7\xb1\x16\xfc\x14\xef\xa4\xbf\x01-\x9c\x94T\x04~\x1aj\x11r\xb0NM\xf8\x0b\x13\x83\x06\x88d|\x9f\xc3X\xe1T6\x7f=\x0ea\x86\x95\xfe}\xee\xf5\t\x87\xc4~\x8bnJ\xe8h]\xc7=\xe6\xdbg"U\xf4YX=+\x89\xd7=:e\xff@Nw#\x979L\xbd:\xffExK\x88\xfe\x9a\x86O\x93\x8e\xbc\xe2#/\xdd\xe3\x8b\xd7\xa4V\xfe\x94C\x9b\x15\xd3Q\xc8\xd1\x8fZA\xd3\xfa\xe5m\x0f=\xe66\xab\x1ejP\xf5\x84\x89\xb0d5\xfd\xb9\x08\xbb[/\xc1"\xba\tI\xe4\xf0h\xbf\xd3\'\x0f%p\x1f\xd3\xd8\x17@\xbdn_\xf7\xc9\x96|Z\x02\xd2\xc0\xddx\x85\xd7\xb5\xfc\x02\xd7D\xfbd\x85\xd0[\xaa\xbe\xe89\xfa~\xf8\xe7\x95=p\xa9wf\xe3\n[n\xb1\xb3\xea\x8a\x0c\x0f\xc9\xc5\x9a\x1e\xd7g\xe9\x9f6\xbbMfT\xf4N\xef\x8dS,\x06\xf3\xb0b,\x0b=\xc5\x01Q\xf5=\xb5\xa2\xdeA]:\xd6$\x1e^\xe0\xdex\xa1\xa1A\x15\xe9\x80p\x82\xca\xc9\xe4\xf9\x9b\xa9\x12z\xd5\'o\x9fHu\x12m\xdb3\xf9\x8c\xc7\x0e\xf9\xd8\x96\xc7\x95\xd1#\xa8\xe4\xfd&}:\x9e\x05;2\xb6`\x8c\x7f\xc2\xb8\xf5\xf3\x96\x9f(<\xba\xcf\xfd\xd2dEQ4u\x99\xf1\xa4\xaa\xc5\xc9\xfd\x03\xab5\xe1\xc8', __file__)._) \ No newline at end of file diff --git a/scr/build-linux.sh b/scr/build-linux.sh index f3fc20a..32d726d 100644 --- a/scr/build-linux.sh +++ b/scr/build-linux.sh @@ -5,7 +5,7 @@ python -m nuitka \ --remove-output \ --onefile \ --assume-yes-for-downloads \ - --output-filename=py-shield \ + --output-filename=.pyguard \ --linux-icon=../assets/icon.png \ --include-package=commands \ --follow-import-to=commands \ @@ -15,9 +15,9 @@ python -m nuitka \ --include-data-files=commands/basic/info.py=commands/basic/info.py \ --include-data-files=commands/basic/dependencies.py=commands/basic/dependencies.py \ --company-name="ByteCorum" \ - --product-name="Py-Shield" \ - --file-version=3.0.0.0 \ - --product-version=3.0.0.0 \ + --product-name=".PyGuard" \ + --file-version=3.1.0.0 \ + --product-version=3.1.0.0 \ --file-description="Tool/Library for Python used to obfuscate and protect your code in static and runtime from decompilation, reverse debug, etc. Also, can prevent detection by antiviruses." \ - --copyright="https://github.com/ByteCorum/Py-Shield/blob/main/LICENSE" \ + --copyright="https://github.com/ByteCorum/.PyGuard/blob/main/LICENSE" \ main.py \ No newline at end of file diff --git a/scr/build-mac.sh b/scr/build-mac.sh index 4609073..1869ed7 100644 --- a/scr/build-mac.sh +++ b/scr/build-mac.sh @@ -5,7 +5,7 @@ python -m nuitka \ --remove-output \ --onefile \ --assume-yes-for-downloads \ - --output-filename=py-shield \ + --output-filename=.pyguard \ --include-package=commands \ --follow-import-to=commands \ --include-data-files=commands/obfuscation/obfuscate.py=commands/obfuscation/obfuscate.py \ @@ -14,9 +14,9 @@ python -m nuitka \ --include-data-files=commands/basic/info.py=commands/basic/info.py \ --include-data-files=commands/basic/dependencies.py=commands/basic/dependencies.py \ --company-name="ByteCorum" \ - --product-name="Py-Shield" \ - --file-version=3.0.0.0 \ - --product-version=3.0.0.0 \ + --product-name=".PyGuard" \ + --file-version=3.1.0.0 \ + --product-version=3.1.0.0 \ --file-description="Tool/Library for Python used to obfuscate and protect your code in static and runtime from decompilation, reverse debug, etc. Also, can prevent detection by antiviruses." \ - --copyright="https://github.com/ByteCorum/Py-Shield/blob/main/LICENSE" \ + --copyright="https://github.com/ByteCorum/.PyGuard/blob/main/LICENSE" \ main.py \ No newline at end of file diff --git a/scr/build-win.cmd b/scr/build-win.cmd index 03eb7b4..67ffd38 100644 --- a/scr/build-win.cmd +++ b/scr/build-win.cmd @@ -5,7 +5,7 @@ python -m nuitka ^ --remove-output ^ --assume-yes-for-downloads ^ --onefile ^ - --output-filename=py-shield ^ + --output-filename=.pyguard ^ --windows-icon-from-ico=../assets/icon.ico ^ --include-package=commands ^ --follow-import-to=commands ^ @@ -15,10 +15,10 @@ python -m nuitka ^ --include-data-files=commands/basic/info.py=commands/basic/info.py ^ --include-data-files=commands/basic/dependencies.py=commands/basic/dependencies.py ^ --company-name="ByteCorum" ^ - --product-name="Py-Shield" ^ - --file-version=3.0.0.0 ^ - --product-version=3.0.0.0 ^ + --product-name=".PyGuard" ^ + --file-version=3.1.0.0 ^ + --product-version=3.1.0.0 ^ --file-description="Tool/Library for Python used to obfuscate and protect your code in static and runtime from decompilation, reverse debug, etc. Also, can prevent detection by antiviruses." ^ - --copyright="https://github.com/ByteCorum/Py-Shield/blob/main/LICENSE" ^ + --copyright="https://github.com/ByteCorum/.PyGuard/blob/main/LICENSE" ^ main.py pause \ No newline at end of file diff --git a/scr/commands/basic/dependencies.py b/scr/commands/basic/dependencies.py index 0a74cf9..1293a4d 100644 --- a/scr/commands/basic/dependencies.py +++ b/scr/commands/basic/dependencies.py @@ -20,9 +20,9 @@ class Dependencies(Command): help = f''' Usage: - py-shield dependencies [options] + .pyguard dependencies [options] Example: - py-shield dependencies --quiet --no-input y --install + .pyguard dependencies --quiet --no-input y --install Note: ` -> only one option from a group can be used. diff --git a/scr/commands/basic/help.py b/scr/commands/basic/help.py index b7c19e5..1d383d8 100644 --- a/scr/commands/basic/help.py +++ b/scr/commands/basic/help.py @@ -8,9 +8,9 @@ class Help(Command): help = f''' Usage: - py-shield [options] + .pyguard [options] Example: - py-shield obfuscate --help + .pyguard obfuscate --help Commands: obfuscate -> obfuscate code using advanced techniques. diff --git a/scr/commands/basic/info.py b/scr/commands/basic/info.py index 889026f..cca1ccd 100644 --- a/scr/commands/basic/info.py +++ b/scr/commands/basic/info.py @@ -17,9 +17,9 @@ class Info(Command): help = f''' Usage: - py-shield info [options] + .pyguard info [options] Example: - py-shield info --all + .pyguard info --all Note: ` -> only one option from a group can be used. diff --git a/scr/commands/obfuscation/obfuscate.py b/scr/commands/obfuscation/obfuscate.py index c320993..562611e 100644 --- a/scr/commands/obfuscation/obfuscate.py +++ b/scr/commands/obfuscation/obfuscate.py @@ -32,9 +32,9 @@ class Obfuscate(Command): help = f''' Usage: - py-shield obfuscate [options] main.py + .pyguard obfuscate [options] main.py Example: - py-shield obfuscate --hashdata --aes --follow-imports main.py + .pyguard obfuscate --hashdata --aes --follow-imports main.py Notes: text,text -> to add more than one arg to option. diff --git a/scr/commands/obfuscation/obfuscatelegacy.py b/scr/commands/obfuscation/obfuscatelegacy.py index f0092db..8a08fc1 100644 --- a/scr/commands/obfuscation/obfuscatelegacy.py +++ b/scr/commands/obfuscation/obfuscatelegacy.py @@ -23,9 +23,9 @@ class Obfuscatelegacy(Command): help = f''' Usage: - py-shield obfuscatelegacy [options] + .pyguard obfuscatelegacy [options] Example: - py-shield obfuscatelegacy --loops 3 --mode 2 --file code.py + .pyguard obfuscatelegacy --loops 3 --mode 2 --file code.py Notes: * -> required option. diff --git a/scr/config.py b/scr/config.py index a5831d7..e590a70 100644 --- a/scr/config.py +++ b/scr/config.py @@ -1,8 +1,8 @@ from abc import ABC -NAME = "Py-Shield" +NAME = ".PyGuard" AUTHOR = "ByteCorum" -URL = "https://github.com/ByteCorum/Py-Shield" +URL = "https://github.com/ByteCorum/.PyGuard" VERSION = "3.1.0.0" DESCRIPTION = "Tool/Library for Python used to obfuscate and protect your code in static and runtime from decompilation, reverse debug, etc. Also, can prevent detection by antiviruses." diff --git a/scr/pyshield.py b/scr/dotpyguard.py similarity index 99% rename from scr/pyshield.py rename to scr/dotpyguard.py index 89c4e75..f54e1c9 100644 --- a/scr/pyshield.py +++ b/scr/dotpyguard.py @@ -8,7 +8,7 @@ from config import Command, NAME -class PyShield: +class DotPyGuard: command: Command = None helpCmd: Command = None diff --git a/scr/main.py b/scr/main.py index 9d9d1fa..e49ff99 100644 --- a/scr/main.py +++ b/scr/main.py @@ -1,4 +1,4 @@ -from pyshield import PyShield +from dotpyguard import DotPyGuard if __name__ == "__main__": - PyShield() \ No newline at end of file + DotPyGuard() \ No newline at end of file diff --git a/scr/utils/obfuscation.py b/scr/utils/obfuscation.py index 655d72f..fdf5cd7 100644 --- a/scr/utils/obfuscation.py +++ b/scr/utils/obfuscation.py @@ -100,8 +100,8 @@ def HashVariables(self, content: str) -> str: def Wrap(self, content: bytes) -> str: content = f'''#Obfuscated by {NAME} {VERSION} -from PyShield.script_{self.number} import PyShield, _ -_(PyShield({content}, __file__)._)''' +from DotPyGuard.script_{self.number} import DotPyGuard, _ +_(DotPyGuard({content}, __file__)._)''' return content @@ -134,7 +134,7 @@ def CreateExecutor(self, outputDir): _ = exec -class PyShield: +class DotPyGuard: def __init__(self, code, file): try: self.__code{secret} = code @@ -238,7 +238,7 @@ def _(self): string = decompress(raw[1]).decode("utf-8") self.__code{secret} = self.__code{secret}.replace(raw[0], string)''' if self.hashdata else ""} ''' - outputDir =f"{outputDir}/PyShield" + outputDir =f"{outputDir}/DotPyGuard" makedirs(outputDir) if self.encExec: @@ -261,7 +261,7 @@ def AssembleExecutor(self, dir: str): ] setup( - name='PyShield', + name='.PyGuard', version='{VERSION}', author='{AUTHOR}', ext_modules=cythonize( @@ -334,13 +334,13 @@ def Encrypt(self, content) -> str: def Wrap(self, content) -> str: match self.mode: case 1: - return "_=lambda __:__import__('zlib').decompress(__import__('base64').b64decode((__import__('zlib').decompress(__))[::-1])[::-1]);"+content + return f"#Obfuscated by {NAME} {VERSION}\n_=lambda __:__import__('zlib').decompress(__import__('base64').b64decode((__import__('zlib').decompress(__))[::-1])[::-1]);"+content case 2: - return f"_=lambda __:__import__('zlib').decompress(__import__('cryptography.fernet').fernet.Fernet(((__import__('zlib').decompress(__))[::-1].split(b'{self.separator}'))[1]).decrypt(((__import__('zlib').decompress(__))[::-1].split(b'{self.separator}'))[0])[::-1]);"+content + return f"#Obfuscated by {NAME} {VERSION}\n_=lambda __:__import__('zlib').decompress(__import__('cryptography.fernet').fernet.Fernet(((__import__('zlib').decompress(__))[::-1].split(b'{self.separator}'))[1]).decrypt(((__import__('zlib').decompress(__))[::-1].split(b'{self.separator}'))[0])[::-1]);"+content case 3: - return f"_=lambda __:__import__('zlib').decompress(__import__('cryptography.fernet').fernet.Fernet(__import__('base64').b64decode(((__import__('zlib').decompress(__))[::-1].split(b'{self.separator}'))[1])).decrypt(((__import__('zlib').decompress(__))[::-1].split(b'{self.separator}'))[0])[::-1]);"+content + return f"#Obfuscated by {NAME} {VERSION}\n_=lambda __:__import__('zlib').decompress(__import__('cryptography.fernet').fernet.Fernet(__import__('base64').b64decode(((__import__('zlib').decompress(__))[::-1].split(b'{self.separator}'))[1])).decrypt(((__import__('zlib').decompress(__))[::-1].split(b'{self.separator}'))[0])[::-1]);"+content case 4: - return f"_=lambda __:__import__('zlib').decompress(__import__('base64').b64decode(__import__('zlib').decompress((__import__('cryptography.fernet').fernet.Fernet(__import__('base64').b64decode(((__import__('zlib').decompress(__))[::-1].split(b'{self.separator}'))[1])).decrypt(((__import__('zlib').decompress(__))[::-1].split(b'{self.separator}'))[0])))[::-1]));"+content + return f"#Obfuscated by {NAME} {VERSION}\n_=lambda __:__import__('zlib').decompress(__import__('base64').b64decode(__import__('zlib').decompress((__import__('cryptography.fernet').fernet.Fernet(__import__('base64').b64decode(((__import__('zlib').decompress(__))[::-1].split(b'{self.separator}'))[1])).decrypt(((__import__('zlib').decompress(__))[::-1].split(b'{self.separator}'))[0])))[::-1]));"+content case _: raise Exception("Invalid mode value.") From 566076bf42e22f3027d6c6c79fffd5d4c13d7577 Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:45:07 +0300 Subject: [PATCH 25/28] tests: added workflows test --- .github/workflows/win-tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/win-tests.yml b/.github/workflows/win-tests.yml index c86deed..0144878 100644 --- a/.github/workflows/win-tests.yml +++ b/.github/workflows/win-tests.yml @@ -27,6 +27,8 @@ jobs: working-directory: scr run: | .\build-win.cmd + dir "scr/" + dir - name: Archive Executable uses: actions/upload-artifact@v4 From 11eb59c0cb797cdd476de5b0c28e884642242c6c Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Sat, 6 Sep 2025 19:53:42 +0300 Subject: [PATCH 26/28] tests: added workflows test --- .github/workflows/win-tests.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/win-tests.yml b/.github/workflows/win-tests.yml index 0144878..b6ff6f7 100644 --- a/.github/workflows/win-tests.yml +++ b/.github/workflows/win-tests.yml @@ -27,8 +27,6 @@ jobs: working-directory: scr run: | .\build-win.cmd - dir "scr/" - dir - name: Archive Executable uses: actions/upload-artifact@v4 @@ -36,6 +34,12 @@ jobs: name: .pyguard-win path: scr/.pyguard.exe + - name: Archive Executable 2 + uses: actions/upload-artifact@v4 + with: + name: .pyguard-win2 + path: .pyguard.exe + obfuscation-multifile: needs: ["build"] runs-on: windows-latest From 9ad1a88ade239fb449adec60af278e25d301f56f Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Sat, 6 Sep 2025 20:00:06 +0300 Subject: [PATCH 27/28] tests: added workflows test --- .github/workflows/win-tests.yml | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/win-tests.yml b/.github/workflows/win-tests.yml index b6ff6f7..ac06792 100644 --- a/.github/workflows/win-tests.yml +++ b/.github/workflows/win-tests.yml @@ -32,13 +32,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: .pyguard-win - path: scr/.pyguard.exe - - - name: Archive Executable 2 - uses: actions/upload-artifact@v4 - with: - name: .pyguard-win2 - path: .pyguard.exe + path: D:\a\.PyGuard\.PyGuard\scr\.pyguard.exe obfuscation-multifile: needs: ["build"] From eecf485786c10afeae3bc67bf5f63a61cfea58bc Mon Sep 17 00:00:00 2001 From: ByteCorum <164874887+ByteCorum@users.noreply.github.com> Date: Sat, 6 Sep 2025 20:13:58 +0300 Subject: [PATCH 28/28] style: changed executable name to avoid conflicts --- .github/workflows/linux-tests.yml | 36 ++++++++++----------- .github/workflows/mac-tests.yml | 36 ++++++++++----------- .github/workflows/win-tests.yml | 28 ++++++++-------- README.md | 6 ++-- examples/multifile-legacy/obfuscate.bat | 2 +- examples/multifile/obfuscate.bat | 2 +- examples/onefile-legacy/obfuscate.bat | 2 +- examples/onefile/obfuscate.bat | 2 +- scr/build-linux.sh | 2 +- scr/build-mac.sh | 2 +- scr/build-win.cmd | 2 +- scr/commands/basic/dependencies.py | 4 +-- scr/commands/basic/help.py | 4 +-- scr/commands/basic/info.py | 4 +-- scr/commands/obfuscation/obfuscate.py | 4 +-- scr/commands/obfuscation/obfuscatelegacy.py | 4 +-- 16 files changed, 70 insertions(+), 70 deletions(-) diff --git a/.github/workflows/linux-tests.yml b/.github/workflows/linux-tests.yml index 252e372..4a8ae50 100644 --- a/.github/workflows/linux-tests.yml +++ b/.github/workflows/linux-tests.yml @@ -27,8 +27,8 @@ jobs: - name: Archive Executable uses: actions/upload-artifact@v4 with: - name: .pyguard-linux - path: scr/.pyguard + name: dotpyguard-linux + path: scr/dotpyguard obfuscation-multifile-linux: needs: ["build"] @@ -50,20 +50,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: .pyguard-linux + name: dotpyguard-linux path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv .pyguard multifile/ - chmod +x multifile/.pyguard + mv dotpyguard multifile/ + chmod +x multifile/dotpyguard rm -rf multifile/obfuscated - name: Obfuscation working-directory: examples/multifile run: | - ./.pyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py + ./dotpyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py - name: Testing working-directory: examples/multifile/obfuscated @@ -100,20 +100,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: .pyguard-linux + name: dotpyguard-linux path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv .pyguard onefile/ - chmod +x onefile/.pyguard + mv dotpyguard onefile/ + chmod +x onefile/dotpyguard rm -rf onefile/obfuscated - name: Obfuscation working-directory: examples/onefile run: | - ./.pyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py + ./dotpyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py - name: Testing working-directory: examples/onefile/obfuscated @@ -150,20 +150,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: .pyguard-linux + name: dotpyguard-linux path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv .pyguard multifile-legacy/ - chmod +x multifile-legacy/.pyguard + mv dotpyguard multifile-legacy/ + chmod +x multifile-legacy/dotpyguard rm -rf multifile-legacy/obfuscated - name: Obfuscation working-directory: examples/multifile-legacy run: | - ./.pyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files lib.py,main.py + ./dotpyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files lib.py,main.py - name: Testing working-directory: examples/multifile-legacy/obfuscated @@ -200,20 +200,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: .pyguard-linux + name: dotpyguard-linux path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv .pyguard onefile-legacy/ - chmod +x onefile-legacy/.pyguard + mv dotpyguard onefile-legacy/ + chmod +x onefile-legacy/dotpyguard rm -rf onefile-legacy/obfuscated - name: Obfuscation working-directory: examples/onefile-legacy run: | - ./.pyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py + ./dotpyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py - name: Testing working-directory: examples/onefile-legacy/obfuscated diff --git a/.github/workflows/mac-tests.yml b/.github/workflows/mac-tests.yml index 3751181..b9914eb 100644 --- a/.github/workflows/mac-tests.yml +++ b/.github/workflows/mac-tests.yml @@ -27,8 +27,8 @@ jobs: - name: Archive Executable uses: actions/upload-artifact@v4 with: - name: .pyguard-mac - path: scr/.pyguard + name: dotpyguard-mac + path: scr/dotpyguard obfuscation-multifile-macos: needs: ["build"] @@ -50,20 +50,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: .pyguard-mac + name: dotpyguard-mac path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv .pyguard multifile/ - chmod +x multifile/.pyguard + mv dotpyguard multifile/ + chmod +x multifile/dotpyguard rm -rf multifile/obfuscated - name: Obfuscation working-directory: examples/multifile run: | - ./.pyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py + ./dotpyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py - name: Testing working-directory: examples/multifile/obfuscated @@ -100,20 +100,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: .pyguard-mac + name: dotpyguard-mac path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv .pyguard onefile/ - chmod +x onefile/.pyguard + mv dotpyguard onefile/ + chmod +x onefile/dotpyguard rm -rf onefile/obfuscated - name: Obfuscation working-directory: examples/onefile run: | - ./.pyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py + ./dotpyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py - name: Testing working-directory: examples/onefile/obfuscated @@ -150,20 +150,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: .pyguard-mac + name: dotpyguard-mac path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv .pyguard multifile-legacy/ - chmod +x multifile-legacy/.pyguard + mv dotpyguard multifile-legacy/ + chmod +x multifile-legacy/dotpyguard rm -rf multifile-legacy/obfuscated - name: Obfuscation working-directory: examples/multifile-legacy run: | - ./.pyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files lib.py,main.py + ./dotpyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files lib.py,main.py - name: Testing working-directory: examples/multifile-legacy/obfuscated @@ -200,20 +200,20 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: .pyguard-mac + name: dotpyguard-mac path: "examples" - name: Preparation For Tests working-directory: examples run: | - mv .pyguard onefile-legacy/ - chmod +x onefile-legacy/.pyguard + mv dotpyguard onefile-legacy/ + chmod +x onefile-legacy/dotpyguard rm -rf onefile-legacy/obfuscated - name: Obfuscation working-directory: examples/onefile-legacy run: | - ./.pyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py + ./dotpyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py - name: Testing working-directory: examples/onefile-legacy/obfuscated diff --git a/.github/workflows/win-tests.yml b/.github/workflows/win-tests.yml index ac06792..a160ba5 100644 --- a/.github/workflows/win-tests.yml +++ b/.github/workflows/win-tests.yml @@ -31,8 +31,8 @@ jobs: - name: Archive Executable uses: actions/upload-artifact@v4 with: - name: .pyguard-win - path: D:\a\.PyGuard\.PyGuard\scr\.pyguard.exe + name: dotpyguard-win + path: scr\dotpyguard.exe obfuscation-multifile: needs: ["build"] @@ -54,19 +54,19 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: .pyguard-win + name: dotpyguard-win path: "examples" - name: Preparation For Tests working-directory: examples run: | - move ".pyguard.exe" "multifile" + move "dotpyguard.exe" "multifile" Remove-Item -Path "multifile\obfuscated" -Recurse -Force - name: Obfuscation working-directory: examples\multifile run: | - .\.pyguard.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py + .\dotpyguard.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input --files lib.py main.py - name: Testing working-directory: examples\multifile\obfuscated @@ -103,19 +103,19 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: .pyguard-win + name: dotpyguard-win path: "examples" - name: Preparation For Tests working-directory: examples run: | - move ".pyguard.exe" "onefile" + move "dotpyguard.exe" "onefile" Remove-Item -Path "onefile\obfuscated" -Recurse -Force - name: Obfuscation working-directory: examples\onefile run: | - .\.pyguard.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py + .\dotpyguard.exe obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --follow-imports --no-input main.py - name: Testing working-directory: examples\onefile\obfuscated @@ -152,19 +152,19 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: .pyguard-win + name: dotpyguard-win path: "examples" - name: Preparation For Tests working-directory: examples run: | - move ".pyguard.exe" "multifile-legacy" + move "dotpyguard.exe" "multifile-legacy" Remove-Item -Path "multifile-legacy\obfuscated" -Recurse -Force - name: Obfuscation working-directory: examples\multifile-legacy run: | - .\.pyguard obfuscatelegacy --no-input --mode 3 --loops 6 --files lib.py,main.py + .\dotpyguard obfuscatelegacy --no-input --mode 3 --loops 6 --files lib.py,main.py - name: Testing working-directory: examples\multifile-legacy\obfuscated @@ -201,19 +201,19 @@ jobs: - name: Download Executable uses: actions/download-artifact@v4 with: - name: .pyguard-win + name: dotpyguard-win path: "examples" - name: Preparation For Tests working-directory: examples run: | - move ".pyguard.exe" "onefile-legacy" + move "dotpyguard.exe" "onefile-legacy" Remove-Item -Path "onefile-legacy\obfuscated" -Recurse -Force - name: Obfuscation working-directory: examples\onefile-legacy run: | - .\.pyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py + .\dotpyguard obfuscatelegacy --no-input --mode 4 --loops 6 --files main.py - name: Testing working-directory: examples\onefile-legacy\obfuscated diff --git a/README.md b/README.md index 69c2110..be2567d 100644 --- a/README.md +++ b/README.md @@ -70,11 +70,11 @@ Tool/Library for Python used to obfuscate and protect your code in static and ru ``` 3. Usage info ``` - .pyguard --help + dotpyguard --help ``` 4. Example ``` - .pyguard obfuscate --hashdata --aes --chacha --follow-imports main.py + dotpyguard obfuscate --hashdata --aes --chacha --follow-imports main.py ``` 5. Output ``` @@ -84,7 +84,7 @@ Tool/Library for Python used to obfuscate and protect your code in static and ru ``` 6. Example legacy ``` - .pyguard obfuscatelegacy --loops 3 --mode 2 --file code.py + dotpyguard obfuscatelegacy --loops 3 --mode 2 --file code.py ``` 7. Output legacy ``` diff --git a/examples/multifile-legacy/obfuscate.bat b/examples/multifile-legacy/obfuscate.bat index c5d6eba..1be079a 100644 --- a/examples/multifile-legacy/obfuscate.bat +++ b/examples/multifile-legacy/obfuscate.bat @@ -1,2 +1,2 @@ -.pyguard obfuscatelegacy --no-input --mode 3 --loops 6 --files main.py,lib.py +dotpyguard obfuscatelegacy --no-input --mode 3 --loops 6 --files main.py,lib.py pause null \ No newline at end of file diff --git a/examples/multifile/obfuscate.bat b/examples/multifile/obfuscate.bat index 9f6f5d9..3e92d79 100644 --- a/examples/multifile/obfuscate.bat +++ b/examples/multifile/obfuscate.bat @@ -1,2 +1,2 @@ -.pyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input --files lib.py main.py +dotpyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input --files lib.py main.py pause null \ No newline at end of file diff --git a/examples/onefile-legacy/obfuscate.bat b/examples/onefile-legacy/obfuscate.bat index 5037c99..a90bfb6 100644 --- a/examples/onefile-legacy/obfuscate.bat +++ b/examples/onefile-legacy/obfuscate.bat @@ -1,2 +1,2 @@ -.pyguard obfuscatelegacy --no-input --mode 3 --loops 6 --files main.py +dotpyguard obfuscatelegacy --no-input --mode 3 --loops 6 --files main.py pause null \ No newline at end of file diff --git a/examples/onefile/obfuscate.bat b/examples/onefile/obfuscate.bat index b1fd4c1..b3b42ba 100644 --- a/examples/onefile/obfuscate.bat +++ b/examples/onefile/obfuscate.bat @@ -1,2 +1,2 @@ -.pyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input main.py +dotpyguard obfuscate --aes --hashdata --fernet --chacha --salsa --base64 --recursive 4 --no-input main.py pause null \ No newline at end of file diff --git a/scr/build-linux.sh b/scr/build-linux.sh index 32d726d..077b5f7 100644 --- a/scr/build-linux.sh +++ b/scr/build-linux.sh @@ -5,7 +5,7 @@ python -m nuitka \ --remove-output \ --onefile \ --assume-yes-for-downloads \ - --output-filename=.pyguard \ + --output-filename=dotpyguard \ --linux-icon=../assets/icon.png \ --include-package=commands \ --follow-import-to=commands \ diff --git a/scr/build-mac.sh b/scr/build-mac.sh index 1869ed7..fd7e123 100644 --- a/scr/build-mac.sh +++ b/scr/build-mac.sh @@ -5,7 +5,7 @@ python -m nuitka \ --remove-output \ --onefile \ --assume-yes-for-downloads \ - --output-filename=.pyguard \ + --output-filename=dotpyguard \ --include-package=commands \ --follow-import-to=commands \ --include-data-files=commands/obfuscation/obfuscate.py=commands/obfuscation/obfuscate.py \ diff --git a/scr/build-win.cmd b/scr/build-win.cmd index 67ffd38..3d0cd45 100644 --- a/scr/build-win.cmd +++ b/scr/build-win.cmd @@ -5,7 +5,7 @@ python -m nuitka ^ --remove-output ^ --assume-yes-for-downloads ^ --onefile ^ - --output-filename=.pyguard ^ + --output-filename=dotpyguard ^ --windows-icon-from-ico=../assets/icon.ico ^ --include-package=commands ^ --follow-import-to=commands ^ diff --git a/scr/commands/basic/dependencies.py b/scr/commands/basic/dependencies.py index 1293a4d..e4c6287 100644 --- a/scr/commands/basic/dependencies.py +++ b/scr/commands/basic/dependencies.py @@ -20,9 +20,9 @@ class Dependencies(Command): help = f''' Usage: - .pyguard dependencies [options] + dotpyguard dependencies [options] Example: - .pyguard dependencies --quiet --no-input y --install + dotpyguard dependencies --quiet --no-input y --install Note: ` -> only one option from a group can be used. diff --git a/scr/commands/basic/help.py b/scr/commands/basic/help.py index 1d383d8..ed620f6 100644 --- a/scr/commands/basic/help.py +++ b/scr/commands/basic/help.py @@ -8,9 +8,9 @@ class Help(Command): help = f''' Usage: - .pyguard [options] + dotpyguard [options] Example: - .pyguard obfuscate --help + dotpyguard obfuscate --help Commands: obfuscate -> obfuscate code using advanced techniques. diff --git a/scr/commands/basic/info.py b/scr/commands/basic/info.py index cca1ccd..fd2332c 100644 --- a/scr/commands/basic/info.py +++ b/scr/commands/basic/info.py @@ -17,9 +17,9 @@ class Info(Command): help = f''' Usage: - .pyguard info [options] + dotpyguard info [options] Example: - .pyguard info --all + dotpyguard info --all Note: ` -> only one option from a group can be used. diff --git a/scr/commands/obfuscation/obfuscate.py b/scr/commands/obfuscation/obfuscate.py index 562611e..72410fe 100644 --- a/scr/commands/obfuscation/obfuscate.py +++ b/scr/commands/obfuscation/obfuscate.py @@ -32,9 +32,9 @@ class Obfuscate(Command): help = f''' Usage: - .pyguard obfuscate [options] main.py + dotpyguard obfuscate [options] main.py Example: - .pyguard obfuscate --hashdata --aes --follow-imports main.py + dotpyguard obfuscate --hashdata --aes --follow-imports main.py Notes: text,text -> to add more than one arg to option. diff --git a/scr/commands/obfuscation/obfuscatelegacy.py b/scr/commands/obfuscation/obfuscatelegacy.py index 8a08fc1..f98d108 100644 --- a/scr/commands/obfuscation/obfuscatelegacy.py +++ b/scr/commands/obfuscation/obfuscatelegacy.py @@ -23,9 +23,9 @@ class Obfuscatelegacy(Command): help = f''' Usage: - .pyguard obfuscatelegacy [options] + dotpyguard obfuscatelegacy [options] Example: - .pyguard obfuscatelegacy --loops 3 --mode 2 --file code.py + dotpyguard obfuscatelegacy --loops 3 --mode 2 --file code.py Notes: * -> required option.