29
29
# =========== Initialize constants
30
30
31
31
programName = 'opy'
32
- programVersion = '1.1.26 '
32
+ programVersion = '1.1.27 '
33
33
34
34
if __name__ == '__main__' :
35
35
print ('{} (TM) Configurable Multi Module Python Obfuscator Version {}' .format (programName .capitalize (), programVersion ))
@@ -105,67 +105,62 @@ def unScramble{0} (keyedStringLiteral):
105
105
106
106
def printHelpAndExit (errorLevel ):
107
107
print (r'''
108
-
109
- *******************************************************************************
108
+ ===============================================================================
110
109
{0} will obfuscate your extensive, real world, multi module Python source code for free!
111
110
And YOU choose per project what to obfuscate and what not, by editting the config file.
112
111
113
- BACKUP YOUR CODE AND VALUABLE DATA TO AN OFF-LINE MEDIUM FIRST TO PREVENT ACCIDENTAL LOSS OF WORK!!!
114
-
112
+ - BACKUP YOUR CODE AND VALUABLE DATA TO AN OFF-LINE MEDIUM FIRST TO PREVENT ACCIDENTAL LOSS OF WORK!!!
115
113
Then copy the default config file to the source top directory <topdir> and run {0} from there.
116
114
It will generate an obfuscation directory <topdir>/../<topdir>_{1}
117
115
118
- At first some identifiers may be obfuscated that shouldn't be, e.g. some of those imported from external modules.
116
+ - At first some identifiers may be obfuscated that shouldn't be, e.g. some of those imported from external modules.
119
117
Adapt your config file to avoid this, e.g. by adding external module names that will be recursively scanned for identifiers.
120
118
You may also exclude certain words or files in your project from obfuscation explicitly.
121
- Source directory, obfuscation directory and config file path can also be supplied as command line parameters in that order.
122
- Comments and string literals can be marked as plain, bypassing obfuscation
119
+
120
+ - Source directory, obfuscation directory and config file path can also be supplied as command line parameters.
121
+ The config file path should be something like C:/config_files/opy.cnf, so including the file name and extension.
122
+ opy [<source directory> [<target directory> [<config file path>]]]
123
+
124
+ - Comments and string literals can be marked as plain, bypassing obfuscation
125
+ Be sure to take a look at the comments in the config file opy_config.txt to discover all features.
123
126
124
127
Known limitations:
125
128
126
- A comment after a string literal should be preceded by whitespace
127
- A ' or " inside a string literal should be escaped with \ rather then doubled
128
- If the pep8_comments option is False (the default), a {2} in a string literal can only be used at the start, so use 'p''{2}''r' rather than 'p{2}r'
129
- If the pep8_comments option is set to True, however, only a <blank><blank>{2}<blank> cannot be used in the middle or at the end of a string literal
130
- Obfuscation of string literals is unsuitable for sensitive information since it can be trivially broken
131
- No renaming backdoor support for methods starting with __ (non-overridable methods, also known as private methods)
129
+ - A comment after a string literal should be preceded by whitespace
130
+ - A ' or " inside a string literal should be escaped with \ rather then doubled
131
+ - If the pep8_comments option is False (the default), a {2} in a string literal can only be used at the start, so use 'p''{2}''r' rather than 'p{2}r'
132
+ - If the pep8_comments option is set to True, however, only a <blank><blank>{2}<blank> cannot be used in the middle or at the end of a string literal
133
+ - Obfuscation of string literals is unsuitable for sensitive information since it can be trivially broken
134
+ - No renaming backdoor support for methods starting with __ (non-overridable methods, also known as private methods)
132
135
133
136
Licence:
134
137
{3}
135
- *******************************************************************************
138
+ ===============================================================================
139
+
136
140
''' .format (programName .capitalize (), programName , r'#' , license ))
137
141
exit (errorLevel )
138
142
139
143
# ============ Assign directories ============
140
144
141
145
if len (sys .argv ) > 1 :
142
- if '?' in sys .argv [1 ]:
143
- printHelpAndExit (0 )
144
- sourceRootDirectory = sys .argv [1 ]
146
+ for switch in '?' , '-h' , '--help' :
147
+ if switch in sys .argv [1 ]:
148
+ printHelpAndExit (0 )
149
+ sourceRootDirectory = sys .argv [1 ] .replace ('\\ ' , '/' )
145
150
else :
146
151
sourceRootDirectory = os .getcwd () .replace ('\\ ' , '/' )
147
152
148
153
if len (sys .argv ) > 2 :
149
- targetRootDirectory = sys .argv [2 ]
154
+ targetRootDirectory = sys .argv [2 ] . replace ( ' \\ ' , '/' )
150
155
else :
151
156
targetRootDirectory = '{0}/{1}_{2}' .format (* (sourceRootDirectory .rsplit ('/' , 1 ) + [programName ]))
152
157
153
158
if len (sys .argv ) > 3 :
154
- configFilePath = sys .argv [3 ]
159
+ configFilePath = sys .argv [3 ] . replace ( ' \\ ' , '/' )
155
160
else :
156
161
configFilePath = '{0}/{1}_config.txt' .format (sourceRootDirectory , programName )
157
-
162
+
158
163
# =========== Read config file
159
-
160
- # Default values for config items to preserve backward compatibility if items are added
161
- obfuscate_strings = False
162
- obfuscated_name_tail = '_{}_' .format (programName )
163
- plain_marker = '_{}_' .format (programName )
164
- source_extensions = ''
165
- skip_extensions = ''
166
- external_modules = ''
167
- plain_files = ''
168
- plain_names = ''
169
164
170
165
try :
171
166
configFile = open (configFilePath )
@@ -175,45 +170,40 @@ def printHelpAndExit (errorLevel):
175
170
176
171
exec (configFile .read ())
177
172
configFile .close ()
178
-
179
- try :
180
- obfuscateStrings = obfuscate_strings
181
- except :
182
- obfuscateStrings = False
183
-
184
- try :
185
- asciiStrings = ascii_strings
186
- except :
187
- asciiStrings = False
188
-
189
- try :
190
- obfuscatedNameTail = obfuscated_name_tail
191
- except :
192
- obfuscatedNameTail = ''
193
-
194
- try :
195
- plainMarker = plain_marker
196
- except :
197
- plainMarker = '_{0}_' .format (programName )
198
-
199
- try :
200
- pep8Comments = pep8_comments
201
- except :
202
- pep8Comments = True
203
-
204
- sourceFileNameExtensionList = source_extensions .split ()
205
- skipFileNameExtensionList = skip_extensions .split ()
206
- externalModuleNameList = external_modules .split ()
207
- plainFileRelPathList = plain_files .split ()
208
- extraPlainWordList = plain_names .split ()
173
+
174
+ def getConfig (parameter , default ):
175
+ try :
176
+ return eval (parameter )
177
+ except :
178
+ return default
179
+
180
+ obfuscateStrings = getConfig ('obfuscate_strings' , False )
181
+ asciiStrings = getConfig ('ascii_strings' , False )
182
+ obfuscatedNameTail = getConfig ('obfuscated_name_tail' , '_{}_' )
183
+ plainMarker = getConfig ('plain_marker' , '_{}_' .format (programName ))
184
+ pep8Comments = getConfig ('pep8_comments' , True )
185
+ sourceFileNameExtensionList = getConfig ('source_extensions.split ()' , ['py' , 'pyx' ])
186
+ skipFileNameExtensionList = getConfig ('skip_extensions.split ()' , ['pyc' ])
187
+ skipPathFragmentList = getConfig ('skip_path_fragments.split ()' , [])
188
+ externalModuleNameList = getConfig ('external_modules.split ()' , [])
189
+ plainFileRelPathList = getConfig ('plain_files.split ()' , [])
190
+ extraPlainWordList = getConfig ('plain_names.split ()' , [])
209
191
210
192
# ============ Gather source file names
211
193
212
- sourceFilePathList = [
194
+ rawSourceFilePathList = [
213
195
'{0}/{1}' .format (directory .replace ('\\ ' , '/' ), fileName )
214
196
for directory , subDirectories , fileNames in os .walk (sourceRootDirectory )
215
197
for fileName in fileNames
216
198
]
199
+
200
+ def hasSkipPathFragment (sourceFilePath ):
201
+ for skipPathFragment in skipPathFragmentList :
202
+ if skipPathFragment in sourceFilePath :
203
+ return True
204
+ return False
205
+
206
+ sourceFilePathList = [sourceFilePath for sourceFilePath in rawSourceFilePathList if not hasSkipPathFragment (sourceFilePath )]
217
207
218
208
# =========== Define comment swapping tools
219
209
@@ -315,7 +305,12 @@ def moveFromFuture (matchObject):
315
305
316
306
skipWordSet = set (keyword .kwlist + ['__init__' ] + extraPlainWordList ) # __init__ should be in, since __init__.py is special
317
307
318
- plainFilePathList = ['{0}/{1}' .format (sourceRootDirectory , plainFileRelPath ) for plainFileRelPath in plainFileRelPathList ]
308
+ rawPlainFilePathList = ['{0}/{1}' .format (sourceRootDirectory , plainFileRelPath ) for plainFileRelPath in plainFileRelPathList ]
309
+
310
+ # Prevent e.g. attempt to open opy_config.txt if it is in a different location but still listed under plain_files
311
+
312
+ plainFilePathList = [plainFilePath for plainFilePath in rawPlainFilePathList if os .path .exists (plainFilePath )]
313
+
319
314
for plainFilePath in plainFilePathList :
320
315
plainFile = open (plainFilePath )
321
316
content = plainFile .read ()
0 commit comments