The "Debugging Save Invisibly As" command interleaves the original source code lines, as comments, with the obfuscated lines. This makes it easier to see how Invisible Basic has transformed your code (or, in this case, itself).
Return to Invisible Basic Home Page
'' '' Invisible Basic: A utility for the obfuscation of VBA code '' in Excel Workbooks. See the Invisible Basic User's Guide '' (InvisibleBasic.html) for a detailed description of why '' this is useful. '' '' Copyright (c) 2005, John C. Gunther. '' All rights reserved. '' '' Redistribution and use in source and binary forms, with '' or without modification, are permitted provided that the '' following conditions are met: '' '' - Redistributions of source code must retain the above '' copyright notice, this list of conditions and the following '' disclaimer. '' '' - Redistributions in binary form must reproduce the above '' copyright notice, this list of conditions and the following '' disclaimer in the documentation and/or other materials '' provided with the distribution '' '' - Neither the name of the Invisible Basic Consortium nor '' the names of its contributors may be used to endorse or '' promote products derived from this software without '' specific prior written permission. '' '' THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND '' CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED '' WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED '' WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR '' PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE '' COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, '' INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL '' DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF '' SUBSTITURE GOODS OR SERVICES; LOSS OF USE, DATA, OR '' PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON '' ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT '' LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) '' ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN '' IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. '' '' Note: the licence conditions above were copied from the '' BSD open source license template available at '' http://www.opensource.org.licenses/bsd-license.php. '' ' 'Option Explicit Option Explicit ' '' Token types: as a program's source code is scanned, it is '' separated into a stream of tokens each with one of these '' types: 'Private Const TT_IDENTIFIER As Integer = 1 'variable names, keywords, etc. Private Const j As Integer = 1 'Private Const TT_STRING As Integer = 2 ' string literal ("myString") Private Const k As Integer = 2 'Private Const TT_NUMBER As Integer = 3 ' numerical literal (1.23) Private Const l As Integer = 3 'Private Const TT_COMMENT As Integer = 4 ' VBA comment text Private Const m As Integer = 4 'Private Const TT_WHITESPACE As Integer = 5 ' space or tab Private Const n As Integer = 5 'Private Const TT_GUID As Integer = 6 ' global universal identifier Private Const o As Integer = 6 ' ' ({C62A69F0-16DC-11CE-9E98-00AA00574A4F}) 'Private Const TT_OTHER As Integer = 7 ' everything else Private Const p As Integer = 7 ' '' Name of the file that contains the list of visible (not obfuscated) '' Excel/VBA keywords, reserved Excel object model names, etc. 'Private Const IB_VISIBLE_KEYWORDS_FILENAME = "visible_names.txt" Private Const r = "visible_names.txt" ' '' establish classes of characters helpful in tokenization: ' 'Private Const wsChars As String = " " & vbTab Private Const t As String = " " & vbTab 'Private Const alphaChars As String = "abcdefghijklmnopqrstuvwxyz" Private Const u As String = "abcdefghijklmnopqrstuvwxyz" 'Private Const underscore As String = "_" Private Const v As String = "_" 'Private Const digits As String = "0123456789" Private Const w As String = "0123456789" 'Private Const dQuote As String = """" Private Const x As String = """" 'Private Const GUID_START As String = "{" ' "Global Universal ID" Private Const y As String = "{" 'Private Const GUID_END As String = "}" ' (occurs in UserForm headers) Private Const z As String = "}" 'Private Const firstNumericChars As String = digits Private Const ab As String = w 'Private Const numericChars As String = firstNumericChars & "." Private Const bb As String = ab & "." 'Private Const firstCommentChar As String = "'" Private Const cb As String = "'" 'Private Const doubleComment As String = firstCommentChar & firstCommentChar Private Const db As String = cb & cb 'Private Const line_continuation_chars As String = " " & underscore Private Const eb As String = " " & v 'Private Const firstIdentifierChars As String = alphaChars Private Const fb As String = u 'Private Const identifierChars As String = alphaChars & underscore & digits Private Const gb As String = u & v & w '' e.g. in the event procedure myButton_Click, "_" delimits the '' control name from the event name: 'Private Const userform_event_delimiter As String = underscore Private Const hb As String = v '' if this character preceeds an indentifier within visible_names.txt, it flags '' that identifier as a userform control attribute. 'Private Const userform_control_attribute_flag As String = underscore Private Const ib As String = v 'Private Const object_attribute_delimiter As String = "." 'object attribute delimiter (e.g. the "." in myLabel.Caption) Private Const jb As String = "." ' '' These keywords, when encountered in source code, are '' recognized by Invisible Basic as directives that define if '' identifiers will be obfucated ("invisible") or retained as is '' ("visible") ' 'Private Const VISIBLE_KEYWORD As String = "#visible" ' for single lines Private Const visible_keyword As String = "#visible" '' for delimiting visible blocks: 'Private Const BEGIN_VISIBLE_KEYWORD As String = "#begin_visible" Private Const begin_visible_keyword As String = "#begin_visible" 'Private Const END_VISIBLE_KEYWORD As String = "#end_visible" Private Const kb As String = "#end_visible" ' '' this is added to the end of the workbook file name to get '' the default new, obfuscated, workbook's filename (e.g. '' myWorkbook.xls becomes saved invisibly, if user accepts '' the initial default name, as myWorkbook_ib.xls): ' 'Private Const IB_FILENAME_SUFFIX As String = "_ib" Private Const lb As String = "_ib" ' '' depth of #begin_visible ... #end_visible nesting: 'Private m_visible_depth As Long Private mb As Long ' '' returned when a specified identifier isn't found: 'Private Const NO_SUCH_ID As String = "" Private Const nb As String = "" ' '' lists of names that will remain in original format '' (visible), and of those that will be obuscated (made invisible) 'Private visible_names As New Collection Private ob As New Collection 'Private invisible_names As New Collection Private pb As New Collection ' '' lists of userform attribute names; userform attributes are used to '' identify userform control names either 1) via their use in event '' procedure names (e.g. the Click attribute identifies myButton as a '' control name in the event procedure myButton_Click) or 2) via the '' direct use of the attribute in code (e.g., the Caption attribute '' identifies myLabel as a control name in the code line: '' myLabel.Caption = "myLable Caption"). Such control names are '' automatically declared as "visible names" by Invisible Basic, '' and not obfuscated. '' '' Why do we even need this, you ask. Unlike most VBA variables, '' UserForm control names are NOT defined in the source code; because I '' could not figure out how to change these (non-source defined) names '' programmatically, I instead must be sure they are NOT changed in the '' source code (or else names would get out of synch, breaking the '' UserForm). Hence the need for these special "visible attribute" '' rules to recognize such control names. '' '' Pre 2.0 versions didn't have this feature, and thus required manual '' user intervention to declare such control names visible. ' 'Private userform_attribute_names As New Collection Private qb As New Collection ' '' if True, each obfuscated final code line will be preceeded with '' a comment containing the original, unobfuscated, line '' it came from (for trouble-shooting). ' 'Private m_interleave_original_code_as_comments As Boolean Private rb As Boolean ' 'Private Const IB_NameOfInvisibleBasicMenu As String = "Invisible&Basic" Private Const sb As String = "Invisible&Basic" '' Excel menu on which the Invisible Basic menu is placed: 'Private Const IB_NameOfExcelWorksheetMenubar As String = _ "Worksheet Menu Bar" Private Const tb As String = _ "Worksheet Menu Bar" ' 'Private Const IB_TEMP_FILENAME_PREFIX = "InvBas_Temp_" ' example temp filename: InvBas_Temp_1.tmp Private Const ub = "InvBas_Temp_" ' '' these declarations are used only by the visit_url function, '' used by the help command to open the Help file. Help command '' only works on Windows platforms. ' 'Private Declare Function GetDesktopWindow Lib "user32" () As Long Private Declare Function vb Lib "user32" () As Long ' 'Private Declare Function ShellExecute Lib "shell32" _ Alias "ShellExecuteA" _ (ByVal hwnd As Long, _ ByVal lpOperation As String, _ ByVal lpFile As String, _ ByVal lpParameters As String, _ ByVal lpDirectory As String, _ ByVal nShowCmd As Long) As Long Private Declare Function xb Lib "shell32" _ Alias "ShellExecuteA" _ (ByVal yb As Long, _ ByVal zb As String, _ ByVal ac As String, _ ByVal bc As String, _ ByVal cc As String, _ ByVal dc As Long) As Long ' 'Private Const SW_SHOWNORMAL As Long = 1 Private Const ec As Long = 1 'Private Const SW_SHOWMAXIMIZED As Long = 3 Private Const fc As Long = 3 'Private Const SW_SHOWDEFAULT As Long = 10 Private Const gc As Long = 10 ' '' end of declarations for visit_url ' '' These constants (sans the "IB_" prefix) are defined in the '' Microsoft Visual Basic for Applications Extensibility Library. ' '' So that users do not have to add a reference to that library, we '' define copies of those constants here. Another reason for doing this '' is that there can be more than one of these libraries on a single '' machine, and if a reference to the wrong version is employed, the '' code breaks with a rather cryptic "type mismatch" error (the type of '' VBComponent used by Excel differs from the type used by the '' VBComponent in the Extensibility library if the extensibility '' library is from a newer version of Excel) ' '' If (as seems very unlikely) Microsoft ever changes these constants, '' these lines would have to be changed. ' 'Private Const IB_vbext_ct_StdModule As Long = 1 Private Const hc As Long = 1 'Private Const IB_vbext_ct_ClassModule As Long = 2 Private Const ic As Long = 2 'Private Const IB_vbext_ct_MSForm As Long = 3 Private Const jc As Long = 3 'Private Const IB_vbext_ct_Document As Long = 100 Private Const kc As Long = 100 ' '' End of Microsoft VBA Extensibility library constants ' '' circular buffer of recently seen tokens (facilitates '' recognition of userform control names): 'Private Const N_BUFFERED_TOKENS As Integer = 3 Private Const lc As Integer = 3 'Private prevTokens(0 To N_BUFFERED_TOKENS - 1) As String Private mc(0 To lc - 1) As String 'Private tokenIndex As Integer Private nc As Integer ' 'Private Function PS() As String ' e.g. a "\" on Windows Private Function oc() As String ' PS = Application.PathSeparator oc = Application.PathSeparator 'End Function End Function ' 'Public Property Get interleave_original_code_as_comments() As Boolean Public Property Get pc() As Boolean ' interleave_original_code_as_comments = m_interleave_original_code_as_comments pc = rb 'End Property End Property ' 'Public Property Let interleave_original_code_as_comments(original_code_as_comments As Boolean) Public Property Let pc(qc As Boolean) ' m_interleave_original_code_as_comments = original_code_as_comments rb = qc 'End Property End Property ' '' VBA within Excel 97 lacks "Debug.Assert". For compatibility with '' all major Excel versions, we therefore emulate it: 'Private Sub assert(condition As Boolean) Private Sub assert(rc As Boolean) ' If (Not condition) Then Stop If (Not rc) Then Stop 'End Sub End Sub ' '' Returns integer type of a token assumed to start at give '' position in the given string ' '' Note: Invisible Basic's lexical analysis is, by design, very simple '' and suitable only for this code obfuscation task. For example, the '' "E" in scientific notation numeric literals will be treated like a '' variable that is always visible (e.g. 1.2E10 is analyzed as the number '' 1.2, the always-visible name E, and the number 10). It all comes out OK in '' the end, but just be aware that the string of tokens seen by '' Invisible Basic is NOT the same as what Visual Basic sees. ' 'Private Function token_type(s As String, _ iStart As Integer) As Integer Private Function sc(tc As String, _ uc As Integer) As Integer ' Dim c As String Dim vc As String ' Dim result As Integer Dim wc As Integer ' assert 1 <= iStart And iStart <= Len(s) assert 1 <= uc And uc <= Len(tc) ' c = LCase(Mid(s, iStart, 1)) vc = LCase(Mid(tc, uc, 1)) ' If (InStr(1, wsChars, c) <> 0) Then If (InStr(1, t, vc) <> 0) Then ' result = TT_WHITESPACE wc = n ' ElseIf (InStr(1, firstIdentifierChars, c) <> 0) Then ElseIf (InStr(1, fb, vc) <> 0) Then ' result = TT_IDENTIFIER wc = j ' ElseIf (c = dQuote) Then ElseIf (vc = x) Then ' result = TT_STRING wc = k ' ElseIf (c = GUID_START) Then ElseIf (vc = y) Then ' result = TT_GUID wc = o ' ElseIf (InStr(1, firstNumericChars, c) <> 0) Then ElseIf (InStr(1, ab, vc) <> 0) Then ' result = TT_NUMBER wc = l ' ElseIf (firstCommentChar = c) Then ElseIf (cb = vc) Then ' result = TT_COMMENT wc = m ' Else Else ' result = TT_OTHER wc = p ' End If End If ' ' token_type = result sc = wc ' 'End Function End Function ' '' Returns the string position 1 character past the end of the '' token that starts at the given position in the given string. ' 'Private Function end_of_token(s As String, _ iStart As Integer) As Integer Private Function xc(tc As String, _ uc As Integer) As Integer ' Dim iEnd As Integer Dim yc As Integer ' Dim tt As Integer Dim zc As Integer ' Dim matchChars As String Dim ad As String ' Dim invertMatch As Boolean Dim bd As Boolean ' Dim matched As Boolean Dim cd As Boolean ' Dim c As String Dim vc As String ' ' tt = token_type(s, iStart) zc = sc(tc, uc) ' ' Select Case (tt) Select Case (zc) ' Case TT_IDENTIFIER Case j ' matchChars = identifierChars ad = gb ' invertMatch = False bd = False ' Case TT_STRING Case k ' matchChars = dQuote ad = x ' invertMatch = True ' all chars until next double quote bd = True ' Case TT_GUID Case o ' matchChars = GUID_END ad = z ' invertMatch = True bd = True ' Case TT_NUMBER Case l ' matchChars = numericChars ad = bb ' invertMatch = False bd = False ' Case TT_COMMENT Case m ' matchChars = "" ad = "" ' invertMatch = True ' match everything until end of line bd = True ' Case TT_WHITESPACE Case n ' matchChars = wsChars ad = t ' invertMatch = False bd = False ' Case TT_OTHER Case p ' ' any character that can NOT be viewed as the first char of ' ' one of the above token types ' matchChars = firstIdentifierChars & firstNumericChars & _ firstCommentChar & dQuote & wsChars & GUID_START ad = fb & ab & _ cb & x & t & y ' invertMatch = True bd = True ' End Select End Select ' ' iEnd = iStart + 1 yc = uc + 1 ' ' Do While (iEnd <= Len(s)) Do While (yc <= Len(tc)) ' c = LCase(Mid(s, iEnd, 1)) vc = LCase(Mid(tc, yc, 1)) ' matched = InStr(1, matchChars, c) <> 0 cd = InStr(1, ad, vc) <> 0 ' If (invertMatch) Then matched = Not matched If (bd) Then cd = Not cd ' ' If (Not matched) Then Exit Do If (Not cd) Then Exit Do ' ' iEnd = iEnd + 1 yc = yc + 1 ' ' Loop Loop ' '' end of string or GUID should include the closing double '' quote or end of GUID character (close curley brace), so '' increase by one to include these final characters. ' '' Note: improperly terminated quotes or GUIDs should be impossible '' in "compilable" VBA source code. In the event that the closing '' character is missing, iEnd will already be one past the last '' character of the input line/string, so no need to advance it. ' ' If ((tt = TT_STRING And c = dQuote) Or _ (tt = TT_GUID And c = GUID_END)) Then If ((zc = k And vc = x) Or _ (zc = o And vc = z)) Then ' iEnd = iEnd + 1 yc = yc + 1 ' End If End If ' ' end_of_token = iEnd xc = yc ' 'End Function End Function ' ' '' returns a meaningless, sequential, variable name (that is '' also reasonably short). ' 'Private Function invisible_variable_name(var_id As Long) As String Private Function dd(ed As Long) As String ' Dim result As String Dim wc As String ' Dim i As Long Dim fd As Long ' Dim L1 As Integer Dim gd As Integer ' Dim L2 As Integer Dim hd As Integer ' ' assert var_id > 0 assert ed > 0 ' result = "" wc = "" '' '' this algorithm obtains a valid, short, and meaningless identifier by '' expressing the given integer variable id as a "mixed base" '' number whose "digits" are the characters valid in an identifier. ' '' Specifically, if you think of the variable id integer as expressed as: ' '' var_id = i0 + L1* (i1 + L2*i2 + L2^2*i3 + L2^3*i4 + ... ) ' '' (by a slight generalization of the basic ideas of "base X" numbers '' you can show that any positive integer can be expressed in such a '' "mixed L1/L2 base" form) ' '' where L1 is the length of the valid initial identifier characters '' string; L2 is the length of the valid non-initial identifier '' character string; i0 is an integer index (0..L1-1) into the initial '' identifier char string, and i1, i2, ... are indexes (0...L2-1) into '' the non-initial identifier char string. Then the chars associated '' with these indexes determine the chars in a valid identifier (variable '' name) uniquely determined by var_id. ' ' i = var_id fd = ed ' L1 = Len(firstIdentifierChars) gd = Len(fb) ' L2 = Len(identifierChars) hd = Len(gb) ' result = Mid(firstIdentifierChars, 1 + i Mod L1, 1) wc = Mid(fb, 1 + fd Mod gd, 1) ' i = Fix(i / L1) fd = Fix(fd / gd) ' Do While (i > 0) Do While (fd > 0) ' result = result & Mid(identifierChars, 1 + i Mod L2, 1) wc = wc & Mid(gb, 1 + fd Mod hd, 1) ' i = Fix(i / L2) fd = Fix(fd / hd) ' Loop Loop ' ' invisible_variable_name = result dd = wc ' 'End Function End Function ' '' Associates an appropriate obfuscated name with each member '' of the invisible names collection '' '' Also excludes names from the invisible names collection '' that are also on the visible names collection. ' 'Private Sub define_obfuscated_names() Private Sub jd() ' Dim iName As Long Dim kd As Long ' Dim vName As String Dim ld As String ' Dim cNew As New Collection Dim md As New Collection ' Dim iObfuscated_Name As Long Dim nd As Long ' ' iObfuscated_Name = 1 nd = 1 ' For iName = 1 To invisible_names.Count For kd = 1 To pb.Count ' If (lookup_identifier(visible_names, invisible_names.Item(iName)) _ = NO_SUCH_ID) Then If (od(ob, pb.Item(kd)) _ = nb) Then ' Do ' keep looking until we get a name that is not on either Do ' ' the visible or invisible list; this loop executes once, on ' ' average, because collisions are unlikely Note: Assuring the ' ' new name isn't on the invisible list is required to avoid ' ' errors when renaming module, class and userform names. ' vName = invisible_variable_name(iObfuscated_Name) ld = dd(nd) ' iObfuscated_Name = iObfuscated_Name + 1 nd = nd + 1 ' Loop Until _ lookup_identifier(visible_names, vName) = NO_SUCH_ID And _ lookup_identifier(invisible_names, vName) = NO_SUCH_ID Loop Until _ od(ob, ld) = nb And _ od(pb, ld) = nb ' add_identifier cNew, invisible_names.Item(iName), vName pd md, pb.Item(kd), ld ' 'else identifier is on visible list, so elide it from invisible list ' End If End If ' Next iName Next kd ' ' Set invisible_names = cNew Set pb = md ' 'End Sub End Sub ' '' returns the (possibly obfuscated, transformed) variable '' name given the original variable name ' 'Private Function var_name(plaintextVarname As String) As String Private Function qd(rd As String) As String ' Dim result As String Dim wc As String ' result = lookup_identifier(invisible_names, LCase(plaintextVarname)) wc = od(pb, LCase(rd)) ' If (result = NO_SUCH_ID) Then If (wc = nb) Then ' ' just keep the original name except converted to lowercase ' result = LCase(plaintextVarname) wc = LCase(rd) ' End If End If ' var_name = result qd = wc 'End Function End Function ' '' clears all of the elements in the lookup table 'Private Sub reset_lookup_table(lookup_table As Collection) Private Sub sd(td As Collection) ' Set lookup_table = New Collection Set td = New Collection 'End Sub End Sub ' '' returns numeric id associated with given name, or NO_SUCH_ID 'Private Function lookup_identifier(c As Collection, sName As String) As String Private Function od(vc As Collection, ud As String) As String ' Dim result As String Dim wc As String ' On Error GoTo not_found On Error GoTo vd ' result = c.Item(LCase(sName)) wc = vc.Item(LCase(ud)) ' GoTo end_of_function GoTo wd 'not_found: vd: ' result = NO_SUCH_ID wc = nb 'end_of_function: wd: ' lookup_identifier = result od = wc 'End Function End Function ' 'Private Sub remove_identifier(c As Collection, sName As String) Private Sub xd(vc As Collection, ud As String) ' If (lookup_identifier(c, sName) <> NO_SUCH_ID) Then If (od(vc, ud) <> nb) Then ' c.Remove LCase(sName) vc.Remove LCase(ud) ' End If End If 'End Sub End Sub ' '' adds the name, value pair to the collection if the name is '' not already on the collection. ' 'Private Sub add_identifier(c As Collection, sName As String, sValue As String) Private Sub pd(vc As Collection, ud As String, yd As String) ' If (NO_SUCH_ID = lookup_identifier(c, sName)) Then If (nb = od(vc, ud)) Then ' c.Add LCase(sValue), LCase(sName) vc.Add LCase(yd), LCase(ud) ' End If End If 'End Sub End Sub ' '' location of the last substring within the given string, or 0 if '' substring doesn't occur within given string. ' 'Private Function last_substring_position(s As String, subS As String) As Integer Private Function zd(tc As String, ae As String) As Integer ' Dim iFound As Integer Dim be As Integer ' Dim iNext As Integer Dim ce As Integer ' ' iFound = 0 be = 0 ' iNext = InStr(1, s, subS) ce = InStr(1, tc, ae) ' Do While (iNext > 0) Do While (ce > 0) ' iFound = iNext be = ce ' iNext = InStr(iFound + 1, s, subS) ce = InStr(be + 1, tc, ae) ' Loop Loop ' ' last_substring_position = iFound zd = be ' 'End Function End Function ' '' location of the event delimiter ("_") within the token, or 0 if none. ' 'Private Function event_delimiter_position(token As String) As Integer Private Function de(ee As String) As Integer ' event_delimiter_position = last_substring_position(token, userform_event_delimiter) de = zd(ee, hb) 'End Function End Function ' '' Returns the part of an event procedure token associated with the '' name of an event. For example, with an event procedure token of '' "myButton_Click", returns "Click" ' '' if the token isn't in the general format of an event procedure name, '' (e.g. it doesn't contain an underscore) it returns NO_SUCH_ID ' 'Private Function event_part(token As String) As String Private Function fe(ee As String) As String ' Dim iPosition As Integer Dim ge As Integer ' Dim result As String Dim wc As String ' ' iPosition = event_delimiter_position(token) ge = de(ee) ' If (iPosition = 0) Then If (ge = 0) Then ' result = NO_SUCH_ID wc = nb ' Else Else ' result = Right(token, Len(token) - (iPosition + Len(userform_event_delimiter) - 1)) wc = Right(ee, Len(ee) - (ge + Len(hb) - 1)) ' End If End If ' ' event_part = result fe = wc ' 'End Function End Function ' '' returns the part of an event procedure name associated with the '' name of the object (e.g. myButton_Click as token would return myButton) ' 'Private Function object_part(token As String) As String Private Function he(ee As String) As String ' Dim iPosition As Integer Dim ge As Integer ' Dim result As String Dim wc As String ' ' iPosition = event_delimiter_position(token) ge = de(ee) ' If (iPosition = 0) Then If (ge = 0) Then ' result = NO_SUCH_ID wc = nb ' Else Else ' result = Left(token, iPosition - 1) wc = Left(ee, ge - 1) ' End If End If ' ' object_part = result he = wc ' 'End Function End Function ' '' does the token represent an event procedure name (e.g. myButton_Click) ? 'Private Function is_event_procedure(token As String) As Boolean Private Function ie(ee As String) As Boolean ' Dim sEvent As String Dim je As String ' Dim result As String Dim wc As String ' sEvent = event_part(token) je = fe(ee) ' If (sEvent = NO_SUCH_ID) Then If (je = nb) Then ' result = False wc = False ' ElseIf (NO_SUCH_ID = lookup_identifier(userform_attribute_names, sEvent)) Then ElseIf (nb = od(qb, je)) Then ' result = False wc = False ' Else Else ' result = True wc = True ' End If End If ' is_event_procedure = result ie = wc 'End Function End Function ' '' does the given string begin with the specified prefix? 'Private Function has_prefix(s As String, prefix As String) As Boolean Private Function ke(tc As String, le As String) As Boolean ' has_prefix = (Left(s, Len(prefix)) = prefix) ke = (Left(tc, Len(le)) = le) 'End Function End Function ' '' does the given string end with the specified suffix? 'Private Function has_suffix(s As String, suffix As String) As Boolean Private Function ne(tc As String, oe As String) As Boolean ' has_suffix = (Right(s, Len(suffix)) = suffix) ne = (Right(tc, Len(oe)) = oe) 'End Function End Function ' '' sets token buffer to the default, "do nothing", token sequence 'Private Sub reset_token_buffer() Private Sub pe() ' Dim i As Integer Dim fd As Integer ' For i = LBound(prevTokens) To UBound(prevTokens) For fd = LBound(mc) To UBound(mc) ' prevTokens(i) = " " ' use whitespace because leading whitespace cannot change how a program is parsed mc(fd) = " " ' Next i ' (the default "" isn't a valid token and can therefore cause problems) Next fd ' tokenIndex = LBound(prevTokens) nc = LBound(mc) 'End Sub End Sub ' '' write the token into the circular token buffer 'Private Sub remember_token(token As String) Private Sub qe(ee As String) ' tokenIndex = (tokenIndex + 1) Mod N_BUFFERED_TOKENS nc = (nc + 1) Mod lc ' prevTokens(tokenIndex) = token mc(nc) = ee 'End Sub End Sub ' '' returns the last token stored in the token buffer 'Private Function last_token() As String Private Function re() As String ' last_token = prevTokens(tokenIndex) re = mc(nc) 'End Function End Function ' '' returns next-to-the-last token stored in the token buffer 'Private Function next_to_last_token() As String Private Function se() As String ' Dim result As String Dim wc As String ' If (tokenIndex = LBound(prevTokens)) Then If (nc = LBound(mc)) Then ' result = prevTokens(UBound(prevTokens)) ' wrap-around to last element wc = mc(UBound(mc)) ' Else Else ' result = prevTokens(tokenIndex - 1) ' no-wrap-around needed wc = mc(nc - 1) ' End If End If ' next_to_last_token = result se = wc 'End Function End Function ' '' does the token represent an attribute (event or property) of a '' control contained on a userform? 'Private Function is_userform_attribute(token As String) As Boolean Private Function te(ee As String) As Boolean ' is_userform_attribute = (NO_SUCH_ID <> lookup_identifier(userform_attribute_names, token)) te = (nb <> od(qb, ee)) 'End Function End Function ' '' adds ids contained in the string to appropriate lookup tables used '' to determine which variable names remain unchanged, and which are '' obfuscated (replaced with variable names meaningless to humans). ' 'Private Sub register_ids(s As String) Private Sub register_ids(tc As String) ' Dim iStart As Integer Dim uc As Integer ' Dim iEnd As Integer Dim yc As Integer ' Dim visible As Boolean Dim visible As Boolean ' Dim obfuscated_id As Long Dim ue As Long ' Dim token As String Dim ee As String ' ' If InStr(1, LCase(s), BEGIN_VISIBLE_KEYWORD) <> 0 Then If InStr(1, LCase(tc), begin_visible_keyword) <> 0 Then ' m_visible_depth = m_visible_depth + 1 mb = mb + 1 ' End If End If ' If InStr(1, LCase(s), END_VISIBLE_KEYWORD) <> 0 Then If InStr(1, LCase(tc), kb) <> 0 Then ' m_visible_depth = m_visible_depth - 1 mb = mb - 1 ' End If End If ' ' If InStr(1, LCase(s), VISIBLE_KEYWORD) > 0 Then If InStr(1, LCase(tc), visible_keyword) > 0 Then ' ' single line #visible keyword makes ids on this line visible, no ' ' matter what our visible depth is ' visible = True visible = True ' Else Else ' ' no line specific keyword, so based on if we are within ' ' a #begin_visible ... #end_visible bracketed region ' visible = m_visible_depth > 0 visible = mb > 0 ' End If End If ' ' iStart = 1 uc = 1 ' Do While (iStart <= Len(s)) Do While (uc <= Len(tc)) ' iEnd = end_of_token(s, iStart) yc = xc(tc, uc) ' token = LCase(Mid(s, iStart, iEnd - iStart)) ee = LCase(Mid(tc, uc, yc - uc)) ' ' If (token_type(token, 1) = TT_IDENTIFIER) Then If (sc(ee, 1) = j) Then ' ' If (last_token() = userform_control_attribute_flag) Then If (re() = ib) Then ' ' token is flagged as representing a userform-related event, ' ' such as Click (or control property such as Caption) ' ' ' ' Example token sequence: "_" followed by "Click" will ' ' register "Click" as a userform attribute. Note that "_Click" ' ' isn't processed as a single token because "_" isn't a valid ' ' first character of a variable name in VBA. ' add_identifier userform_attribute_names, token, token pd qb, ee, ee ' add_identifier visible_names, token, token pd ob, ee, ee ' ElseIf (is_event_procedure(token)) Then ElseIf (ie(ee)) Then ' ' example token: myButton_Click will make itself and myButton visible if _Click is listed in visible_names.txt ' add_identifier visible_names, token, token pd ob, ee, ee ' add_identifier visible_names, object_part(token), object_part(token) pd ob, he(ee), he(ee) ' ElseIf (is_userform_attribute(token) And _ last_token() = object_attribute_delimiter And token_type(next_to_last_token(), 1) = TT_IDENTIFIER) Then ElseIf (te(ee) And _ re() = jb And sc(se(), 1) = j) Then ' ' example: myLabel.Caption will make myLabel a visible name if ' ' "_Caption" is listed in visible_names.txt (the leading _ ' flags Caption as a userform control attribute (event or property)) ' add_identifier visible_names, next_to_last_token(), next_to_last_token() pd ob, se(), se() ' ElseIf (visible) Then ElseIf (visible) Then ' add_identifier visible_names, token, token pd ob, ee, ee ' Else Else ' ' note: if an identifier gets added to both visible and ' ' invisible lists, it will considered visible (and get removed ' ' from the invisible list in a separate step later on). ' add_identifier invisible_names, token, token pd pb, ee, ee ' End If End If ' ' else not an identifier, so it can never be added to lookup tables ' ' used to determine token visibility. ' End If End If ' ' remember_token token ' stores last few token in a circular buffer for easier parsing qe ee ' ' iStart = iEnd uc = yc ' Loop Loop ' 'End Sub End Sub ' '' the length of a string, excluding and leading/trailing double quotes 'Private Function length_sans_quotes(s As String) As Integer Private Function ve(tc As String) As Integer ' Dim result As Integer Dim wc As Integer ' result = Len(s) wc = Len(tc) ' If (has_prefix(s, dQuote)) Then result = result - Len(dQuote) If (ke(tc, x)) Then wc = wc - Len(x) ' If (has_suffix(s, dQuote)) Then result = result - Len(dQuote) If (ne(tc, x)) Then wc = wc - Len(x) ' length_sans_quotes = result ve = wc 'End Function End Function ' '' length of the given prefix within a specified string, or 0 if that '' prefix is not at the beginning of the specified string 'Private Function length_of_prefix(s As String, prefix As String) As Integer Private Function we(tc As String, le As String) As Integer ' Dim result As Integer Dim wc As Integer ' If (has_prefix(s, prefix)) Then If (ke(tc, le)) Then ' result = Len(prefix) wc = Len(le) ' Else Else ' result = 0 wc = 0 ' End If End If ' length_of_prefix = result we = wc 'End Function End Function '' strips leading, trailing, double quotes from a given string '' (if no such quotes present, returns original string) 'Private Function NQ(s As String) As String Private Function xe(tc As String) As String ' NQ = Mid(s, 1 + length_of_prefix(s, dQuote), length_sans_quotes(s)) xe = Mid(tc, 1 + we(tc, x), ve(tc)) 'End Function End Function ' '' adds double quotes around the given string 'Private Function Q(s As String) As String Private Function ye(tc As String) As String ' Q = dQuote & s & dQuote ye = x & tc & x 'End Function End Function ' '' returns an obfuscated, functionally equivalent, source code line '' for the given source code line 'Private Function obfuscated_line(s As String) As String Private Function ze(tc As String) As String ' Dim result As String Dim wc As String ' Dim iStart As Integer Dim uc As Integer ' Dim iEnd As Integer Dim yc As Integer ' Dim token As String Dim ee As String ' ' result = "" wc = "" ' ' iStart = 1 uc = 1 ' ' Do While (iStart <= Len(s)) Do While (uc <= Len(tc)) ' iEnd = end_of_token(s, iStart) yc = xc(tc, uc) ' token = Mid(s, iStart, iEnd - iStart) ee = Mid(tc, uc, yc - uc) ' Select Case (token_type(token, 1)) Select Case (sc(ee, 1)) ' Case TT_IDENTIFIER Case j ' result = result & var_name(token) wc = wc & qd(ee) ' Case TT_WHITESPACE Case n ' result = result & " " wc = wc & " " ' Case TT_NUMBER Case l ' result = result & token wc = wc & ee ' Case TT_STRING Case k ' result = result & token wc = wc & ee ' Case TT_COMMENT Case m ' If (has_prefix(token, doubleComment)) Then If (ke(ee, db)) Then ' ' double comments are retained (for copywrite notices, etc.) ' result = result & Right(token, Len(token) - Len(firstCommentChar)) wc = wc & Right(ee, Len(ee) - Len(cb)) ' ' else just ignore/elide the comment ' End If End If ' Case TT_GUID Case o ' result = result & token wc = wc & ee ' Case TT_OTHER Case p ' result = result & token wc = wc & ee ' Case Else Case Else ' assert False ' should have been type "other" assert False ' End Select End Select ' iStart = iEnd uc = yc ' Loop Loop ' '' trim to drop any leading whitespace (makes lines all flush left) ' obfuscated_line = Trim(result) ze = Trim(wc) ' 'End Function End Function ' '' reads each line from the specified sourcecode file, and '' registers any identifiers contained in the file on the '' appropriate (visible or invisible) lookup table. ' 'Private Sub register_identifiers(fName As String) Private Sub af(cf As String) ' Dim fid As Integer Dim df As Integer ' Dim sLine As String Dim ef As String ' Dim errNo As Long Dim ff As Long ' On Error GoTo error_exit On Error GoTo error_exit ' '' open file for reading ' fid = freefile() df = freefile() ' Open fName For Input As #fid Open cf For Input As #df ' '' read each (possibly continued) line, registering its ids ' Do While Not EOF(fid) Do While Not EOF(df) ' sLine = get_continued_line(fid) ef = gf(df) ' register_ids sLine register_ids ef ' Loop Loop ' ' Close fid Close df ' GoTo end_of_sub GoTo hf 'error_exit: error_exit: ' errNo = Err.Number ff = Err.Number ' On Error Resume Next On Error Resume Next ' Close fid Close df ' Err.Raise errNo Err.Raise ff ' 'end_of_sub: hf: 'End Sub End Sub ' '' is the line one that is continued on the next line (ends in '' the VBA line continuation character sequence, " _") 'Private Function is_continued_line(sLine As String) As Boolean Private Function jf(ef As String) As Boolean ' is_continued_line = has_suffix(sLine, line_continuation_chars) jf = ne(ef, eb) 'End Function End Function ' ' '' adds another line to an existing series of "vbNewLine '' separated" lines, returning the so-extended series of lines. ' 'Private Function add_line(sOld As String, sNew As String) As String Private Function kf(lf As String, mf As String) As String ' Dim result As String Dim wc As String ' If (sOld = "") Then If (lf = "") Then ' result = sNew wc = mf ' Else Else ' result = sOld & vbNewLine & sNew wc = lf & vbNewLine & mf ' End If End If ' add_line = result kf = wc 'End Function End Function ' '' returns a (possibly continued) source code line from the given '' input file. 'Private Function get_continued_line(f_in As Integer) As String Private Function gf(nf As Integer) As String ' Dim result As String Dim wc As String ' Dim sTmp As String Dim pf As String ' result = "" wc = "" ' Do ' read & concatenate continued lines Do ' Line Input #f_in, sTmp Line Input #nf, pf ' result = add_line(result, sTmp) wc = kf(wc, pf) ' Loop Until EOF(f_in) Or Not is_continued_line(sTmp) Loop Until EOF(nf) Or Not jf(pf) ' ' get_continued_line = result gf = wc ' 'End Function End Function ' ' '' obfuscates the given sourcecode file by removing comments, '' replacing meaningful names with meaningless names, etc. ' '' A side benefit: it tends to reduce the size of the source code '' files, due to comment elimination and the fact that '' obfuscated names are usually substantially shorter than '' the original names. ' 'Private Sub obfuscate_sourcecode_file( _ f_plain As String, f_obfuscated As String) Private Sub qf( _ rf As String, sf As String) ' Dim f_in As Integer Dim nf As Integer ' Dim f_out As Integer Dim tf As Integer ' Dim sLine As String Dim ef As String ' Dim sObfuscated As String Dim uf As String ' Dim errNo As Long Dim ff As Long ' On Error GoTo error_exit On Error GoTo error_exit ' ' f_in = freefile() nf = freefile() ' Open f_plain For Input As #f_in Open rf For Input As #nf ' f_out = freefile() tf = freefile() ' Open f_obfuscated For Output As #f_out Open sf For Output As #tf ' '' obfuscate, and then write, each original input source code '' file line into the obfuscated source code output file ' Do While Not EOF(f_in) Do While Not EOF(nf) ' sLine = get_continued_line(f_in) ef = gf(nf) ' sObfuscated = obfuscated_line(sLine) uf = ze(ef) ' If (m_interleave_original_code_as_comments) Then If (rb) Then ' Print #f_out, firstCommentChar & sLine Print #tf, cb & ef ' Print #f_out, sObfuscated ' empty obfuscated lines retained--helpful when debugging. Print #tf, uf ' ElseIf (sObfuscated <> "") Then ElseIf (uf <> "") Then ' Print #f_out, sObfuscated Print #tf, uf ' ' else elide lines that are empty after obfuscation ' End If End If ' ' Loop Loop ' ' Close f_in Close nf ' Close f_out Close tf ' GoTo end_of_sub GoTo hf 'error_exit: error_exit: ' errNo = Err.Number ff = Err.Number ' On Error Resume Next On Error Resume Next ' Close f_in Close nf ' On Error Resume Next On Error Resume Next ' Close f_out Close tf ' Err.Raise errNo Err.Raise ff ' 'end_of_sub: hf: ' 'End Sub End Sub ' '' returns a temporary file name given a file number 'Private Function temp_file_name(wb As Workbook, _ iFile As Integer, Optional extension = ".tmp") As String Private Function vf(wf As Workbook, _ xf As Integer, Optional yf = ".tmp") As String ' temp_file_name = wb.Path & PS() & IB_TEMP_FILENAME_PREFIX & _ CStr(iFile) & extension vf = wf.Path & oc() & ub & _ CStr(xf) & yf 'End Function End Function ' '' returns a random module name suitable for use as a VBA code module 'Private Function random_module_name() As String Private Function zf() As String '' highly unlike this name will conflict with any existing names ' random_module_name = "qzx" & _ Format(10 ^ 6 * Rnd(), "000000") & Format(10 ^ 6 * Rnd(), "000000") zf = "qzx" & _ Format(10 ^ 6 * Rnd(), "000000") & Format(10 ^ 6 * Rnd(), "000000") 'End Function End Function ' '' writes source code in a given VBComponent into a specified file '' (overwrites any existing file contents) ' 'Private Sub write_component_code(vbc As Object, f As String) Private Sub ag(vbc As Object, f As String) ' Dim f_out As Integer Dim tf As Integer ' Dim iLine As Long Dim bg As Long ' Dim errNo As Long Dim ff As Long ' On Error GoTo error_exit On Error GoTo error_exit ' ' f_out = freefile() tf = freefile() ' Open f For Output As #f_out Open f For Output As #tf ' ' For iLine = 1 To vbc.CodeModule.CountOfLines For bg = 1 To vbc.codemodule.countoflines ' Print #f_out, vbc.CodeModule.Lines(startLine:=iLine, Count:=1) Print #tf, vbc.codemodule.Lines(startline:=bg, Count:=1) ' Next iLine Next bg ' ' Close f_out Close tf ' GoTo end_of_sub GoTo hf 'error_exit: error_exit: ' errNo = Err.Number ff = Err.Number ' On Error Resume Next On Error Resume Next ' Close f_out Close tf ' Err.Raise errNo Err.Raise ff ' 'end_of_sub: hf: ' 'End Sub End Sub ' '' reads source code in a given file into the specified component '' (overwrites any existing code in the component) ' 'Private Sub read_component_code(vbc As Object, f As String) Private Sub cg(vbc As Object, f As String) ' Dim f_in As Integer Dim nf As Integer ' Dim sLine As String Dim ef As String ' Dim iLine As Long Dim bg As Long ' Dim errNo As Long Dim ff As Long ' On Error GoTo error_exit On Error GoTo error_exit ' ' vbc.CodeModule.DeleteLines startLine:=1, Count:=vbc.CodeModule.CountOfLines vbc.codemodule.deletelines startline:=1, Count:=vbc.codemodule.countoflines ' '' vbc.CodeModule.AddFromFile has unpleasant side-effects related to module name '' changes, so we just add the lines one at a time instead: ' f_in = freefile() nf = freefile() ' Open f For Input As #f_in Open f For Input As #nf ' iLine = 1 bg = 1 ' Do While Not EOF(f_in) ' read each source code line and insert into component Do While Not EOF(nf) ' Line Input #f_in, sLine Line Input #nf, ef ' vbc.CodeModule.InsertLines iLine, sLine vbc.codemodule.insertlines bg, ef ' iLine = iLine + 1 bg = bg + 1 ' Loop Loop ' ' Close f_in Close nf ' GoTo end_of_sub GoTo hf 'error_exit: error_exit: ' errNo = Err.Number ff = Err.Number ' On Error Resume Next On Error Resume Next ' Close f_in Close nf ' Err.Raise errNo Err.Raise ff ' 'end_of_sub: hf: ' 'End Sub End Sub ' '' obfuscates all VBA source code modules, classes and UserForms ' 'Private Sub obfuscate_workbook(wb As Workbook) Private Sub dg(wf As Workbook) ' Dim vbc As Object Dim vbc As Object ' Dim iFile As Integer Dim xf As Integer ' Dim tmpFile As String Dim eg As String ' Dim old_display_status_bar As Boolean Dim fg As Boolean ' Dim newName As String Dim newname As String ' ' old_display_status_bar = Application.DisplayStatusBar fg = Application.DisplayStatusBar ' Application.DisplayStatusBar = True Application.DisplayStatusBar = True ' ' Application.StatusBar = "Saving Invisibly: initializing..." Application.StatusBar = "Saving Invisibly: initializing..." '' start with empty variable name identifier tables ' reset_lookup_table visible_names sd ob ' reset_lookup_table invisible_names sd pb ' reset_lookup_table userform_attribute_names sd qb ' reset_token_buffer pe '' the E "identifier" appears within numeric literals '' expressed in scientific notation, and thus must never be '' obfuscated (this "non-obfuscation of e" is needed because '' our lexical analysis of numbers is otherwise too simple to '' get numeric literals expressed in scientific notation right). ' register_ids "e '#visible" register_ids "e '#visible" ' '' register all built-in visible identifiers stored in '' a special text file shipped with the application '' (Excel/VBA keywords and user defined universal keywords) ' ' assert Dir(ThisWorkbook.Path & PS() & IB_VISIBLE_KEYWORDS_FILENAME) <> "" assert Dir(ThisWorkbook.Path & oc() & r) <> "" ' m_visible_depth = 1 mb = 1 ' register_identifiers ThisWorkbook.Path & PS() & IB_VISIBLE_KEYWORDS_FILENAME af ThisWorkbook.Path & oc() & r ' m_visible_depth = 0 mb = 0 ' '' first pass: store each code module in a temp file, '' register that file's visible identifiers, and then delete '' the code component. ' ' For iFile = 1 To wb.VBProject.VBComponents.Count For xf = 1 To wf.VBProject.vbcomponents.Count ' Set vbc = wb.VBProject.VBComponents(iFile) Set vbc = wf.VBProject.vbcomponents(xf) ' Select Case vbc.Type Select Case vbc.Type ' Case IB_vbext_ct_StdModule, IB_vbext_ct_ClassModule, IB_vbext_ct_MSForm Case hc, ic, jc ' ' the name of a module, class, or userform is obfuscated ' ' (normal case). Register the name as "invisible" ' m_visible_depth = 0 mb = 0 ' register_ids vbc.Name register_ids vbc.Name ' Case IB_vbext_ct_Document Case kc ' ' document (e.g. Worksheet) code names remain visible because ' ' there isn't an easy way to RELIABLY change them ' ' programmatically (surprisingly, setting vbc.Name doesn't do it) ' register_ids vbc.Name & " '#visible" register_ids vbc.Name & " '#visible" ' Case Else Case Else ' ' if Microsoft adds a new type, play it safe by keeping ' ' names unchanged ("visible"). ' register_ids vbc.Name & " '#visible" register_ids vbc.Name & " '#visible" ' End Select End Select ' Application.StatusBar = "Saving Invisibly: Pass 1 of 2, VBComponent " & CStr(iFile) & " of " & CStr(wb.VBProject.VBComponents.Count) Application.StatusBar = "Saving Invisibly: Pass 1 of 2, VBComponent " & CStr(xf) & " of " & CStr(wf.VBProject.vbcomponents.Count) ' write_component_code vbc, temp_file_name(wb, iFile) ag vbc, vf(wf, xf) ' m_visible_depth = 0 ' invisible unless otherwise noted mb = 0 ' reset_token_buffer pe ' register_identifiers temp_file_name(wb, iFile) af vf(wf, xf) ' Next iFile Next xf ' ' define_obfuscated_names 'choose obscure ids for invisible names jd ' '' second pass obfuscates by replacing registered, non-visible '' variable ids with meaningless ids, stripping comments, etc, and '' then reading the so-obfuscated code back into each component. ' ' tmpFile = temp_file_name(wb, wb.VBProject.VBComponents.Count + 1) eg = vf(wf, wf.VBProject.vbcomponents.Count + 1) ' For iFile = 1 To wb.VBProject.VBComponents.Count For xf = 1 To wf.VBProject.vbcomponents.Count ' Set vbc = wb.VBProject.VBComponents(iFile) Set vbc = wf.VBProject.vbcomponents(xf) ' ' newName = obfuscated_line(vbc.Name) newname = ze(vbc.Name) ' ' this "if" (to prevent changing name when name isn't obfuscated) ' ' was added because I don't trust that name changes in such cases, even to the same ' ' name, are reliable. ' If (LCase(newName) <> LCase(vbc.Name)) Then vbc.Name = newName If (LCase(newname) <> LCase(vbc.Name)) Then vbc.Name = newname ' ' Application.StatusBar = "Saving Invisibly: Pass 2 of 2, VBComponent " & CStr(iFile) & " of " & CStr(wb.VBProject.VBComponents.Count) Application.StatusBar = "Saving Invisibly: Pass 2 of 2, VBComponent " & CStr(xf) & " of " & CStr(wf.VBProject.vbcomponents.Count) ' reset_token_buffer pe ' obfuscate_sourcecode_file temp_file_name(wb, iFile), tmpFile qf vf(wf, xf), eg ' read_component_code vbc, tmpFile cg vbc, eg ' Kill tmpFile Kill eg ' Kill temp_file_name(wb, iFile) Kill vf(wf, xf) ' Next iFile Next xf ' ' Application.StatusBar = False ' restore status bar status quo Application.StatusBar = False ' Application.DisplayStatusBar = old_display_status_bar Application.DisplayStatusBar = fg ' 'End Sub End Sub ' '' If this function returns True, the two strings are guaranteed '' to represent different physical files (regardless of what '' default paths might be added to any file name strings that do not '' have explicitly specified full pathnames) ' 'Private Function are_different_files(f1_in As String, f2_in As String) As Boolean Private Function gg(hg As String, ig As String) As Boolean ' Dim f1 As String Dim jg As String ' Dim f2 As String Dim kg As String ' Dim result As Boolean Dim wc As Boolean ' ' f1 = Trim(LCase(f1_in)) jg = Trim(LCase(hg)) ' f2 = Trim(LCase(f2_in)) kg = Trim(LCase(ig)) ' '' making each filename start with path separator simplifies the tests: ' If (Not has_prefix(f1, PS())) Then f1 = PS() & f1 If (Not ke(jg, oc())) Then jg = oc() & jg ' If (Not has_prefix(f2, PS())) Then f2 = PS() & f2 If (Not ke(kg, oc())) Then kg = oc() & kg ' '' if the last half of either filename string equals the other, '' the filename COULD represent the same physical file ' If (has_suffix(f1, f2) Or has_suffix(f2, f1)) Then If (ne(jg, kg) Or ne(kg, jg)) Then ' result = False wc = False ' Else Else ' ' filenames definitely represent different files ' result = True wc = True ' End If End If ' ' are_different_files = result gg = wc ' 'End Function End Function ' '' obfuscates the given workbook, saving it into the specified file ' 'Public Sub obfuscate_workbook_as(wb As Workbook, fileName As String) Public Sub lg(wf As Workbook, filename As String) ' ' assert are_different_files(wb.fullName, fileName) assert gg(wf.FullName, filename) ' '' saving under a new name breaks connection with original file, '' assuring that original unobfuscated workbook isn't damaged. '' (even if we crash and user then accidentally saves the '' so-damaged workbook, originally named file is still safe) ' wb.SaveAs fileName wf.SaveAs filename ' obfuscate_workbook wb dg wf ' Application.DisplayAlerts = False Application.DisplayAlerts = False ' wb.SaveAs fileName ' save again under the new name wf.SaveAs filename ' Application.DisplayAlerts = True Application.DisplayAlerts = True ' 'End Sub End Sub ' '' default filename in which to store "invisible" version 'Private Function ib_initial_filename(wb As Workbook) As String Private Function mg(wf As Workbook) As String ' Dim dot_position As Integer Dim ng As Integer ' Dim result As String Dim wc As String ' dot_position = last_substring_position(wb.Name, ".") ng = zd(wf.Name, ".") ' If (dot_position = 0) Then If (ng = 0) Then ' result = wb.Path & PS() & wb.Name & IB_FILENAME_SUFFIX wc = wf.Path & oc() & wf.Name & lb ' Else Else ' result = wb.Path & PS() & Left(wb.Name, dot_position - 1) & _ IB_FILENAME_SUFFIX & Right(wb.Name, Len(wb.Name) - (dot_position - 1)) wc = wf.Path & oc() & Left(wf.Name, ng - 1) & _ lb & Right(wf.Name, Len(wf.Name) - (ng - 1)) ' End If End If ' ib_initial_filename = result mg = wc 'End Function End Function ' '' Save the active workbook invisibly in a user-selected workbook ' 'Private Sub ib_save_invisibly_as() Private Sub og() ' Dim fileName As String Dim filename As String ' Dim wb As Workbook Dim wf As Workbook ' On Error GoTo error_exit On Error GoTo error_exit ' ' Set wb = ActiveWorkbook Set wf = ActiveWorkbook ' If (Not wb.saved) Then If (Not wf.saved) Then ' MsgBox "Workbook """ & ActiveWorkbook.Name & """ has unsaved changes. " & _ "To help prevent accidental source code losses, workbooks " & _ "with unsaved changes cannot be saved invisibly. " & vbNewLine & vbNewLine & _ "Save your original workbook, then try again. ", _ vbCritical, "Workbooks with unsaved changes cannot be saved invisibly." MsgBox "Workbook """ & ActiveWorkbook.Name & """ has unsaved changes. " & _ "To help prevent accidental source code losses, workbooks " & _ "with unsaved changes cannot be saved invisibly. " & vbNewLine & vbNewLine & _ "Save your original workbook, then try again. ", _ vbCritical, "Workbooks with unsaved changes cannot be saved invisibly." ' GoTo end_of_sub GoTo hf ' End If End If '' present a "save as" type filename dialog ' fileName = Application.GetSaveAsFilename( _ InitialFilename:=ib_initial_filename(wb), _ FileFilter:="Microsoft Excel Workbook (*.xls),*.xls,All Files (*.*),*.*", _ Title:="Select file into which workbook will be saved invisibly") filename = Application.GetSaveAsFilename( _ initialfilename:=mg(wf), _ filefilter:="Microsoft Excel Workbook (*.xls),*.xls,All Files (*.*),*.*", _ Title:="Select file into which workbook will be saved invisibly") ' '' Because there is too much potential for total code loss, we do not '' allow user to overwrite the original workbook with the obfuscated '' workbook: ' ' If (Not are_different_files(wb.fullName, fileName)) Then If (Not gg(wf.FullName, filename)) Then ' MsgBox "The selected filename (" & fileName & _ ") must be clearly different from the current workbook's filename (" & wb.fullName & _ "). Try again, next time choosing a different name.", _ vbCritical, "Save Invisibly As Filename Must Differ from Original Filename" MsgBox "The selected filename (" & filename & _ ") must be clearly different from the current workbook's filename (" & wf.FullName & _ "). Try again, next time choosing a different name.", _ vbCritical, "Save Invisibly As Filename Must Differ from Original Filename" ' ElseIf (fileName <> "False") Then ElseIf (filename <> "False") Then ' obfuscate_workbook_as wb, fileName lg wf, filename ' End If End If ' ' GoTo end_of_sub GoTo hf 'error_exit: error_exit: ' Application.DisplayAlerts = True Application.DisplayAlerts = True ' Application.StatusBar = False ' resume default status bar behavior Application.StatusBar = False ' MsgBox "Error #" & CStr(Err.Number) & " during ""Save Invisibly As"": " & Err.Description, _ vbCritical, "Invisible Basic Save Invisibly As Error" MsgBox "Error #" & CStr(Err.Number) & " during ""Save Invisibly As"": " & Err.Description, _ vbCritical, "Invisible Basic Save Invisibly As Error" 'end_of_sub: hf: ' ' 'End Sub End Sub ' '' Top level "Save Invisibly As..." command ' 'Public Sub invisible_basic_save_invisibly_as() Public Sub pg() ' m_interleave_original_code_as_comments = False rb = False ' ib_save_invisibly_as og 'End Sub End Sub ' ' '' Top level "Debugging Save Invisibly As..." command ' 'Public Sub invisible_basic_debugging_save_invisibly_as() Public Sub qg() ' m_interleave_original_code_as_comments = True rb = True ' ib_save_invisibly_as og 'End Sub End Sub ' 'Private Sub visit_url(url As String) Private Sub rg(sg As String) ' ShellExecute GetDesktopWindow(), "Open", url, 0, 0, SW_SHOWMAXIMIZED xb vb(), "Open", sg, 0, 0, fc 'End Sub End Sub ' '' Just shows the HTML file that contains the InvisibleBasic help file 'Public Sub invisible_basic_show_help() Public Sub tg() ' visit_url ThisWorkbook.Path & PS() & "InvisibleBasic.html" rg ThisWorkbook.Path & oc() & "InvisibleBasic.html" 'End Sub End Sub ' 'Public Sub invisible_basic_add_menu() Public Sub d() ' Dim cbp As CommandBarPopup ' new invisible basic menu bar Dim cbp As ug ' Dim cbb As CommandBarButton ' new menu item added to this bar Dim cbb As vg ' ' Call invisible_basic_remove_menu ' to prevent adding menu twice Call g ' ' Set cbp = Application.CommandBars(IB_NameOfExcelWorksheetMenubar).Controls.Add( _ Type:=msoControlPopup) Set cbp = Application.wg(tb).xg.Add( _ Type:=yg) ' ' cbp.caption = IB_NameOfInvisibleBasicMenu cbp.caption = sb ' cbp.tooltiptext = _ "Source code obfuscation utility for Excel/VBA applications." cbp.zg = _ "Source code obfuscation utility for Excel/VBA applications." ' ' Set cbb = cbp.Controls.Add(Type:=msoControlButton) Set cbb = cbp.xg.Add(Type:=ah) ' cbb.caption = "&Save Invisibly As..." cbb.caption = "&Save Invisibly As..." ' cbb.DescriptionText = "Saves copy of workbook whose VBA code is replaced with equivalant, but hard-to-read, code." cbb.bh = "Saves copy of workbook whose VBA code is replaced with equivalant, but hard-to-read, code." ' cbb.onAction = "invisible_basic_save_invisibly_as" cbb.OnAction = "invisible_basic_save_invisibly_as" ' ' Set cbb = cbp.Controls.Add(Type:=msoControlButton) Set cbb = cbp.xg.Add(Type:=ah) ' cbb.caption = "&Debugging Save Invisibly As..." cbb.caption = "&Debugging Save Invisibly As..." ' cbb.DescriptionText = "Same as Save Invisibly As except interleaves original source code as comments (for debugging)." cbb.bh = "Same as Save Invisibly As except interleaves original source code as comments (for debugging)." ' cbb.onAction = "invisible_basic_debugging_save_invisibly_as" cbb.OnAction = "invisible_basic_debugging_save_invisibly_as" ' ' Set cbb = cbp.Controls.Add(Type:=msoControlButton) Set cbb = cbp.xg.Add(Type:=ah) ' cbb.caption = "&Help..." cbb.caption = "&Help..." ' cbb.DescriptionText = "Invisible Basic Help" cbb.bh = "Invisible Basic Help" ' cbb.onAction = "invisible_basic_show_help" cbb.OnAction = "invisible_basic_show_help" ' 'End Sub End Sub ' 'Public Sub invisible_basic_remove_menu() Public Sub g() ' On Error Resume Next On Error Resume Next ' Application.CommandBars(IB_NameOfExcelWorksheetMenubar).Controls( _ IB_NameOfInvisibleBasicMenu).Delete Application.wg(tb).xg( _ sb).Delete 'End Sub End Sub ' '' Simple test of Invisible Basic. Test requires that the test '' workbook, IB_Test.xls, be in the same folder as the Add-in is. '' '' The test makes the test workbook invisible, then runs a test '' routine within the (then obfuscated) test workbook. '' '' You may see two "OK to overwrite" prompts (answer Yes) '' and you should see "Hello Invisible Basic" (four times) '' if the test passes. If you don't see "Hello Visible Basic", '' four times, the test has failed. '' ' 'Public Sub ib_test() Public Sub ch() ' Dim wb As Workbook Dim wf As Workbook ' Dim fTest As String Dim dh As String ' Dim fObf As String Dim eh As String ' Dim iPass As Integer Dim fh As Integer ' ' assert event_part("myButton_Click") = "Click" assert fe("myButton_Click") = "Click" ' assert object_part("myButton_Click") = "myButton" assert he("myButton_Click") = "myButton" ' assert event_part("myButtonClick") = "" assert fe("myButtonClick") = "" ' assert event_part("myButton_20_Click") = "Click" assert fe("myButton_20_Click") = "Click" ' assert object_part("myButton_20_Click") = "myButton_20" assert he("myButton_20_Click") = "myButton_20" ' ' For iPass = 1 To 2 For fh = 1 To 2 ' ' If (iPass = 1) Then If (fh = 1) Then ' invisiblebasic.interleave_original_code_as_comments = False h.pc = False ' Else Else ' invisiblebasic.interleave_original_code_as_comments = True h.pc = True ' End If End If ' ' fTest = ThisWorkbook.Path & PS() & "IB_Test.xls" dh = ThisWorkbook.Path & oc() & "IB_Test.xls" ' fObf = ThisWorkbook.Path & PS() & "IB_Test_Obf.xls" eh = ThisWorkbook.Path & oc() & "IB_Test_Obf.xls" '' Open the test workbook ' Workbooks.Open fTest Workbooks.Open dh ' Set wb = Workbooks(Workbooks.Count) Set wf = Workbooks(Workbooks.Count) ' ' ' Save it invisibly as a new workbook ' ' obfuscate_workbook_as wb, fObf lg wf, eh ' ' ' the test module exercies code in the obfuscated modules in IB_Test ' ' and compares results with expected results. ' ' Evaluate "IBTest.testModule.ibt_test()" Evaluate "IBTest.testModule.ibt_test()" ' ' wb.Close SaveChanges:=False wf.Close savechanges:=False ' ' Next iPass Next fh 'End Sub End Sub ' ' ' ' ' '