2011-09-22 10:55:30 +08:00
/ *
2012-01-10 06:02:47 +08:00
Copyright ( C ) 2011 - 2012 de4dot @gmail . com
2011-09-22 10:55:30 +08:00
This file is part of de4dot .
de4dot is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
de4dot is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with de4dot . If not , see < http : //www.gnu.org/licenses/>.
* /
using System ;
using System.IO ;
using System.Collections.Generic ;
2012-11-11 12:31:11 +08:00
using dot10.DotNet ;
2012-12-01 10:24:12 +08:00
using dot10.DotNet.Writer ;
2011-12-09 16:02:06 +08:00
using de4dot.code ;
using de4dot.code.deobfuscators ;
using de4dot.code.AssemblyClient ;
2012-12-07 22:06:38 +08:00
using de4dot.code.renamer ;
2011-09-22 10:55:30 +08:00
2011-12-09 16:02:06 +08:00
namespace de4dot.cui {
2011-09-22 10:55:30 +08:00
class CommandLineParser {
static Infos stringDecrypterTypes = new Infos ( ) ;
ObfuscatedFile . Options newFileOptions = null ;
IList < IObfuscatedFile > files = new List < IObfuscatedFile > ( ) ;
Dictionary < string , Option > optionsDict = new Dictionary < string , Option > ( StringComparer . Ordinal ) ;
IList < IDeobfuscatorInfo > deobfuscatorInfos ;
IList < Option > miscOptions = new List < Option > ( ) ;
IList < Option > fileOptions = new List < Option > ( ) ;
Option defaultOption ;
FilesDeobfuscator . Options filesOptions ;
FilesDeobfuscator . SearchDir searchDir ;
DecrypterType ? defaultStringDecrypterType ;
List < string > defaultStringDecrypterMethods = new List < string > ( ) ;
class Info {
public object value ;
public string name ;
public string desc ;
public Info ( object value , string name , string desc ) {
this . value = value ;
this . name = name ;
this . desc = desc ;
}
}
class Infos {
List < Info > infos = new List < Info > ( ) ;
public void add ( object value , string name , string desc ) {
infos . Add ( new Info ( value , name , desc ) ) ;
}
public IEnumerable < Info > getInfos ( ) {
return infos ;
}
public bool getValue ( string name , out object value ) {
foreach ( var info in infos ) {
if ( name . Equals ( info . name , StringComparison . OrdinalIgnoreCase ) ) {
value = info . value ;
return true ;
}
}
value = null ;
return false ;
}
}
static CommandLineParser ( ) {
stringDecrypterTypes . add ( DecrypterType . None , "none" , "Don't decrypt strings" ) ;
2011-09-28 22:06:10 +08:00
stringDecrypterTypes . add ( DecrypterType . Default , "default" , "Use default string decrypter type (usually static)" ) ;
2011-09-22 10:55:30 +08:00
stringDecrypterTypes . add ( DecrypterType . Static , "static" , "Use static string decrypter if available" ) ;
stringDecrypterTypes . add ( DecrypterType . Delegate , "delegate" , "Use a delegate to call the real string decrypter" ) ;
2011-09-24 16:26:29 +08:00
stringDecrypterTypes . add ( DecrypterType . Emulate , "emulate" , "Call real string decrypter and emulate certain instructions" ) ;
2011-09-22 10:55:30 +08:00
}
public CommandLineParser ( IList < IDeobfuscatorInfo > deobfuscatorInfos , FilesDeobfuscator . Options filesOptions ) {
this . deobfuscatorInfos = deobfuscatorInfos ;
this . filesOptions = filesOptions ;
this . filesOptions . DeobfuscatorInfos = deobfuscatorInfos ;
this . filesOptions . AssemblyClientFactory = new NewAppDomainAssemblyClientFactory ( ) ;
addAllOptions ( ) ;
}
void addAllOptions ( ) {
miscOptions . Add ( new OneArgOption ( "r" , null , "Scan for .NET files in all subdirs" , "dir" , ( val ) = > {
addSearchDir ( ) ;
searchDir = new FilesDeobfuscator . SearchDir ( ) ;
2011-11-05 16:56:51 +08:00
if ( ! Utils . pathExists ( val ) )
2011-09-22 10:55:30 +08:00
exitError ( string . Format ( "Directory {0} does not exist" , val ) ) ;
searchDir . InputDirectory = val ;
} ) ) ;
miscOptions . Add ( new OneArgOption ( "ro" , null , "Output base dir for recursively found files" , "dir" , ( val ) = > {
if ( searchDir = = null )
exitError ( "Missing -r option" ) ;
searchDir . OutputDirectory = val ;
} ) ) ;
2011-10-08 18:30:35 +08:00
miscOptions . Add ( new NoArgOption ( "ru" , null , "Skip recursively found files with unsupported obfuscator" , ( ) = > {
2011-09-22 10:55:30 +08:00
if ( searchDir = = null )
exitError ( "Missing -r option" ) ;
searchDir . SkipUnknownObfuscators = true ;
} ) ) ;
miscOptions . Add ( new NoArgOption ( "d" , null , "Detect obfuscators and exit" , ( ) = > {
filesOptions . DetectObfuscators = true ;
} ) ) ;
2011-11-05 15:43:40 +08:00
miscOptions . Add ( new OneArgOption ( null , "asm-path" , "Add an assembly search path" , "path" , ( val ) = > {
2012-11-02 15:23:20 +08:00
TheAssemblyResolver . Instance . addSearchDirectory ( val ) ;
2011-09-22 10:55:30 +08:00
} ) ) ;
miscOptions . Add ( new NoArgOption ( null , "dont-rename" , "Don't rename classes, methods, etc." , ( ) = > {
filesOptions . RenameSymbols = false ;
2012-12-07 22:06:38 +08:00
filesOptions . RenamerFlags = 0 ;
2011-09-22 10:55:30 +08:00
} ) ) ;
2012-12-01 11:35:39 +08:00
miscOptions . Add ( new OneArgOption ( null , "keep-names" , "Don't rename n(amespaces), t(ypes), p(rops), e(vents), f(ields), m(ethods), a(rgs), g(enericparams), d(elegate fields). Can be combined, eg. efm" , "flags" , ( val ) = > {
2012-12-01 09:12:13 +08:00
foreach ( var c in val ) {
switch ( c ) {
2012-12-07 22:06:38 +08:00
case 'n' : filesOptions . RenamerFlags & = ~ RenamerFlags . RenameNamespaces ; break ;
case 't' : filesOptions . RenamerFlags & = ~ RenamerFlags . RenameTypes ; break ;
case 'p' : filesOptions . RenamerFlags & = ~ RenamerFlags . RenameProperties ; break ;
case 'e' : filesOptions . RenamerFlags & = ~ RenamerFlags . RenameEvents ; break ;
case 'f' : filesOptions . RenamerFlags & = ~ RenamerFlags . RenameFields ; break ;
case 'm' : filesOptions . RenamerFlags & = ~ RenamerFlags . RenameMethods ; break ;
case 'a' : filesOptions . RenamerFlags & = ~ RenamerFlags . RenameMethodArgs ; break ;
case 'g' : filesOptions . RenamerFlags & = ~ RenamerFlags . RenameGenericParams ; break ;
case 'd' : filesOptions . RenamerFlags | = RenamerFlags . DontRenameDelegateFields ; break ;
2012-12-01 09:12:13 +08:00
default : throw new UserException ( string . Format ( "Unrecognized --keep-names char: '{0}'" , c ) ) ;
}
}
} ) ) ;
2012-12-01 09:22:59 +08:00
miscOptions . Add ( new NoArgOption ( null , "dont-create-params" , "Don't create method params when renaming" , ( ) = > {
2012-12-07 22:06:38 +08:00
filesOptions . RenamerFlags | = RenamerFlags . DontCreateNewParamDefs ;
2012-12-01 09:22:59 +08:00
} ) ) ;
2011-11-23 12:45:30 +08:00
miscOptions . Add ( new NoArgOption ( null , "dont-restore-props" , "Don't restore properties/events" , ( ) = > {
2012-12-07 22:06:38 +08:00
filesOptions . RenamerFlags & = ~ ( RenamerFlags . RestorePropertiesFromNames | RenamerFlags . RestoreEventsFromNames ) ;
2011-11-23 12:45:30 +08:00
} ) ) ;
2011-09-22 10:55:30 +08:00
miscOptions . Add ( new OneArgOption ( null , "default-strtyp" , "Default string decrypter type" , "type" , ( val ) = > {
object decrypterType ;
if ( ! stringDecrypterTypes . getValue ( val , out decrypterType ) )
exitError ( string . Format ( "Invalid string decrypter type '{0}'" , val ) ) ;
defaultStringDecrypterType = ( DecrypterType ) decrypterType ;
} ) ) ;
miscOptions . Add ( new OneArgOption ( null , "default-strtok" , "Default string decrypter method token or [type::][name][(args,...)]" , "method" , ( val ) = > {
defaultStringDecrypterMethods . Add ( val ) ;
} ) ) ;
2011-10-21 16:38:27 +08:00
miscOptions . Add ( new NoArgOption ( null , "no-cflow-deob" , "No control flow deobfuscation (NOT recommended)" , ( ) = > {
2011-09-22 10:55:30 +08:00
filesOptions . ControlFlowDeobfuscation = false ;
} ) ) ;
miscOptions . Add ( new NoArgOption ( null , "load-new-process" , "Load executed assemblies into a new process" , ( ) = > {
filesOptions . AssemblyClientFactory = new NewProcessAssemblyClientFactory ( ) ;
} ) ) ;
miscOptions . Add ( new NoArgOption ( null , "keep-types" , "Keep obfuscator types, fields, methods" , ( ) = > {
filesOptions . KeepObfuscatorTypes = true ;
} ) ) ;
2012-12-01 10:24:12 +08:00
miscOptions . Add ( new NoArgOption ( null , "preserve-tokens" , "Preserve important tokens, #US, #Blob, extra sig data" , ( ) = > {
filesOptions . MetaDataFlags | = MetaDataFlags . PreserveRids |
MetaDataFlags . PreserveUSOffsets |
MetaDataFlags . PreserveBlobOffsets |
MetaDataFlags . PreserveExtraSignatureData ;
} ) ) ;
2012-12-08 19:13:21 +08:00
miscOptions . Add ( new OneArgOption ( null , "preserve-table" , "Preserve rids in table: tr (TypeRef), td (TypeDef), fd (Field), md (Method), pd (Param), mr (MemberRef), s (StandAloneSig), ed (Event), pr (Property), ts (TypeSpec), ms (MethodSpec), all (all previous tables). Use - to disable (eg. all,-pd). Can be combined: ed,fd,md" , "flags" , ( val ) = > {
foreach ( var t in val . Split ( ',' ) ) {
var s = t . Trim ( ) ;
if ( s . Length = = 0 )
continue ;
bool clear = s [ 0 ] = = '-' ;
if ( clear )
s = s . Substring ( 1 ) ;
MetaDataFlags flag ;
2012-12-01 10:24:12 +08:00
switch ( s . Trim ( ) ) {
2012-12-08 19:13:21 +08:00
case "" : flag = 0 ; break ;
case "all" : flag = MetaDataFlags . PreserveRids ; break ;
case "tr" : flag = MetaDataFlags . PreserveTypeRefRids ; break ;
case "td" : flag = MetaDataFlags . PreserveTypeDefRids ; break ;
case "fd" : flag = MetaDataFlags . PreserveFieldRids ; break ;
case "md" : flag = MetaDataFlags . PreserveMethodRids ; break ;
case "pd" : flag = MetaDataFlags . PreserveParamRids ; break ;
case "mr" : flag = MetaDataFlags . PreserveMemberRefRids ; break ;
case "s" : flag = MetaDataFlags . PreserveStandAloneSigRids ; break ;
case "ed" : flag = MetaDataFlags . PreserveEventRids ; break ;
case "pr" : flag = MetaDataFlags . PreservePropertyRids ; break ;
case "ts" : flag = MetaDataFlags . PreserveTypeSpecRids ; break ;
case "ms" : flag = MetaDataFlags . PreserveMethodSpecRids ; break ;
2012-12-01 10:24:12 +08:00
default : throw new UserException ( string . Format ( "Invalid --preserve-table option: {0}" , s ) ) ;
}
2012-12-08 19:13:21 +08:00
if ( clear )
filesOptions . MetaDataFlags & = ~ flag ;
else
filesOptions . MetaDataFlags | = flag ;
2012-12-01 10:24:12 +08:00
}
} ) ) ;
miscOptions . Add ( new NoArgOption ( null , "preserve-strings" , "Preserve #Strings heap offsets" , ( ) = > {
filesOptions . MetaDataFlags | = MetaDataFlags . PreserveStringsOffsets ;
} ) ) ;
miscOptions . Add ( new NoArgOption ( null , "preserve-us" , "Preserve #US heap offsets" , ( ) = > {
filesOptions . MetaDataFlags | = MetaDataFlags . PreserveUSOffsets ;
} ) ) ;
miscOptions . Add ( new NoArgOption ( null , "preserve-blob" , "Preserve #Blob heap offsets" , ( ) = > {
filesOptions . MetaDataFlags | = MetaDataFlags . PreserveBlobOffsets ;
} ) ) ;
miscOptions . Add ( new NoArgOption ( null , "preserve-sig-data" , "Preserve extra data at the end of signatures" , ( ) = > {
filesOptions . MetaDataFlags | = MetaDataFlags . PreserveExtraSignatureData ;
} ) ) ;
2011-09-28 07:19:19 +08:00
miscOptions . Add ( new NoArgOption ( null , "one-file" , "Deobfuscate one file at a time" , ( ) = > {
filesOptions . OneFileAtATime = true ;
} ) ) ;
2011-09-22 10:55:30 +08:00
miscOptions . Add ( new NoArgOption ( "v" , null , "Verbose" , ( ) = > {
2012-11-11 12:31:11 +08:00
Logger . Instance . MaxLoggerEvent = LoggerEvent . Verbose ;
2012-11-18 10:02:12 +08:00
Logger . Instance . CanIgnoreMessages = false ;
2011-09-22 10:55:30 +08:00
} ) ) ;
miscOptions . Add ( new NoArgOption ( "vv" , null , "Very verbose" , ( ) = > {
2012-11-11 12:31:11 +08:00
Logger . Instance . MaxLoggerEvent = LoggerEvent . VeryVerbose ;
2012-11-18 10:02:12 +08:00
Logger . Instance . CanIgnoreMessages = false ;
2011-09-22 10:55:30 +08:00
} ) ) ;
miscOptions . Add ( new NoArgOption ( "h" , "help" , "Show this help message" , ( ) = > {
usage ( ) ;
exit ( 0 ) ;
} ) ) ;
defaultOption = new OneArgOption ( "f" , null , "Name of .NET file" , "file" , ( val ) = > {
addFile ( ) ;
2011-11-05 16:56:51 +08:00
if ( ! Utils . fileExists ( val ) )
2011-09-22 10:55:30 +08:00
exitError ( string . Format ( "File \"{0}\" does not exist." , val ) ) ;
newFileOptions = new ObfuscatedFile . Options {
Filename = val ,
ControlFlowDeobfuscation = filesOptions . ControlFlowDeobfuscation ,
KeepObfuscatorTypes = filesOptions . KeepObfuscatorTypes ,
2012-12-01 10:24:12 +08:00
MetaDataFlags = filesOptions . MetaDataFlags ,
2012-12-07 22:06:38 +08:00
RenamerFlags = filesOptions . RenamerFlags ,
2011-09-22 10:55:30 +08:00
} ;
if ( defaultStringDecrypterType ! = null )
newFileOptions . StringDecrypterType = defaultStringDecrypterType . Value ;
newFileOptions . StringDecrypterMethods . AddRange ( defaultStringDecrypterMethods ) ;
} ) ;
fileOptions . Add ( defaultOption ) ;
fileOptions . Add ( new OneArgOption ( "o" , null , "Name of output file" , "file" , ( val ) = > {
if ( newFileOptions = = null )
exitError ( "Missing input file" ) ;
if ( string . Equals ( Utils . getFullPath ( newFileOptions . Filename ) , Utils . getFullPath ( val ) , StringComparison . OrdinalIgnoreCase ) )
exitError ( string . Format ( "Output file can't be same as input file ({0})" , val ) ) ;
newFileOptions . NewFilename = val ;
} ) ) ;
fileOptions . Add ( new OneArgOption ( "p" , null , "Obfuscator type (see below)" , "type" , ( val ) = > {
if ( newFileOptions = = null )
exitError ( "Missing input file" ) ;
if ( ! isValidObfuscatorType ( val ) )
exitError ( string . Format ( "Invalid obfuscator type '{0}'" , val ) ) ;
newFileOptions . ForcedObfuscatorType = val ;
} ) ) ;
fileOptions . Add ( new OneArgOption ( null , "strtyp" , "String decrypter type" , "type" , ( val ) = > {
if ( newFileOptions = = null )
exitError ( "Missing input file" ) ;
object decrypterType ;
if ( ! stringDecrypterTypes . getValue ( val , out decrypterType ) )
exitError ( string . Format ( "Invalid string decrypter type '{0}'" , val ) ) ;
newFileOptions . StringDecrypterType = ( DecrypterType ) decrypterType ;
} ) ) ;
fileOptions . Add ( new OneArgOption ( null , "strtok" , "String decrypter method token or [type::][name][(args,...)]" , "method" , ( val ) = > {
if ( newFileOptions = = null )
exitError ( "Missing input file" ) ;
newFileOptions . StringDecrypterMethods . Add ( val ) ;
} ) ) ;
addOptions ( miscOptions ) ;
addOptions ( fileOptions ) ;
foreach ( var info in deobfuscatorInfos )
addOptions ( info . getOptions ( ) ) ;
}
void addOptions ( IEnumerable < Option > options ) {
foreach ( var option in options ) {
addOption ( option , option . ShortName ) ;
addOption ( option , option . LongName ) ;
}
}
void addOption ( Option option , string name ) {
if ( name = = null )
return ;
if ( optionsDict . ContainsKey ( name ) )
throw new ApplicationException ( string . Format ( "Option {0} is present twice!" , name ) ) ;
optionsDict [ name ] = option ;
}
public void parse ( string [ ] args ) {
if ( args . Length = = 0 ) {
usage ( ) ;
exit ( 1 ) ;
}
for ( int i = 0 ; i < args . Length ; i + + ) {
var arg = args [ i ] ;
string val = null ;
Option option ;
if ( optionsDict . TryGetValue ( arg , out option ) ) {
if ( option . NeedArgument ) {
if ( + + i > = args . Length )
exitError ( "Missing options value" ) ;
val = args [ i ] ;
}
}
else {
option = defaultOption ;
val = arg ;
}
string errorString ;
if ( ! option . set ( val , out errorString ) )
exitError ( errorString ) ;
}
addFile ( ) ;
addSearchDir ( ) ;
filesOptions . Files = files ;
filesOptions . DefaultStringDecrypterMethods . AddRange ( defaultStringDecrypterMethods ) ;
filesOptions . DefaultStringDecrypterType = defaultStringDecrypterType ;
}
void addFile ( ) {
if ( newFileOptions = = null )
return ;
2012-11-05 03:06:58 +08:00
files . Add ( new ObfuscatedFile ( newFileOptions , filesOptions . ModuleContext , filesOptions . AssemblyClientFactory ) ) ;
2011-09-22 10:55:30 +08:00
newFileOptions = null ;
}
void addSearchDir ( ) {
if ( searchDir = = null )
return ;
filesOptions . SearchDirs . Add ( searchDir ) ;
searchDir = null ;
}
bool isValidObfuscatorType ( string type ) {
foreach ( var info in deobfuscatorInfos ) {
if ( string . Equals ( info . Type , type , StringComparison . OrdinalIgnoreCase ) )
return true ;
}
return false ;
}
void exitError ( string msg ) {
usage ( ) ;
2012-11-18 10:20:40 +08:00
Logger . Instance . LogErrorDontIgnore ( "\n\nERROR: {0}\n" , msg ) ;
2011-09-22 10:55:30 +08:00
exit ( 2 ) ;
}
void exit ( int exitCode ) {
2012-02-15 04:51:31 +08:00
throw new ExitException ( exitCode ) ;
2011-09-22 10:55:30 +08:00
}
void usage ( ) {
string progName = getProgramBaseName ( ) ;
2012-11-11 12:31:11 +08:00
Logger . n ( "Some of the advanced options may be incompatible, causing a nice exception." ) ;
Logger . n ( "With great power comes great responsibility." ) ;
Logger . n ( "" ) ;
Logger . n ( "{0} <options> <file options>" , progName ) ;
Logger . n ( "Options:" ) ;
2011-09-22 10:55:30 +08:00
foreach ( var option in miscOptions )
printOption ( option ) ;
2012-11-11 12:31:11 +08:00
Logger . n ( "" ) ;
Logger . n ( "File options:" ) ;
2011-09-22 10:55:30 +08:00
foreach ( var option in fileOptions )
printOption ( option ) ;
2012-11-11 12:31:11 +08:00
Logger . n ( "" ) ;
Logger . n ( "Deobfuscator options:" ) ;
2011-09-22 10:55:30 +08:00
foreach ( var info in deobfuscatorInfos ) {
2012-11-11 12:31:11 +08:00
Logger . n ( "Type {0} ({1})" , info . Type , info . Name ) ;
2011-09-22 10:55:30 +08:00
foreach ( var option in info . getOptions ( ) )
printOption ( option ) ;
2012-11-11 12:31:11 +08:00
Logger . n ( "" ) ;
2011-09-22 10:55:30 +08:00
}
printInfos ( "String decrypter types" , stringDecrypterTypes ) ;
2012-11-11 12:31:11 +08:00
Logger . n ( "" ) ;
Logger . n ( "Multiple regexes can be used if separated by '{0}'." , NameRegexes . regexSeparatorChar ) ;
Logger . n ( "Use '{0}' if you want to invert the regex. Example: {0}^[a-z\\d]{{1,2}}${1}{0}^[A-Z]_\\d+${1}^[\\w.]+$" , NameRegex . invertChar , NameRegexes . regexSeparatorChar ) ;
Logger . n ( "" ) ;
Logger . n ( "Examples:" ) ;
Logger . n ( "{0} -r c:\\my\\files -ro c:\\my\\output" , progName ) ;
Logger . n ( "{0} file1 file2 file3" , progName ) ;
Logger . n ( "{0} file1 -f file2 -o file2.out -f file3 -o file3.out" , progName ) ;
Logger . n ( "{0} file1 --strtyp delegate --strtok 06000123" , progName ) ;
2011-09-22 10:55:30 +08:00
}
string getProgramBaseName ( ) {
return Utils . getBaseName ( Environment . GetCommandLineArgs ( ) [ 0 ] ) ;
}
void printInfos ( string desc , Infos infos ) {
2012-11-11 12:31:11 +08:00
Logger . n ( "{0}" , desc ) ;
2011-09-22 10:55:30 +08:00
foreach ( var info in infos . getInfos ( ) )
printOptionAndExplanation ( info . name , info . desc ) ;
}
void printOption ( Option option ) {
string defaultAndDesc ;
if ( option . NeedArgument & & option . Default ! = null )
defaultAndDesc = string . Format ( "{0} ({1})" , option . Description , option . Default ) ;
else
defaultAndDesc = option . Description ;
printOptionAndExplanation ( getOptionAndArgName ( option , option . ShortName ? ? option . LongName ) , defaultAndDesc ) ;
if ( option . ShortName ! = null & & option . LongName ! = null )
printOptionAndExplanation ( option . LongName , string . Format ( "Same as {0}" , option . ShortName ) ) ;
}
void printOptionAndExplanation ( string option , string explanation ) {
const int maxCols = 16 ;
const string prefix = " " ;
string left = string . Format ( string . Format ( "{{0,-{0}}}" , maxCols ) , option ) ;
if ( option . Length > maxCols ) {
2012-11-11 12:31:11 +08:00
Logger . n ( "{0}{1}" , prefix , left ) ;
Logger . n ( "{0}{1} {2}" , prefix , new string ( ' ' , maxCols ) , explanation ) ;
2011-09-22 10:55:30 +08:00
}
else
2012-11-11 12:31:11 +08:00
Logger . n ( "{0}{1} {2}" , prefix , left , explanation ) ;
2011-09-22 10:55:30 +08:00
}
string getOptionAndArgName ( Option option , string optionName ) {
if ( option . NeedArgument )
return optionName + " " + option . ArgumentValueName . ToUpperInvariant ( ) ;
else
return optionName ;
}
}
}