maya-umbrella 0.9.0__py2.py3-none-any.whl → 0.11.0__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of maya-umbrella might be problematic. Click here for more details.
- maya_umbrella/__init__.py +10 -1
- maya_umbrella/__version__.py +1 -1
- maya_umbrella/_vendor/__init__.py +0 -0
- maya_umbrella/_vendor/six/__init__.pyi +1 -0
- maya_umbrella/_vendor/six/moves/__init__.pyi +1 -0
- maya_umbrella/_vendor/six/moves/configparser.pyi +1 -0
- maya_umbrella/_vendor/six.LICENSE +18 -0
- maya_umbrella/_vendor/six.py +998 -0
- maya_umbrella/_vendor/vendor.txt +1 -0
- maya_umbrella/cleaner.py +1 -1
- maya_umbrella/constants.py +0 -17
- maya_umbrella/defender.py +19 -5
- maya_umbrella/filesystem.py +38 -32
- maya_umbrella/hooks/delete_unknown_plugin_node.py +12 -12
- maya_umbrella/i18n.py +2 -2
- maya_umbrella/log.py +6 -4
- maya_umbrella/maya_funs.py +1 -1
- maya_umbrella/scanner.py +14 -4
- maya_umbrella/signatures.py +14 -0
- maya_umbrella/vaccines/vaccine2.py +6 -4
- maya_umbrella/vaccines/vaccine3.py +28 -18
- {maya_umbrella-0.9.0.dist-info → maya_umbrella-0.11.0.dist-info}/METADATA +42 -14
- maya_umbrella-0.11.0.dist-info/RECORD +35 -0
- maya_umbrella-0.9.0.dist-info/RECORD +0 -28
- {maya_umbrella-0.9.0.dist-info → maya_umbrella-0.11.0.dist-info}/LICENSE +0 -0
- {maya_umbrella-0.9.0.dist-info → maya_umbrella-0.11.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
six==1.16.0
|
maya_umbrella/cleaner.py
CHANGED
|
@@ -5,13 +5,13 @@ import os
|
|
|
5
5
|
import re
|
|
6
6
|
|
|
7
7
|
# Import local modules
|
|
8
|
-
from maya_umbrella.constants import FILE_VIRUS_SIGNATURES
|
|
9
8
|
from maya_umbrella.filesystem import remove_virus_file_by_signature
|
|
10
9
|
from maya_umbrella.filesystem import safe_remove_file
|
|
11
10
|
from maya_umbrella.filesystem import safe_rmtree
|
|
12
11
|
from maya_umbrella.i18n import Translator
|
|
13
12
|
from maya_umbrella.maya_funs import check_reference_node_exists
|
|
14
13
|
from maya_umbrella.maya_funs import cmds
|
|
14
|
+
from maya_umbrella.signatures import FILE_VIRUS_SIGNATURES
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
class MayaVirusCleaner(object):
|
maya_umbrella/constants.py
CHANGED
|
@@ -3,20 +3,3 @@ PACKAGE_NAME = "maya_umbrella"
|
|
|
3
3
|
LOG_FORMAT = "%(asctime)s [%(levelname)s] %(name)s: %(message)s"
|
|
4
4
|
|
|
5
5
|
LOG_MAX_BYTES = 1024 * 1024 * 5
|
|
6
|
-
|
|
7
|
-
FILE_VIRUS_SIGNATURES = [
|
|
8
|
-
"import vaccine",
|
|
9
|
-
"cmds.evalDeferred.*leukocyte.+",
|
|
10
|
-
# https://regex101.com/r/0MNzF7/1
|
|
11
|
-
"python(.*);.+exec.+(pyCode).+;",
|
|
12
|
-
]
|
|
13
|
-
|
|
14
|
-
JOB_SCRIPTS_VIRUS_SIGNATURES = [
|
|
15
|
-
"petri_dish_path.+cmds.internalVar.+",
|
|
16
|
-
"userSetup",
|
|
17
|
-
"fuckVirus",
|
|
18
|
-
# https://regex101.com/r/0MNzF7/1
|
|
19
|
-
"python(.*);.+exec.+(pyCode).+;",
|
|
20
|
-
# https://regex101.com/r/2D14UA/1
|
|
21
|
-
r"^\['.+']",
|
|
22
|
-
]
|
maya_umbrella/defender.py
CHANGED
|
@@ -15,6 +15,7 @@ from maya_umbrella.maya_funs import om
|
|
|
15
15
|
|
|
16
16
|
# Global list to store IDs of Maya callbacks
|
|
17
17
|
MAYA_UMBRELLA_CALLBACK_IDS = []
|
|
18
|
+
MAYA_UMBRELLA_DEFENDER = None
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def _add_callbacks_id(id_):
|
|
@@ -115,10 +116,11 @@ class MayaVirusDefender(object):
|
|
|
115
116
|
|
|
116
117
|
def stop(self):
|
|
117
118
|
"""Stop the MayaVirusDefender."""
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
119
|
+
while MAYA_UMBRELLA_CALLBACK_IDS:
|
|
120
|
+
for ids in MAYA_UMBRELLA_CALLBACK_IDS:
|
|
121
|
+
self.logger.debug("remove callback. %s", ids)
|
|
122
|
+
om.MSceneMessage.removeCallback(ids)
|
|
123
|
+
MAYA_UMBRELLA_CALLBACK_IDS.remove(ids)
|
|
122
124
|
|
|
123
125
|
def get_unfixed_references(self):
|
|
124
126
|
"""Get the list of unfixed reference files.
|
|
@@ -155,7 +157,19 @@ def context_defender():
|
|
|
155
157
|
Yields:
|
|
156
158
|
MayaVirusDefender: An instance of MayaVirusDefender.
|
|
157
159
|
"""
|
|
158
|
-
defender =
|
|
160
|
+
defender = get_defender_instance()
|
|
159
161
|
defender.stop()
|
|
160
162
|
yield defender
|
|
161
163
|
defender.setup()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def get_defender_instance():
|
|
167
|
+
"""Get the MayaVirusDefender instance.
|
|
168
|
+
|
|
169
|
+
Returns:
|
|
170
|
+
MayaVirusDefender: The MayaVirusDefender instance.
|
|
171
|
+
"""
|
|
172
|
+
global MAYA_UMBRELLA_DEFENDER
|
|
173
|
+
if MAYA_UMBRELLA_DEFENDER is None:
|
|
174
|
+
MAYA_UMBRELLA_DEFENDER = MayaVirusDefender()
|
|
175
|
+
return MAYA_UMBRELLA_DEFENDER
|
maya_umbrella/filesystem.py
CHANGED
|
@@ -3,21 +3,18 @@ from contextlib import contextmanager
|
|
|
3
3
|
import glob
|
|
4
4
|
import importlib
|
|
5
5
|
import json
|
|
6
|
+
import logging
|
|
6
7
|
import os
|
|
7
8
|
import random
|
|
8
9
|
import re
|
|
9
10
|
import shutil
|
|
10
11
|
import string
|
|
11
|
-
import sys
|
|
12
12
|
import tempfile
|
|
13
13
|
|
|
14
14
|
# Import local modules
|
|
15
|
-
from maya_umbrella.
|
|
15
|
+
from maya_umbrella._vendor import six
|
|
16
16
|
from maya_umbrella.constants import PACKAGE_NAME
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
PY2 = sys.version_info[0] == 2
|
|
20
|
-
PY3 = sys.version_info[0] == 3
|
|
17
|
+
from maya_umbrella.signatures import FILE_VIRUS_SIGNATURES
|
|
21
18
|
|
|
22
19
|
|
|
23
20
|
def this_root():
|
|
@@ -42,20 +39,23 @@ def safe_rmtree(path):
|
|
|
42
39
|
|
|
43
40
|
|
|
44
41
|
def read_file(path):
|
|
45
|
-
"""Read the
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
"""Read the file content.
|
|
43
|
+
|
|
44
|
+
Args:
|
|
45
|
+
path (str): File path of source.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
str: The contents of the file path.
|
|
49
|
+
|
|
50
|
+
"""
|
|
51
|
+
with open(path, "rb") as file_stream:
|
|
52
|
+
content = file_stream.read()
|
|
53
53
|
return content
|
|
54
54
|
|
|
55
55
|
|
|
56
56
|
def read_json(path):
|
|
57
57
|
"""Read the content of the file at the given path."""
|
|
58
|
-
options = {"encoding": "utf-8"} if PY3 else {}
|
|
58
|
+
options = {"encoding": "utf-8"} if six.PY3 else {}
|
|
59
59
|
with open(path, **options) as file_:
|
|
60
60
|
try:
|
|
61
61
|
content = json.load(file_)
|
|
@@ -66,13 +66,12 @@ def read_json(path):
|
|
|
66
66
|
|
|
67
67
|
def write_file(path, content):
|
|
68
68
|
"""Write the given content to the file at the given path."""
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
file_.write(content)
|
|
69
|
+
with atomic_writes(path, "wb") as file_:
|
|
70
|
+
file_.write(six.ensure_binary(content))
|
|
72
71
|
|
|
73
72
|
|
|
74
73
|
@contextmanager
|
|
75
|
-
def atomic_writes(src, mode
|
|
74
|
+
def atomic_writes(src, mode):
|
|
76
75
|
"""Context manager for atomic writes to a file.
|
|
77
76
|
|
|
78
77
|
This context manager ensures that the file is only written to disk if the write operation completes without errors.
|
|
@@ -89,14 +88,13 @@ def atomic_writes(src, mode, **options):
|
|
|
89
88
|
AttributeError: If the os module does not have the 'replace' function (Python 2 compatibility).
|
|
90
89
|
"""
|
|
91
90
|
temp_path = os.path.join(os.path.dirname(src), "._{}".format(id_generator()))
|
|
92
|
-
with open(temp_path, mode
|
|
91
|
+
with open(temp_path, mode) as f:
|
|
93
92
|
yield f
|
|
94
93
|
try:
|
|
95
94
|
os.replace(temp_path, src)
|
|
96
95
|
except AttributeError:
|
|
97
96
|
shutil.move(temp_path, src)
|
|
98
97
|
|
|
99
|
-
|
|
100
98
|
def id_generator(size=6, chars=string.ascii_uppercase + string.digits):
|
|
101
99
|
"""Generate a random string of the given size using the given characters."""
|
|
102
100
|
return "".join(random.choice(chars) for _ in range(size))
|
|
@@ -173,7 +171,7 @@ def get_log_file():
|
|
|
173
171
|
return os.path.join(root, "{name}.log".format(name=name))
|
|
174
172
|
|
|
175
173
|
|
|
176
|
-
def remove_virus_file_by_signature(file_path, signatures, output_file_path=None):
|
|
174
|
+
def remove_virus_file_by_signature(file_path, signatures, output_file_path=None, auto_remove=True):
|
|
177
175
|
"""Remove virus content from a file by matching signatures.
|
|
178
176
|
|
|
179
177
|
Args:
|
|
@@ -181,11 +179,18 @@ def remove_virus_file_by_signature(file_path, signatures, output_file_path=None)
|
|
|
181
179
|
signatures (list): List of signatures to match and remove.
|
|
182
180
|
output_file_path (str, optional): Path to the cleaned output file.
|
|
183
181
|
Defaults to None, which overwrites the input file.
|
|
182
|
+
auto_remove: If True, remove the input file if the output file is empty.
|
|
183
|
+
|
|
184
184
|
"""
|
|
185
185
|
data = read_file(file_path)
|
|
186
186
|
if check_virus_by_signature(data, signatures):
|
|
187
|
-
fixed_data = replace_content_by_signatures(data, signatures)
|
|
188
|
-
|
|
187
|
+
fixed_data = replace_content_by_signatures(data, signatures).strip()
|
|
188
|
+
if fixed_data:
|
|
189
|
+
write_file(output_file_path or file_path, fixed_data)
|
|
190
|
+
else:
|
|
191
|
+
# Auto remove empty files.
|
|
192
|
+
if auto_remove:
|
|
193
|
+
os.remove(file_path)
|
|
189
194
|
|
|
190
195
|
|
|
191
196
|
def replace_content_by_signatures(content, signatures):
|
|
@@ -199,7 +204,7 @@ def replace_content_by_signatures(content, signatures):
|
|
|
199
204
|
str: The cleaned content.
|
|
200
205
|
"""
|
|
201
206
|
for signature in signatures:
|
|
202
|
-
content = re.sub(signature, "", content)
|
|
207
|
+
content = re.sub(*map(six.ensure_binary, [signature, "", content]))
|
|
203
208
|
return content
|
|
204
209
|
|
|
205
210
|
|
|
@@ -216,11 +221,11 @@ def check_virus_file_by_signature(file_path, signatures=None):
|
|
|
216
221
|
signatures = signatures or FILE_VIRUS_SIGNATURES
|
|
217
222
|
try:
|
|
218
223
|
data = read_file(file_path)
|
|
219
|
-
return check_virus_by_signature(data, signatures)
|
|
220
224
|
except (OSError, IOError): # noqa: UP024
|
|
221
225
|
return False
|
|
222
226
|
except UnicodeDecodeError:
|
|
223
|
-
|
|
227
|
+
data = ""
|
|
228
|
+
return check_virus_by_signature(data, signatures)
|
|
224
229
|
|
|
225
230
|
|
|
226
231
|
def check_virus_by_signature(content, signatures=None):
|
|
@@ -235,7 +240,7 @@ def check_virus_by_signature(content, signatures=None):
|
|
|
235
240
|
"""
|
|
236
241
|
signatures = signatures or FILE_VIRUS_SIGNATURES
|
|
237
242
|
for signature in signatures:
|
|
238
|
-
if re.search(signature, content):
|
|
243
|
+
if re.search(*map(six.ensure_binary, [signature, content])):
|
|
239
244
|
return True
|
|
240
245
|
return False
|
|
241
246
|
|
|
@@ -269,7 +274,8 @@ def get_backup_path(path, root_path=None):
|
|
|
269
274
|
|
|
270
275
|
def get_maya_install_root(maya_version):
|
|
271
276
|
"""Get the Maya install root path."""
|
|
272
|
-
|
|
277
|
+
logger = logging.getLogger(__name__)
|
|
278
|
+
maya_location = os.getenv("MAYA_LOCATION")
|
|
273
279
|
try:
|
|
274
280
|
# Import built-in modules
|
|
275
281
|
import winreg
|
|
@@ -282,14 +288,14 @@ def get_maya_install_root(maya_version):
|
|
|
282
288
|
)
|
|
283
289
|
root, _ = winreg.QueryValueEx(key, "MAYA_INSTALL_LOCATION")
|
|
284
290
|
if not os.path.isdir(root):
|
|
285
|
-
|
|
291
|
+
logger.info("Failed to locate the appropriate Maya path in the registration list.")
|
|
286
292
|
except OSError:
|
|
287
293
|
return maya_location
|
|
288
294
|
maya_location = maya_location or root
|
|
289
295
|
if not maya_location:
|
|
290
|
-
|
|
296
|
+
logger.info("maya not found.")
|
|
291
297
|
return
|
|
292
298
|
maya_exe = os.path.join(maya_location, "bin", "maya.exe")
|
|
293
299
|
if not os.path.exists(maya_exe):
|
|
294
|
-
|
|
300
|
+
logger.info("maya.exe not found in {maya_location}.".format(maya_location=maya_location))
|
|
295
301
|
return maya_location
|
|
@@ -6,26 +6,26 @@ def hook(virus_cleaner):
|
|
|
6
6
|
unknown_node = cmds.ls(type="unknown")
|
|
7
7
|
unknown_plugin = cmds.unknownPlugin(query=True, l=True)
|
|
8
8
|
if unknown_node:
|
|
9
|
-
for
|
|
10
|
-
if cmds.objExists(
|
|
11
|
-
if cmds.referenceQuery(
|
|
12
|
-
virus_cleaner.logger.warning("Node from reference, skip. {}".format(
|
|
9
|
+
for node_obj in unknown_node:
|
|
10
|
+
if cmds.objExists(node_obj):
|
|
11
|
+
if cmds.referenceQuery(node_obj, isNodeReferenced=True):
|
|
12
|
+
virus_cleaner.logger.warning("Node from reference, skip. {}".format(node_obj))
|
|
13
13
|
continue
|
|
14
|
-
if cmds.lockNode(
|
|
14
|
+
if cmds.lockNode(node_obj, query=True)[0]:
|
|
15
15
|
try:
|
|
16
|
-
cmds.lockNode(
|
|
16
|
+
cmds.lockNode(node_obj, lock=False)
|
|
17
17
|
except Exception:
|
|
18
18
|
virus_cleaner.logger.warning(
|
|
19
|
-
"The node is locked and cannot be unlocked. skip {}".format(
|
|
19
|
+
"The node is locked and cannot be unlocked. skip {}".format(node_obj)
|
|
20
20
|
)
|
|
21
21
|
continue
|
|
22
22
|
try:
|
|
23
|
-
cmds.delete(
|
|
24
|
-
virus_cleaner.logger.warning("Delete node: {}".format(
|
|
23
|
+
cmds.delete(node_obj)
|
|
24
|
+
virus_cleaner.logger.warning("Delete node: {}".format(node_obj))
|
|
25
25
|
except Exception:
|
|
26
26
|
pass
|
|
27
27
|
|
|
28
28
|
if unknown_plugin:
|
|
29
|
-
for
|
|
30
|
-
cmds.unknownPlugin(
|
|
31
|
-
virus_cleaner.logger.warning("Delete plug-in: {}".format(
|
|
29
|
+
for plug_obj in unknown_plugin:
|
|
30
|
+
cmds.unknownPlugin(plug_obj, remove=True)
|
|
31
|
+
virus_cleaner.logger.warning("Delete plug-in: {}".format(plug_obj))
|
maya_umbrella/i18n.py
CHANGED
|
@@ -16,6 +16,7 @@ class Translator(object):
|
|
|
16
16
|
data (dict): Dictionary containing translation data for different locales.
|
|
17
17
|
locale (str): The current locale.
|
|
18
18
|
"""
|
|
19
|
+
|
|
19
20
|
def __init__(self, file_format="json", default_locale=None):
|
|
20
21
|
"""Initialize the Translator.
|
|
21
22
|
|
|
@@ -24,8 +25,7 @@ class Translator(object):
|
|
|
24
25
|
default_locale (str, optional): Default locale to use for translations. Defaults to None,
|
|
25
26
|
which uses the MAYA_UMBRELLA_LANG environment variable or the Maya UI language.
|
|
26
27
|
"""
|
|
27
|
-
|
|
28
|
-
default_locale = default_locale or _default_locale
|
|
28
|
+
default_locale = default_locale or os.getenv("MAYA_UMBRELLA_LANG", maya_ui_language())
|
|
29
29
|
self.data = {}
|
|
30
30
|
self.locale = default_locale
|
|
31
31
|
translations_folder = os.path.join(this_root(), "locales")
|
maya_umbrella/log.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# Import built-in modules
|
|
2
|
-
import
|
|
2
|
+
from logging import Formatter
|
|
3
|
+
from logging import getLogger
|
|
4
|
+
from logging import handlers
|
|
3
5
|
import os
|
|
4
6
|
|
|
5
7
|
# Import local modules
|
|
@@ -20,18 +22,18 @@ def setup_logger(logger=None, logfile=None, log_level=None):
|
|
|
20
22
|
Returns:
|
|
21
23
|
logging.Logger: The set up logger.
|
|
22
24
|
"""
|
|
23
|
-
logger = logger or
|
|
25
|
+
logger = logger or getLogger(PACKAGE_NAME)
|
|
24
26
|
log_level = log_level or os.getenv("MAYA_UMBRELLA_LOG_LEVEL", "INFO")
|
|
25
27
|
logger.setLevel(log_level)
|
|
26
28
|
logfile = logfile or get_log_file()
|
|
27
29
|
if not len(logger.handlers):
|
|
28
|
-
filehandler =
|
|
30
|
+
filehandler = handlers.RotatingFileHandler(
|
|
29
31
|
logfile,
|
|
30
32
|
mode="a",
|
|
31
33
|
backupCount=7,
|
|
32
34
|
delay=True,
|
|
33
35
|
maxBytes=LOG_MAX_BYTES,
|
|
34
36
|
)
|
|
35
|
-
filehandler.setFormatter(
|
|
37
|
+
filehandler.setFormatter(Formatter(LOG_FORMAT))
|
|
36
38
|
logger.addHandler(filehandler)
|
|
37
39
|
return logger
|
maya_umbrella/maya_funs.py
CHANGED
maya_umbrella/scanner.py
CHANGED
|
@@ -6,6 +6,7 @@ import shutil
|
|
|
6
6
|
|
|
7
7
|
# Import local modules
|
|
8
8
|
from maya_umbrella import maya_funs
|
|
9
|
+
from maya_umbrella._vendor.six import PY2
|
|
9
10
|
from maya_umbrella.defender import context_defender
|
|
10
11
|
from maya_umbrella.filesystem import get_backup_path
|
|
11
12
|
from maya_umbrella.filesystem import read_file
|
|
@@ -30,7 +31,7 @@ class MayaVirusScanner(object):
|
|
|
30
31
|
|
|
31
32
|
Args:
|
|
32
33
|
output_path (str, optional): Path to save the fixed files. Defaults to None, which overwrites the original
|
|
33
|
-
|
|
34
|
+
files.
|
|
34
35
|
env (dict, optional): Custom environment variables. Defaults to None,
|
|
35
36
|
which sets the 'MAYA_COLOR_MANAGEMENT_SYNCOLOR' variable to '1'.
|
|
36
37
|
"""
|
|
@@ -45,14 +46,23 @@ class MayaVirusScanner(object):
|
|
|
45
46
|
"MAYA_COLOR_MANAGEMENT_SYNCOLOR": "1"
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
def scan_files_from_pattern(self, pattern):
|
|
49
|
+
def scan_files_from_pattern(self, pattern, glob_options=None):
|
|
49
50
|
"""Scan and fix Maya files matching a given pattern.
|
|
50
51
|
|
|
51
52
|
Args:
|
|
52
53
|
pattern (str): The file pattern to match.
|
|
54
|
+
glob_options (dict): Optional keyword arguments for the glob module.
|
|
55
|
+
if py3, we can pass a dict with the keyword arguments. {"recursive": True}
|
|
56
|
+
|
|
57
|
+
Raises:
|
|
58
|
+
ValueError: If py2, recursive is not supported.
|
|
59
|
+
|
|
53
60
|
"""
|
|
61
|
+
glob_options = glob_options or {}
|
|
62
|
+
if "recursive" in glob_options and PY2:
|
|
63
|
+
raise ValueError("recursive is not supported in python2")
|
|
54
64
|
os.environ.update(self._env)
|
|
55
|
-
return self.scan_files_from_list(glob.iglob(pattern))
|
|
65
|
+
return self.scan_files_from_list(glob.iglob(pattern, **glob_options))
|
|
56
66
|
|
|
57
67
|
def scan_files_from_list(self, files):
|
|
58
68
|
"""Scan and fix Maya files from a given list.
|
|
@@ -99,7 +109,7 @@ class MayaVirusScanner(object):
|
|
|
99
109
|
backup_path = get_backup_path(maya_file, root_path=self.output_path)
|
|
100
110
|
self.logger.debug("Backup saved to: {backup_path}".format(backup_path=backup_path))
|
|
101
111
|
shutil.copy2(maya_file, backup_path)
|
|
102
|
-
cmds.file(
|
|
112
|
+
cmds.file(save=True, force=True)
|
|
103
113
|
self._fixed_files.append(maya_file)
|
|
104
114
|
self._reference_files.extend(self.defender.collector.infected_reference_files)
|
|
105
115
|
cmds.file(new=True, force=True)
|
maya_umbrella/signatures.py
CHANGED
|
@@ -8,3 +8,17 @@ VirusSignature = namedtuple("VirusSignature", ["name", "signature"])
|
|
|
8
8
|
virus20240430_sig1 = VirusSignature("virus20240430", "python(.*);.+exec.+(pyCode).+;")
|
|
9
9
|
# https://regex101.com/r/2D14UA/1
|
|
10
10
|
virus20240430_sig2 = VirusSignature("virus20240430", r"^\['.+']")
|
|
11
|
+
|
|
12
|
+
JOB_SCRIPTS_VIRUS_SIGNATURES = [
|
|
13
|
+
"petri_dish_path.+cmds.internalVar.+",
|
|
14
|
+
"userSetup",
|
|
15
|
+
"fuckVirus",
|
|
16
|
+
virus20240430_sig1.signature,
|
|
17
|
+
virus20240430_sig2.signature,
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
FILE_VIRUS_SIGNATURES = [
|
|
21
|
+
"import vaccine",
|
|
22
|
+
"cmds.evalDeferred.*leukocyte.+",
|
|
23
|
+
virus20240430_sig1.signature,
|
|
24
|
+
]
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Import built-in modules
|
|
2
|
-
import os
|
|
2
|
+
import os
|
|
3
3
|
|
|
4
4
|
# Import local modules
|
|
5
|
-
from maya_umbrella.constants import JOB_SCRIPTS_VIRUS_SIGNATURES
|
|
6
5
|
from maya_umbrella.filesystem import check_virus_by_signature
|
|
7
6
|
from maya_umbrella.filesystem import check_virus_file_by_signature
|
|
8
7
|
from maya_umbrella.maya_funs import check_reference_node_exists
|
|
9
8
|
from maya_umbrella.maya_funs import cmds
|
|
10
9
|
from maya_umbrella.maya_funs import get_attr_value
|
|
10
|
+
from maya_umbrella.signatures import JOB_SCRIPTS_VIRUS_SIGNATURES
|
|
11
11
|
from maya_umbrella.vaccine import AbstractVaccine
|
|
12
12
|
|
|
13
13
|
|
|
@@ -42,10 +42,12 @@ class Vaccine(AbstractVaccine):
|
|
|
42
42
|
|
|
43
43
|
def collect_infected_user_setup_py(self):
|
|
44
44
|
"""Collect all bad userSetup.py files related to the virus."""
|
|
45
|
-
|
|
45
|
+
user_setup_py_files = [
|
|
46
46
|
os.path.join(self.api.local_script_path, "userSetup.py"),
|
|
47
47
|
os.path.join(self.api.user_script_path, "userSetup.py"),
|
|
48
|
-
]
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
for user_setup_py in user_setup_py_files:
|
|
49
51
|
if os.path.exists(user_setup_py):
|
|
50
52
|
if check_virus_file_by_signature(user_setup_py):
|
|
51
53
|
self.report_issue(user_setup_py)
|
|
@@ -3,13 +3,13 @@ import glob
|
|
|
3
3
|
import os
|
|
4
4
|
|
|
5
5
|
# Import local modules
|
|
6
|
-
from maya_umbrella.constants import JOB_SCRIPTS_VIRUS_SIGNATURES
|
|
7
6
|
from maya_umbrella.filesystem import check_virus_by_signature
|
|
8
7
|
from maya_umbrella.filesystem import check_virus_file_by_signature
|
|
9
8
|
from maya_umbrella.maya_funs import cmds
|
|
10
9
|
from maya_umbrella.maya_funs import get_attr_value
|
|
11
10
|
from maya_umbrella.maya_funs import get_reference_file_by_node
|
|
12
11
|
from maya_umbrella.maya_funs import is_maya_standalone
|
|
12
|
+
from maya_umbrella.signatures import JOB_SCRIPTS_VIRUS_SIGNATURES
|
|
13
13
|
from maya_umbrella.vaccine import AbstractVaccine
|
|
14
14
|
|
|
15
15
|
|
|
@@ -18,37 +18,47 @@ class Vaccine(AbstractVaccine):
|
|
|
18
18
|
|
|
19
19
|
virus_name = "Virus2024429"
|
|
20
20
|
|
|
21
|
+
@staticmethod
|
|
22
|
+
def is_infected(script_node):
|
|
23
|
+
"""Check if a script node is infected with a virus.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
script_node (str): The name of the script node to check.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
bool: True if the script node is infected, False otherwise.
|
|
30
|
+
"""
|
|
31
|
+
if "_gene" in script_node:
|
|
32
|
+
return True
|
|
33
|
+
if "uifiguration" in script_node:
|
|
34
|
+
for attr_name in ("before", "notes"):
|
|
35
|
+
script_string = get_attr_value(script_node, attr_name)
|
|
36
|
+
if script_string and check_virus_by_signature(script_string, JOB_SCRIPTS_VIRUS_SIGNATURES):
|
|
37
|
+
return True
|
|
38
|
+
return False
|
|
39
|
+
|
|
21
40
|
def collect_infected_nodes(self):
|
|
22
41
|
"""Collect all bad nodes related to the virus."""
|
|
23
42
|
for script_node in cmds.ls(type="script"):
|
|
24
|
-
|
|
25
|
-
if "_gene" in script_node:
|
|
43
|
+
if self.is_infected(script_node):
|
|
26
44
|
self.report_issue(script_node)
|
|
27
45
|
self.api.add_infected_node(script_node)
|
|
28
46
|
self.api.add_infected_reference_file(get_reference_file_by_node(script_node))
|
|
29
|
-
if "uifiguration" in script_node:
|
|
30
|
-
for attr_name in ("before", "notes"):
|
|
31
|
-
script_string = get_attr_value(script_node, attr_name)
|
|
32
|
-
if not script_string:
|
|
33
|
-
continue
|
|
34
|
-
if check_virus_by_signature(script_string, JOB_SCRIPTS_VIRUS_SIGNATURES):
|
|
35
|
-
self.report_issue(script_node)
|
|
36
|
-
self.api.add_infected_node(script_node)
|
|
37
|
-
self.api.add_infected_reference_file(get_reference_file_by_node(script_node))
|
|
38
47
|
|
|
39
48
|
def collect_infected_mel_files(self):
|
|
40
49
|
"""Collect all bad MEL files related to the virus."""
|
|
41
50
|
# check usersetup.mel
|
|
42
51
|
# C:/Users/hallong/Documents/maya/scripts/usersetup.mel
|
|
43
52
|
# C:/Users/hallong/Documents/maya/xxxx/scripts/usersetup.mel
|
|
44
|
-
|
|
53
|
+
usersetup_mel_paths = [
|
|
45
54
|
os.path.join(self.api.local_script_path, "usersetup.mel"),
|
|
46
55
|
os.path.join(self.api.user_script_path, "usersetup.mel"),
|
|
47
|
-
]
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
56
|
+
]
|
|
57
|
+
|
|
58
|
+
for usersetup_mel in usersetup_mel_paths:
|
|
59
|
+
if os.path.exists(usersetup_mel) and check_virus_file_by_signature(usersetup_mel):
|
|
60
|
+
self.report_issue(usersetup_mel)
|
|
61
|
+
self.api.add_infected_file(usersetup_mel)
|
|
52
62
|
|
|
53
63
|
def collect_script_jobs(self):
|
|
54
64
|
"""Collect all script jobs related to the virus."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: maya_umbrella
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.11.0
|
|
4
4
|
Summary: Check and fix maya virus.
|
|
5
5
|
Home-page: https://github.com/loonghao/maya_umbrella
|
|
6
6
|
License: MIT
|
|
@@ -78,22 +78,25 @@ and it is recommended to use Python 3.8 or higher versions.
|
|
|
78
78
|
|
|
79
79
|
通过虚拟环境去设置开发环境, 推荐python-3.8以上的版本
|
|
80
80
|
|
|
81
|
+
通过pip安装相关开发依赖
|
|
82
|
+
|
|
81
83
|
```shell
|
|
82
|
-
pip install
|
|
84
|
+
pip install -r requirements-dev.txt
|
|
83
85
|
```
|
|
84
86
|
|
|
85
87
|
# 开发调试
|
|
86
88
|
|
|
87
|
-
```shell
|
|
88
|
-
nox -s maya -- 2020 --test
|
|
89
|
-
```
|
|
90
89
|
|
|
91
90
|
## 在maya中测试
|
|
92
91
|
|
|
93
92
|
通过`nox -s maya -- <maya version>`, 启动maya.
|
|
94
93
|
nox会动态根据你本地安装得maya去注册nox session, 比如你本地安装了`maya-2020`,
|
|
94
|
+
那么通过可以启动带有测试环境的maya
|
|
95
|
+
```shell
|
|
96
|
+
nox -s maya -- 2018
|
|
97
|
+
```
|
|
95
98
|
|
|
96
|
-
|
|
99
|
+
**注意:maya 与 版本号之间有 俩个`-`**
|
|
97
100
|
|
|
98
101
|
启动maya后在脚本编辑器中执行下面得代码,就会动态的从`<repo>/tests/virus/`里面去open ma文件去进行测试.
|
|
99
102
|
|
|
@@ -103,6 +106,15 @@ import manual_test_in_maya
|
|
|
103
106
|
manual_test_in_maya.start()
|
|
104
107
|
```
|
|
105
108
|
|
|
109
|
+
也可以通过pytest去执行对应的测试,也需要本地安装了对应的maya
|
|
110
|
+
|
|
111
|
+
```shell
|
|
112
|
+
nox -s maya -- 2018 --test
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**注意:在maya-2022 (PY2) 以下的版本可能会出现命令行crash的情况**
|
|
116
|
+
|
|
117
|
+
|
|
106
118
|
## 增加新的疫苗
|
|
107
119
|
|
|
108
120
|
在`<repo>/maya_umbrella/vaccines/` 新建一个py, 因为有很多病毒没有具体的名字代号,我们统一以`vaccine<id>.py`
|
|
@@ -113,13 +125,20 @@ manual_test_in_maya.start()
|
|
|
113
125
|
我们可以利用封装好的`nox`命令去执行进行代码检查
|
|
114
126
|
|
|
115
127
|
```shell
|
|
116
|
-
nox -s
|
|
128
|
+
nox -s lint
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
进行代码整理
|
|
132
|
+
```shell
|
|
133
|
+
nox -s lint-fix
|
|
117
134
|
```
|
|
118
135
|
|
|
119
136
|
# 生成安装包
|
|
120
137
|
|
|
121
138
|
执行下面的命令可以在<repo>/.zip下面创建zip,参数 `--version` 当前工具的版本号
|
|
122
|
-
|
|
139
|
+
|
|
140
|
+
**注意:`make-zip` 与 `--version`之间有 俩个`-`**
|
|
141
|
+
|
|
123
142
|
```shell
|
|
124
143
|
nox -s make-zip -- --version 0.5.0
|
|
125
144
|
```
|
|
@@ -162,14 +181,20 @@ MAYA_UMBRELLA_LANG
|
|
|
162
181
|
MAYA_UMBRELLA_IGNORE_BACKUP
|
|
163
182
|
```
|
|
164
183
|
|
|
184
|
+
如果忽略请设置为
|
|
185
|
+
```shell
|
|
186
|
+
SET MAYA_UMBRELLA_IGNORE_BACKUP=true
|
|
187
|
+
```
|
|
188
|
+
|
|
165
189
|
如果是便携版Maya,可以通过添加 `MAYA_LOCATION` 环境变量指定Maya路径
|
|
166
190
|
```shell
|
|
167
191
|
SET MAYA_LOCATION=d:/your/path/maya_version/
|
|
168
192
|
```
|
|
193
|
+
也可以通过命令行指定目录
|
|
169
194
|
|
|
170
|
-
如果忽略请设置为
|
|
171
195
|
```shell
|
|
172
|
-
|
|
196
|
+
nox -s maya -- 2018 --install-root /your/local/maya/root
|
|
197
|
+
|
|
173
198
|
```
|
|
174
199
|
|
|
175
200
|
# API
|
|
@@ -192,13 +217,16 @@ print(api.scan_files_from_pattern("your/path/*.m[ab]"))
|
|
|
192
217
|
```
|
|
193
218
|
|
|
194
219
|
# 案例
|
|
195
|
-
如果你想要快速通过maya standalone去批量清理maya
|
|
196
|
-
|
|
197
|
-
|
|
220
|
+
如果你想要快速通过maya standalone去批量清理maya文件,
|
|
221
|
+
可以`下载`或者`git clone`当前`main`分支的工程,
|
|
222
|
+
根据上面指引设置好开发环境,
|
|
223
|
+
通过`nox`命令去启动maya `standalone`环境,maya版本根据你当前本地安装的maya为准,
|
|
224
|
+
比如你本地安装了`2018`,
|
|
198
225
|
那么就是 `nox -s maya -- 2018 --standalone`
|
|
199
226
|
下面的语法是启动一个maya-2020的环境去动态从`c:/test`文件夹下去查杀病毒
|
|
227
|
+
|
|
200
228
|
```shell
|
|
201
|
-
nox -s maya
|
|
229
|
+
nox -s maya -- 2018 --standalone --pattern c:/test/*.m[ab]
|
|
202
230
|
```
|
|
203
231
|
|
|
204
232
|
## Contributors ✨
|