From 3ae1954901839cc949903eef39bfdfb6799ccc9e Mon Sep 17 00:00:00 2001 From: enusbaum Date: Sun, 3 Mar 2019 19:09:41 -0500 Subject: [PATCH] v1.5 Checking in latest changes for v1.5 --- .../Analysis/Artifacts/ModuleDefinition.cs | 1 + MBBSDASM/Analysis/Assets/DOSCALLS_def.json | 892 +++++++++++++++++ MBBSDASM/Analysis/Assets/GALGSBL_def.json | 460 ++++++--- MBBSDASM/Analysis/Assets/MajorBBS_def.json | 49 +- MBBSDASM/Analysis/{Analyzer.cs => MBBS.cs} | 54 +- MBBSDASM/Artifacts/StringRecord.cs | 12 +- MBBSDASM/Constants.cs | 10 + MBBSDASM/Dasm/Disassembler.cs | 937 ++++++++++-------- MBBSDASM/Dasm/DisassemblyLine.cs | 2 +- MBBSDASM/Logging/CustomLogger.cs | 24 + MBBSDASM/MBBSDASM.csproj | 12 +- MBBSDASM/Program.cs | 270 +---- MBBSDASM/Renderer/impl/StringRenderer.cs | 211 ++++ MBBSDASM/UI/IUserInterface.cs | 12 + MBBSDASM/UI/impl/ConsoleUI.cs | 177 ++++ MBBSDASM/UI/impl/InteractiveUI.cs | 178 ++++ changelog.md | 14 + mbbsdasm_ui.png | Bin 0 -> 25618 bytes readme.md | 19 +- 19 files changed, 2431 insertions(+), 903 deletions(-) create mode 100644 MBBSDASM/Analysis/Assets/DOSCALLS_def.json rename MBBSDASM/Analysis/{Analyzer.cs => MBBS.cs} (88%) create mode 100644 MBBSDASM/Constants.cs create mode 100644 MBBSDASM/Logging/CustomLogger.cs create mode 100644 MBBSDASM/Renderer/impl/StringRenderer.cs create mode 100644 MBBSDASM/UI/IUserInterface.cs create mode 100644 MBBSDASM/UI/impl/ConsoleUI.cs create mode 100644 MBBSDASM/UI/impl/InteractiveUI.cs create mode 100644 mbbsdasm_ui.png diff --git a/MBBSDASM/Analysis/Artifacts/ModuleDefinition.cs b/MBBSDASM/Analysis/Artifacts/ModuleDefinition.cs index 37a4ff9..c7ef949 100644 --- a/MBBSDASM/Analysis/Artifacts/ModuleDefinition.cs +++ b/MBBSDASM/Analysis/Artifacts/ModuleDefinition.cs @@ -5,6 +5,7 @@ namespace MBBSDASM.Analysis.Artifacts public class ModuleDefinition { public string Name { get; set; } + public string Comment { get; set; } public List Exports { get; set; } } } \ No newline at end of file diff --git a/MBBSDASM/Analysis/Assets/DOSCALLS_def.json b/MBBSDASM/Analysis/Assets/DOSCALLS_def.json new file mode 100644 index 0000000..579e7c9 --- /dev/null +++ b/MBBSDASM/Analysis/Assets/DOSCALLS_def.json @@ -0,0 +1,892 @@ + { + "Name": "DOSCALLS", + "Comment" : "Export definitions for DOSCALLS.H which is a C++ OS/2 library for DOS APIs", + "Exports": [ + { + "name": "DOSCWAIT", + "ord": 2, + "Signature": "USHORT rc = DosCwait(USHORT ActionCode, USHORT WaitOption, PRESULTCODES ReturnCodes, PPID ProcessIDWord, PID ProcessID);", + "Comments": [ + "Places the current thread in a wait state until an asynchronous child process ends.", + "When the process ends, its process ID and termination code are returned to the caller." + ] + }, + { + "name": "DOSENTERCRITSEC", + "ord": 3, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSEXIT", + "ord": 5, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSEXITCRITSEC", + "ord": 6, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSEXITLIST", + "ord": 7, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETINFOSEG", + "ord": 8, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETPRTY", + "ord": 9, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSKILLPROCESS", + "ord": 10, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSKILLPROCESS", + "ord": 11, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSPTRACE", + "ord": 12, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSHOLDSIGNAL", + "ord": 13, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSETSIGHANDLER", + "ord": 14, + "Signature": "", + "Comments": [ + "Registers signal handler" + ] + }, + { + "name": "DOSFLAGPROCESS", + "ord": 15, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSMAKEPIPE", + "ord": 16, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSEMSETWAIT", + "ord": 20, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSMUXSEMWAIT", + "ord": 22, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSCLOSESEM", + "ord": 23, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSCREATESEM", + "ord": 24, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSOPENSEM", + "ord": 25, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSRESUMETHREAD", + "ord": 26, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSUSPENDTHREAD", + "ord": 27, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSETDATETIME", + "ord": 28, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSTIMERASYNC", + "ord": 29, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSTIMERSTART", + "ord": 30, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSTIMERSTOP", + "ord": 31, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSTIMERSTOP", + "ord": 31, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSLEEP", + "ord": 32, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETDATETIME", + "ord": 33, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSALLOCSEG", + "ord": 34, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSALLOCSHRSEG", + "ord": 35, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETSHRSEG", + "ord": 36, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGIVESEG", + "ord": 37, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSREALLOCSEG", + "ord": 38, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFREESEG", + "ord": 39, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSALLOCHUGE", + "ord": 40, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETHUGESHIFT", + "ord": 41, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSREALLOCHUGE", + "ord": 42, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSCREATECSALIAS", + "ord": 43, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSLOADMODULE", + "ord": 44, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETPROCADDR", + "ord": 45, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFREEMODULE", + "ord": 46, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETMODHANDLE", + "ord": 47, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETMODNAME", + "ord": 48, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETMACHINEMODE", + "ord": 49, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSBEEP", + "ord": 50, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSCLIACCESS", + "ord": 51, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSDEVCONFIG", + "ord": 52, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSDEVIOCTL", + "ord": 53, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSBUFRESET", + "ord": 56, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSCHDIR", + "ord": 57, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSCHGFILEPTR", + "ord": 58, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSCLOSE", + "ord": 59, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSDELETE", + "ord": 60, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSDUPHANDLE", + "ord": 61, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFILELOCKS", + "ord": 62, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFINDCLOSE", + "ord": 63, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFINDFIRST", + "ord": 64, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFINDNEXT", + "ord": 65, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSMKDIR", + "ord": 66, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSMOVE", + "ord": 67, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSNEWSIZE", + "ord": 68, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSPORTACCESS", + "ord": 69, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSOPEN", + "ord": 70, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSQCURDIR", + "ord": 71, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSQCURDISK", + "ord": 72, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSQFHANDSTATE", + "ord": 73, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSQFILEINFO", + "ord": 74, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSQFILEMODE", + "ord": 75, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSQFSINFO", + "ord": 76, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSQHANDTYPE", + "ord": 77, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSQVERIFY", + "ord": 78, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSRMDIR", + "ord": 80, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSELECTDISK", + "ord": 81, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSETFHANDSTATE", + "ord": 82, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSETFILEINFO", + "ord": 83, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSETFILEMODE", + "ord": 84, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSETMAXFH", + "ord": 85, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSETVERIFY", + "ord": 86, + "Signature": "APIRET DosSetVerify (BOOL32 fVerifySetting);", + "Comments": [ + "Sets system read-after-write flag" + ] + }, + { + "name": "DOSSYSTEMSERVICE", + "ord": 88, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSETVEC", + "ord": 89, + "Signature": "USHORT rc = DosSetVec(USHORT VecNum, PFN Routine, PFN PrevAddress);", + "Comments": [ + "Registers handler for hardware exception", + "The DosSetVec process is analogous to setting an address in the interrupt vector table when running in 8086 mode." + ] + }, + { + "name": "DOSSYSTRACE", + "ord": 90, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETENV", + "ord": 91, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETVERSION", + "ord": 92, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETPID", + "ord": 94, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSOPEN2", + "ord": 95, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSLIBINIT", + "ord": 96, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSETFSINFO", + "ord": 97, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSQPATHINFO", + "ord": 98, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSDEVIOCTL2", + "ord": 99, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSETPATHINFO", + "ord": 104, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSERROR", + "ord": 120, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETSEG", + "ord": 121, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSLOCKSEG", + "ord": 122, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSUNLOCKSEG", + "ord": 123, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSIZESEG", + "ord": 126, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSMEMAVAIL", + "ord": 127, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSPHYSICALDISK", + "ord": 129, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETCP", + "ord": 130, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSENDSIGNAL", + "ord": 134, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSHUGESHIFT", + "ord": 135, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSHUGEINCR", + "ord": 136, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSREAD", + "ord": 137, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSWRITE", + "ord": 138, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSERRCLASS", + "ord": 139, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSEMREQUEST", + "ord": 140, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSEMCLEAR", + "ord": 141, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSEMWAIT", + "ord": 142, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSEMSET", + "ord": 143, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSEXECPGM", + "ord": 144, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSCREATETHREAD", + "ord": 145, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSUBSET", + "ord": 146, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSUBALLOC", + "ord": 147, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSUBFREE", + "ord": 148, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSREADASYNC", + "ord": 149, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSWRITEASYNC", + "ord": 150, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSEARCHPATH", + "ord": 151, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSCANENV", + "ord": 152, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSETCP", + "ord": 153, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETRESOURCE", + "ord": 155, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETPPID", + "ord": 156, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSCALLBACK", + "ord": 157, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSR2STACKREALLOC", + "ord": 160, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFSRAMSEMREQUEST", + "ord": 161, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFSRAMSEMCLEAR", + "ord": 162, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSQAPPTYPE", + "ord": 163, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSETPROCCP", + "ord": 164, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSDYNAMICTRACE", + "ord": 165, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSQSYSINFO", + "ord": 166, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFSATTACH", + "ord": 181, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSQFSATTACH", + "ord": 182, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFSCTL", + "ord": 183, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFINDFIRST2", + "ord": 184, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSMKDIR2", + "ord": 185, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFILEIO", + "ord": 186, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFINDNOTIFYCLOSE", + "ord": 187, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFINDNOTIFYFIRST", + "ord": 188, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFINDNOTIFYNEXT", + "ord": 189, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSEDITNAME", + "ord": 191, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSLOGREGISTER", + "ord": 195, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSLOGREAD", + "ord": 196, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSCOPY", + "ord": 201, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFORCEDELETE", + "ord": 203, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSENUMATTRIBUTE", + "ord": 204, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSSHUTDOWN", + "ord": 206, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSGETRESOURCE2", + "ord": 207, + "Signature": "", + "Comments": [] + }, + { + "name": "DOSFREERESOURCE", + "ord": 208, + "Signature": "", + "Comments": [] + } + ] + } \ No newline at end of file diff --git a/MBBSDASM/Analysis/Assets/GALGSBL_def.json b/MBBSDASM/Analysis/Assets/GALGSBL_def.json index 1a3d827..6d10ad6 100644 --- a/MBBSDASM/Analysis/Assets/GALGSBL_def.json +++ b/MBBSDASM/Analysis/Assets/GALGSBL_def.json @@ -1,17 +1,22 @@ { - "Name" : "GALGSBL", + "Name": "GALGSBL", + "Comment": "Export definitions for the Galacticomm Software Breakout Library (GSBL)", "Exports": [ { "name": "_BTUBSE", "ord": 1, - "Signature" : "int btubse(int chan,char bschar);", - "Comments": [] + "Signature": "int btubse(int chan,char bschar);", + "Comments": [ + "Set backspace-echo character" + ] }, { "name": "_BTUBSZ", "ord": 2, - "Signature" : "int btubsz(int chan,int isiz,int osiz);", - "Comments": [] + "Signature": "int btubsz(int chan,int isiz,int osiz);", + "Comments": [ + "Respecify input and output buffer sizes" + ] }, { "name": "_BTUCHE", @@ -41,148 +46,204 @@ { "name": "_BTUCLI", "ord": 6, - "Signature" : "int btucli(int chan);", - "Comments": [] + "Signature": "int btucli(int chan);", + "Comments": [ + "Cleat data input buffer", + "-10 == Channel not defined, -11 == channel number is out of range, 0 == all is well" + ] }, { "name": "_BTUCLO", "ord": 7, - "Signature" : "int btuclo(int chan);", - "Comments": [] + "Signature": "int btuclo(int chan);", + "Comments": [ + "Clear data output buffer", + "-10 == Channel not defined, -11 == channel number is out of range, 0 == all is well" + ] }, { "name": "_BTUCLS", "ord": 8, - "Signature" : "int btucls(int chan);", - "Comments": [] + "Signature": "int btucls(int chan);", + "Comments": [ + "Clear status input buffer", + "-10 == Channel not defined, -11 == channel number is out of range, 0 == all is well" + ] }, { "name": "_BTUCMD", "ord": 9, - "Signature" : "int btucmd(int chan,char *cmdstg);", - "Comments": [] + "Signature": "int btucmd(int chan,char *cmdstg);", + "Comments": [ + "Command channel", + "This routine controls functions of the UART and (if used) the modem on a channel" + ] }, { "name": "_BTUDEF", "ord": 10, - "Signature" : "int btudef(int schan,int sport,int n);", - "Comments": [] + "Signature": "int btudef(int schan,int sport,int n);", + "Comments": [ + "Define Channels" + ] }, { "name": "_BTECH", "ord": 11, - "Signature" : "int btuech(int chan,int onoff);", + "Signature": "int btuech(int chan, int mode);", "Comments": [ - "Possible type in Module Definition for BTUECH()" + "Set Echo on/off", + "0 == disable echo, 1 == enable echo (echo-plex on X.25 channels), 2 == enable echo (GSBL echo on X.25 channels)" ] }, { "name": "_BTUEND", "ord": 12, - "Signature" : "void btuend(void);", - "Comments": [] + "Signature": "void btuend(void);", + "Comments": [ + "Shut down the Software Breakthrough", + "Prepare the PC for return to DOS. This routine must be called as part of your exit cleanup procedure." + ] }, { "name": "_BTUERP", "ord": 13, - "Signature" : "int btuerp(int chan,int onoff);", - "Comments": [] + "Signature": "int btuerp(int chan,int onoff);", + "Comments": [ + "Pass/Block input bytes with errors", + "1 == accept characters with PE/FE/OE errors, setting the high order bit of each (default), 0 == ignore characters with PE/EE/OE errors" + ] }, { "name": "_BTUFFO", "ord": 14, - "Signature" : "int btuffo(int chan,int onoff);", - "Comments": [] + "Signature": "int btuffo(int chan,int onoff);", + "Comments": [ + "Enable receiver FIFO on 16550 UART", + "1 == enable 16-byte FIFOs, 0 == disable (for exact 16450 compatibility)" + ] }, { "name": "_BTUHCR", "ord": 15, - "Signature" : "int btuhcr(int chan,char hardcr);", - "Comments": [] + "Signature": "int btuhcr(int chan,char hardcr);", + "Comments": [ + "Set the hard-CR character (for output wordwrap)" + ] }, { "name": "_BTUHDR", "ord": 16, - "Signature" : "int btuhdr(int sapchn,int bufsiz,void *buffer);", - "Comments": [] + "Signature": "int btuhdr(int sapchn,int bufsiz,void *buffer);", + "Comments": [ + "Capture information on X.25 or LAN channel" + ] }, { "name": "_BTUHPK", "ord": 17, - "Signature" : "int btuhpk(int chan,int far (*hpkrou)(int chan,char c));", - "Comments": [] + "Signature": "int btuhpk(int chan,int far (*hpkrou)(int chan,char c));", + "Comments": [ + "Handle keystrokes during screen-pause mode" + ] }, { "name": "_BTUHWH", "ord": 18, - "Signature" : "int btuhwh(int chan,int inpcut);", - "Comments": [] + "Signature": "int btuhwh(int chan,int inpcut);", + "Comments": [ + "Enable hardware handshaking using RTS/CTS" + ] }, { "name": "_BTUIBW", "ord": 19, - "Signature" : "int btuibw(int chan);", - "Comments": [] + "Signature": "int btuibw(int chan);", + "Comments": [ + "Input Bytes Waiting", + "Report the number of bytes received and waiting in the input buffer" + ] }, { "name": "_BTUICT", "ord": 20, - "Signature" : "int btuict(int chan,char *rdbptr);", - "Comments": [] + "Signature": "int btuict(int chan,char *rdbptr);", + "Comments": [ + "Input from a channel - by byte count prearranged with btutrg()" + ] }, { "name": "_BTUINJ", "ord": 21, - "Signature" : "int btuinj(int chan,int status);", - "Comments": [] + "Signature": "int btuinj(int chan,int status);", + "Comments": [ + "Inject a status code into a channel" + ] }, { "name": "_BTUINP", "ord": 22, - "Signature" : "void chiinp(int chan,char c);", - "Comments": [] + "Signature": "void chiinp(int chan,char c);", + "Comments": [ + "Input from a channel (ASCIIZ string)" + ] }, { "name": "_BTUIRP", "ord": 23, - "Singnature" : "int btuirp(int comno);", - "Comments": [] + "Singnature": "int btuirp(int comno);", + "Comments": [ + "Define alternate GSBL timing source using COM1/2/3/4" + ] }, { "name": "_BTUITZ", "ord": 24, - "Signature" : "int btuitz(void *region);", - "Comments": [] + "Signature": "int btuitz(void *region);", + "Comments": [ + "Initialize the Software Breakthrough", + "This routine initializes the Software Breakthrough package" + ] }, { "name": "_BTULFD", "ord": 25, - "Signature" : "int btulfd(int chan,char lfchar);", - "Comments": [] + "Signature": "int btulfd(int chan,char lfchar);", + "Comments": [ + "Set linefeed character (what follows every carriage return)" + ] }, { "name": "_BTULOK", "ord": 26, - "Signature" : "int btulok(int chan,int onoff);", - "Comments": [] + "Signature": "int btulok(int chan,int onoff);", + "Comments": [ + "Set input lockout on/off" + ] }, { "name": "_BTULSZ", "ord": 27, - "Signature" : "long btulsz(int nchan,int isiz,int osiz);", - "Comments": [] + "Signature": "long btulsz(int nchan,int isiz,int osiz);", + "Comments": [ + "Size of dynamic memory needed (long version, used when more than 64K bytes are needed)" + ] }, { "name": "_BTUMDS", "ord": 28, - "Signature" : "int btumds(void);", - "Comments": [] + "Signature": "int btumds(void);", + "Comments": [ + "Get next displayed character from the monitored channel (as specified by btumon())" + ] }, { "name": "_BTUMDS2", "ord": 29, - "Signature" : "int btumds2(void);", - "Comments": [] + "Signature": "int btumds2(void);", + "Comments": [ + "Get next displayed character from the monitored channel (as specified by btumon2()" + ] }, { "name": "_BTUMIL", @@ -198,50 +259,68 @@ { "name": "_BTUMKS", "ord": 31, - "Signature" : "void btumks(char kyschr);", - "Comments": [] + "Signature": "void btumks(char kyschr);", + "Comments": [ + "Simulate a keystroke on the monitored channel (as specified by btumon())" + ] }, { "name": "_BTMKS2", "ord": 32, - "Signature" : "void btumks2(char kyschr);", - "Comments": [] + "Signature": "void btumks2(char kyschr);", + "Comments": [ + "Simulate a keystroke on the monitored channel (as specified by btomon2())" + ] }, { "name": "_BTUMON", "ord": 33, - "Signature" : "int btumon(int chan);", - "Comments": [] + "Signature": "int btumon(int chan);", + "Comments": [ + "Start/Stop monitoring a channel" + ] }, { "name": "_BTUMON2", "ord": 34, - "Signature" : "int btumon2(int chan);", - "Comments": [] + "Signature": "int btumon2(int chan);", + "Comments": [ + "Start/Stop monitoring a channel", + "This function is a clone of btumon(), for emulating a second channel" + ] }, { "name": "_BTUMXS", "ord": 35, - "Signature" : "int btumxs(unsigned bdrate);", - "Comments": [] + "Signature": "int btumxs(unsigned bdrate);", + "Comments": [ + "Set maximum data speed" + ] }, { "name": "_BTUOBA", "ord": 36, - "Signature" : "int btuoba(int chan);", - "Comments": [] + "Signature": "int btuoba(int chan);", + "Comments": [ + "Output Bytes Available", + "Report the amount of space (number of bytes) available in the output buffer" + ] }, { "name": "_BTUOES", "ord": 37, - "Signature" : "int btuoes(int chan,int onoff);", - "Comments": [] + "Signature": "int btuoes(int chan,int onoff);", + "Comments": [ + "Enable/Disable Output-Empty status codes" + ] }, { "name": "_BTUOLK", "ord": 38, - "Signature" : "int btuolk(int chan,int onoff);", - "Comments": [] + "Signature": "int btuolk(int chan, int onoff);", + "Comments": [ + "Set output pausing on/off" + ] }, { "name": "_BTUPBC", @@ -255,74 +334,107 @@ { "name": "_BTUPMT", "ord": 40, - "Signature" : "int btupmt(int chan,char pmchar);", - "Comments": [] + "Signature": "int btupmt(int chan, char pmchar);", + "SignatureFormat": "int btupmt(??,'{0}');", + "PrecedingInstructions": [ + { + "Offset": -4, + "Op": "PUSH", + "Type": "char", + "Name": "Prompt Character" + } + ], + "Comments": [ + "Set prompt character" + ] }, { "name": "_BTURST", "ord": 41, - "Signature" : "int bturst(int chan);", - "Comments": [] + "Signature": "int bturst(int chan);", + "Comments": [ + "Reset a channel" + ] }, { "name": "_BTURTI", "ord": 42, - "Signature" : "int bturti(int n,void (*rtirou)(void));", - "Comments": [] + "Signature": "int bturti(int n,void (*rtirou)(void));", + "Comments": [ + "Define routine to be called in real-time" + ] }, { "name": "_BTUSCN", "ord": 43, - "Signature" : "int btuscn(void);", - "Comments": [] + "Signature": "int btuscn(void);", + "Comments": [ + "Scan for channels in need of service (those with nonzero status)" + ] }, { "name": "_BTUSCR", "ord": 44, - "Signature" : "int btuscr(int chan,char softcr);", - "Comments": [] + "Signature": "int btuscr(int chan,char softcr);", + "Comments": [ + "Set the soft-CR character (for output wordwrap)" + ] }, { "name": "_BTUSDF", "ord": 45, - "Signature" : "int btusdf(int schan,int nchan,int chtype,...);", - "Comments": [] + "Signature": "int btusdf(int schan,int nchan,int chtype,...);", + "Comments": [ + "Super-define channel groups" + ] }, { "name": "_BTUSET", "ord": 46, - "Signature" : "long btuset(int chan,int stid,long newval);", - "Comments": [] + "Signature": "long btuset(int chan,int stid,long newval);", + "Comments": [ + "Set and report channel statistics" + ] }, { "name": "_BTUSIZ", "ord": 47, - "Signature" : "unsigned btusiz(int nchan,int isiz,int osiz);", - "Comments": [] + "Signature": "unsigned btusiz(int nchan,int isiz,int osiz);", + "Comments": [ + "Size of dynamic memory needed (only if < 64K)" + ] }, { "name": "_BTUSTS", "ord": 48, - "Signature" : "int btusts(int chan);", - "Comments": [] + "Signature": "int btusts(int chan);", + "Comments": [ + "Status of a channel" + ] }, { "name": "_BTUTRG", "ord": 49, - "Signature" : "int btutrg(int chan,int nbyt);", - "Comments": [] + "Signature": "int btutrg(int chan,int nbyt);", + "Comments": [ + "Set the input byte trigger quantity (used in conjunction with btuict())" + ] }, { "name": "_BTUTRM", "ord": 50, - "Signature" : "int btutrm(int chan,char crchar);", - "Comments": [] + "Signature": "int btutrm(int chan,char crchar);", + "Comments": [ + "Set input line terminator character" + ] }, { "name": "_BTUTRS", "ord": 51, - "Signature" : "int btutrs(int chan,int onoff);", - "Comments": [] + "Signature": "int btutrs(int chan,int onoff);", + "Comments": [ + "Generate status 6 when output aborted by user?" + ] }, { "name": "_BTUTRU", @@ -336,8 +448,10 @@ { "name": "_BTUTSW", "ord": 53, - "Signature" : "int btutsw(int chan,int width);", - "Comments": [] + "Signature": "int btutsw(int chan,int width);", + "Comments": [ + "Set terminal screen width, and select output word wrap" + ] }, { "name": "_BTUTSW", @@ -350,73 +464,95 @@ { "name": "_BTUUDF", "ord": 54, - "Signature" : "int btuudf(int schan,int n);", - "Comments": [] + "Signature": "int btuudf(int schan,int n);", + "Comments": [ + "Un-Define channels", + "This command undoes the effects of btudef()" + ] }, { "name": "_BTUX29", "ord": 55, - "Signature" : "int btux29(int chan,int nbyt,char *data);", + "Signature": "int btux29(int chan,int nbyt,char *data);", "Comments": [] }, { "name": "_BTUXCT", "ord": 56, - "Signature" : "int btuxct(int chan,int nbyt,char *datstg);", - "Comments": [] + "Signature": "int btuxct(int chan,int nbyt,char *datstg);", + "Comments": [ + "Transmit to channel (by byte count)" + ] }, { "name": "_BTUXLT", "ord": 57, - "Signature" : "void btuxlt(char oldchr,char newchr);", - "Comments": [] + "Signature": "void btuxlt(char oldchr,char newchr);", + "Comments": [ + "Set input translation table" + ] }, { "name": "_BTUXMN", "ord": 58, - "Signature" : "int btuxmn(int chan,char *datstg);", - "Comments": [] + "Signature": "int btuxmn(int chan,char *datstg);", + "Comments": [ + "Transmit ASCII string that btuclo() will not be able to clear", + "btubsz() will clear, however" + ] }, { "name": "_BTUXMT", "ord": 59, - "Signature" : "int btuxmt(int chan,char *datstg);", - "Comments": [] + "Signature": "int btuxmt(int chan,char *datstg);", + "Comments": [ + "Transmit to channel (ASCIIZ string)" + ] }, { "name": "_BTUXNF", "ord": 60, - "Signature" : "int btuxnf(int chan,int xon,int xoff,...);", - "Comments": [] + "Signature": "int btuxnf(int chan,int xon,int xoff,...);", + "Comments": [ + "Set XON/XOFF characters, select page mode" + ] }, { "name": "_CHIINJ", "ord": 61, - "Signature" : "void chiinj(int chan,int s);", - "Comments": [] + "Signature": "void chiinj(int chan,int status);", + "Comments": [ + "Status Inject Utility" + ] }, { "name": "_CHIINP", "ord": 62, - "Signature" : "void chiinp(int chan,char c);", - "Comments": [] + "Signature": "void chiinp(int chan,char c);", + "Comments": [ + "Character Input Utility" + ] }, { "name": "_CHIOUS", "ord": 63, - "Signature" : "void chious(int chan,char *stg);", - "Comments": [] + "Signature": "void chious(int chan,char *stg);", + "Comments": [ + "String Output (via Echo Buffer)" + ] }, { "name": "_CHIOUT", "ord": 64, - "Signature" : "void chiout(int chan,char c);", - "Comments": [] + "Signature": "void chiout(int chan,char c);", + "Comments": [ + "Character Output (via Echo Buffer)" + ] }, { "name": "_TICKER", "ord": 65, - "Signature" : "volatile unsigned ticker;", + "Signature": "volatile unsigned ticker;", "Comments": [ "Increments once per second" ] @@ -426,7 +562,7 @@ "ord": 66, "Signature": "unsigned long btuhrt;", "Comments": [ - "32-bit integer increments at approximatley 65536hz" + "32-bit integer increments at approximately 65536hz" ] }, { @@ -461,8 +597,10 @@ { "name": "_BTUBRT", "ord": 70, - "Signature" : "int btubrt(int chan,unsigned bdrate);", - "Comments": [] + "Signature": "int btubrt(int chan,unsigned bdrate);", + "Comments": [ + "Set channels baud rate" + ] }, { "name": "_SUSLCK", @@ -472,7 +610,7 @@ { "name": "_BTURNO", "ord": 72, - "Signature": "char btruno[];", + "Signature": "char bturno[];", "Comments": [ "8 digit + NULL GSBL Registration Number" ] @@ -480,15 +618,15 @@ { "name": "_BTUDTR", "ord": 73, - "Signature" : "int btudtr;", + "Signature": "int btudtr;", "Comments": [ - "Set 1 1 to disable DTR-dropping during reset" + "Set 1 to disable DTR-dropping during reset" ] }, { "name": "_X25UDT", "ord": 74, - "Signature" : "int x25udt;", + "Signature": "int x25udt;", "Comments": [ "Set to 1 to capture User Data Field" ] @@ -501,7 +639,7 @@ { "name": "_X25IGN", "ord": 76, - "Signature" : "int x25ign;", + "Signature": "int x25ign;", "Comments": [ "Count of received packets ignored by GSBL" ] @@ -509,7 +647,7 @@ { "name": "_LANREV", "ord": 77, - "Signature" : "char lanrev[2];", + "Signature": "char lanrev[2];", "Comments": [ "SPX revision number" ] @@ -517,7 +655,7 @@ { "name": "_LANSOP", "ord": 78, - "Signature" : "int lansop;", + "Signature": "int lansop;", "Comments": [ "Socket actually opened by btusdf() call" ] @@ -525,7 +663,7 @@ { "name": "_LANSCA", "ord": 79, - "Signature" : "int lansca;", + "Signature": "int lansca;", "Comments": [ "SPX connections available" ] @@ -546,7 +684,7 @@ { "name": "_ICTACT", "ord": 82, - "Signature" : "int ictact;", + "Signature": "int ictact;", "Comments": [ "After btuict() or btuica(): # of bytes available" ] @@ -554,49 +692,59 @@ { "name": "_BTUEBA", "ord": 83, - "Signature" : "int btueba(int chan);", - "Comments": [] + "Signature": "int btueba(int chan);", + "Comments": [ + "Echo buffer space available, in bytes" + ] }, { "name": "_BTUHIT", "ord": 84, - "Signature" : "int btuhit(int comint);", - "Comments": [] + "Signature": "int btuhit(int comint);", + "Comments": [ + "Hook into a COM port interrupt and use it to invoke channel servicing" + ] }, { "name": "_BTUITM", "ord": 85, - "Signature" : "int btuitm(void *region);", - "Comments": [] + "Signature": "int btuitm(void *region);", + "Comments": [ + "Initialize the Software Breakthrough for use in a multi-tasking environment" + ] }, { "name": "_BTUCPC", "ord": 86, - "Signature" : "int btucpc(int chan,char clrpch);", - "Comments": [] + "Signature": "int btucpc(int chan,char clrpch);", + "Comments": [ + "Set the clear pause-counter character (puts off screen pauses when in output stream)" + ] }, { "name": "_BTUICA", "ord": 87, - "Signature" : "int btuica(int chan,char *rdbptr,int max);", - "Comments": [] + "Signature": "int btuica(int chan,char *rdbptr,int max);", + "Comments": [ + "Input from a channel - reading in whatever bytes are available, up to a limit" + ] }, { "name": "_BTUPFL", "ord": 88, - "Signature" : "int btupfl(void (*pflrou)(int type,unsigned off,unsigned sel));", + "Signature": "int btupfl(void (*pflrou)(int type,unsigned off,unsigned sel));", "Comments": [] }, { "name": "_PPFLREAL", "ord": 89, - "Signature" : "extern long far *ppflreal;", + "Signature": "extern long far *ppflreal;", "Comments": [] }, { "name": "_PFLGSLB", "ord": 90, - "Signature" : "extern long pflgsbl;", + "Signature": "extern long pflgsbl;", "Comments": [ "Possible type of PFGSBL in Module Definition" ] @@ -604,61 +752,65 @@ { "name": "_PFLRNG3", "ord": 91, - "Signature" : "extern long pflrng3;", + "Signature": "extern long pflrng3;", "Comments": [] }, { "name": "_PFLPLAP", "ord": 92, - "Signature" : "extern long pflplap;", + "Signature": "extern long pflplap;", "Comments": [] }, { "name": "_PFN", "ord": 93, - "Signature" : "extern char pfn;", + "Signature": "extern char pfn;", "Comments": [] }, { "name": "_BTUUSP", "ord": 94, - "Signature" : "int btuusp(int chan,int onoff);", - "Comments": [] + "Signature": "int btuusp(int chan,int onoff);", + "Comments": [ + "Special UART polling method" + ] }, { "name": "_BTUREP", "ord": 95, - "Signature" : "long bturep(int chan,int stid);", - "Comments": [] + "Signature": "long bturep(int chan,int stid);", + "Comments": [ + "Report Channel Statistics" + ] }, { "name": "_PFLMYS3", "ord": 96, - "Signature" : "extern long pflmys3;", + "Signature": "extern long pflmys3;", "Comments": [] }, { "name": "_BTUCDI", "ord": 97, - "Signature" : "struct datstm far *btucdi(int chan,struct datstm far *outsnk);", + "Signature": "struct datstm far *btucdi(int chan,struct datstm far *outsnk);", "Comments": [] }, { "name": "_BTUOPL", "ord": 98, - "Signature" : "int btuopl(int chan);", + "Signature": "int btuopl(int chan);", "Comments": [] }, { "name": "_BTUPCC", "ord": 99, - "Signature" : "int btupcc(int chan,int mode);", + "Signature": "int btupcc(int chan,int mode);", "Comments": [] }, { "name": "_BTUBBR", "ord": 56, - "Signature" : "int btubbr(int chan,long bdrate);", + "Signature": "int btubbr(int chan,long bdrate);", "Comments": [] } ] diff --git a/MBBSDASM/Analysis/Assets/MajorBBS_def.json b/MBBSDASM/Analysis/Assets/MajorBBS_def.json index 3f9d68f..249aaea 100644 --- a/MBBSDASM/Analysis/Assets/MajorBBS_def.json +++ b/MBBSDASM/Analysis/Assets/MajorBBS_def.json @@ -1,5 +1,6 @@ { "Name": "MAJORBBS", + "Comment": "Export definitions for MAJORBBS.DEF which is part of the Galacticomm/MajorBBS SDK", "Exports": [ { "name": "__READ", @@ -2917,7 +2918,7 @@ "ord": 386, "Signature" : "void listing(char *path, void (*whndun)())", "Comments": [ - "List an ASCII file to the users's screen" + "List an ASCII file to the users screen" ] }, { @@ -3006,7 +3007,7 @@ "ord": 393, "Signature" : "void longjmp(jmp_buf jmpb, int retval);", "Comments": [ - "longjmp - performs a nonlocal goto" + "longjmp - performs a non-local goto" ] }, { @@ -3071,7 +3072,7 @@ "Signature": "int margc;", "Comments": [ "Number of words in the user input line", - "Value initalized by parsin()" + "Value initialized by parsin()" ] }, { @@ -3080,7 +3081,7 @@ "Signature": "char *margn[];", "Comments": [ "An Array of pointers to the ENDS of the words in the user's input line", - "Value initalized by parsin()" + "Value initialized by parsin()" ] }, { @@ -3089,7 +3090,7 @@ "Signature": "char *margv[];", "Comments": [ "An array of pointers to the words in user's input line", - "Value initalized by parsin()" + "Value initialized by parsin()" ] }, { @@ -3656,7 +3657,7 @@ "ord": 469, "Signature" : "int pltile(unsigned long size,int bsel,unsigned stride,unsigned tsize);", "Comments": [ - "Allocate Phar Lap tiled region" + "Allocate Phar-Lap tiled region" ] }, { @@ -4063,7 +4064,7 @@ "ord": 521, "Signature": "int found=samein(char *subs, char *string);", "Comments": [ - "Searches the string for any accurance of the substring", + "Searches the string for any occurence of the substring", "Returns 1 if it finds any" ] }, @@ -4205,7 +4206,7 @@ "ord": 541, "Signature" : "int setjmp(jmp_buf jmpb);", "Comments": [ - "setjmp - nonlocal goto" + "setjmp - non-local goto" ] }, { @@ -4350,7 +4351,7 @@ "Signature": "char *sprstg=spr(char *ctlstg, TYPE p1, TYPE p2,...,pn);", "Comments": [ "sprintf-like string formatter utility", - "Main differentiator is that spr() supports long integer and floating point conversions" + "Main differentiation is that spr() supports long integer and floating point conversions" ] }, { @@ -4471,7 +4472,7 @@ "ord": 572, "Signature": "char * strchr ( const char * str, int character );", "Comments": [ - "Returns a pointer to the first occuence of character in the C string str" + "Returns a pointer to the first occurence of character in the C string str" ] }, { @@ -5402,7 +5403,7 @@ "ord": 688, "Comments": [ "Copies Struct into another Struct (Borland C++ Implicit Function)", - "Aguments: Two Far Pointers, 1st == Source, 2nd == Destination" + "Arguments: Two Far Pointers, 1st == Source, 2nd == Destination" ] }, { @@ -5589,7 +5590,7 @@ "ord": 714, "Signature": "int hichp1;", "Comments": [ - "Highest Channel numberin use, plus 1" + "Highest Channel number in use, plus 1" ] }, { @@ -5597,7 +5598,7 @@ "ord": 715, "Signature" : "char chanty[NGROUPS];", "Comments": [ - "Array of btusdf() codes for LAN chan grps" + "Array of btusdf() codes for LAN chan groups" ] }, { @@ -5613,7 +5614,7 @@ "ord": 717, "Signature": "int numcat;", "Comments": [ - "Number of cancat'd commands so far" + "Number of concat'd commands so far" ] }, { @@ -7352,7 +7353,7 @@ "ord": 962, "Signature" : "int sgnusz", "Comments": [ - "Maximum size of User-IDs for new signups" + "Maximum size of User-IDs for new sign-ups" ] }, { @@ -8339,7 +8340,7 @@ "ord": 1107, "Signature" : "char *asupq[NMQSTS];", "Comments": [ - "Pointers to addtn'l signup questions" + "Pointers to additional sign-up questions" ] }, { @@ -8347,7 +8348,7 @@ "ord": 1108, "Signature" : "char *suphdr;", "Comments": [ - "Additional signup questions header" + "Additional sign-up questions header" ] }, { @@ -8355,7 +8356,7 @@ "ord": 1109, "Signature" : "char *supend;", "Comments": [ - "Closing thanks for additonal signup questions" + "Closing thanks for additional sign-up questions" ] }, { @@ -8793,7 +8794,7 @@ "ord": 1169, "Signature" : "void unrarea(int poolhdl, int areahdl);", "Comments": [ - "Unreserve a memory area within a pool" + "Un-reserve a memory area within a pool" ] }, { @@ -8833,7 +8834,7 @@ "ord": 1174, "Signature" : "void dclanno(int size);", "Comments": [ - "Module declaration for annoncement per-request" + "Module declaration for announcement per-request" ] }, { @@ -8915,7 +8916,7 @@ "ord": 1185, "Signature" : "bool infsdhup;", "Comments": [ - "Determines if we're in the middle of FSD handup handling" + "Determines if we're in the middle of FSD hangup handling" ] }, { @@ -9152,7 +9153,7 @@ "ord": 1216, "Signature" : "void (*btverrptr)(char *who)", "Comments": [ - "Vector for low level Btrieve funcs" + "Vector for low level Btrieve functions" ] }, { @@ -9290,7 +9291,7 @@ "ord": 1317, "Signature" : "int clisrv;", "Comments": [ - "Denotes supporting client/server logons" + "Denotes supporting client/server log-ons" ] }, { @@ -9456,7 +9457,7 @@ "ord": "1338", "Signature" : "int multsk;", "Comments": [ - "Is a multitasker being used?" + "Is a multi-tasker being used?" ] }, { diff --git a/MBBSDASM/Analysis/Analyzer.cs b/MBBSDASM/Analysis/MBBS.cs similarity index 88% rename from MBBSDASM/Analysis/Analyzer.cs rename to MBBSDASM/Analysis/MBBS.cs index 83e0c8e..22bc050 100644 --- a/MBBSDASM/Analysis/Analyzer.cs +++ b/MBBSDASM/Analysis/MBBS.cs @@ -1,16 +1,15 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Linq; using System.Reflection; -using System.Runtime.InteropServices; -using System.Security.Cryptography.X509Certificates; using MBBSDASM.Analysis.Artifacts; using MBBSDASM.Artifacts; using MBBSDASM.Dasm; using MBBSDASM.Enums; +using MBBSDASM.Logging; using Newtonsoft.Json; +using NLog; using SharpDisasm.Udis86; namespace MBBSDASM.Analysis @@ -18,19 +17,20 @@ namespace MBBSDASM.Analysis /// /// Performs Analysis on Imported Functions using defined Module Definiton JSON files /// - public static class Analyzer + public static class MBBS { + private static readonly Logger _logger = LogManager.GetCurrentClassLogger(typeof(CustomLogger)); private static readonly List ModuleDefinitions; /// /// Default Constructor /// - static Analyzer() + static MBBS() { ModuleDefinitions = new List(); //Load Definitions - var assembly = typeof(Analyzer).GetTypeInfo().Assembly; + var assembly = typeof(MBBS).GetTypeInfo().Assembly; foreach (var def in assembly.GetManifestResourceNames().Where(x => x.EndsWith("_def.json"))) { using (var reader = new StreamReader(assembly.GetManifestResourceStream(def))) @@ -38,13 +38,6 @@ static Analyzer() ModuleDefinitions.Add(JsonConvert.DeserializeObject(reader.ReadToEnd())); } } - - //Coverage Tracking - foreach (var m in ModuleDefinitions) - { - var covered = m.Exports.Count(x => !string.IsNullOrEmpty(x.Signature) || x.Comments.Count > 0); - var total = m.Exports.Count; - } } public static void Analyze(NEFile file) @@ -60,11 +53,11 @@ public static void Analyze(NEFile file) /// private static void ImportedFunctionIdentification(NEFile file) { - Console.WriteLine($"{DateTime.Now} Identifying Imported Functions"); + _logger.Info($"Identifying Imported Functions"); if (!file.ImportedNameTable.Any(nt => ModuleDefinitions.Select(md => md.Name).Contains(nt.Name))) { - Console.WriteLine($"{DateTime.Now} No known Module Definitions found in target file, skipping Imported Function Identification"); + _logger.Info($"No known Module Definitions found in target file, skipping Imported Function Identification"); return; } @@ -74,22 +67,25 @@ private static void ImportedFunctionIdentification(NEFile file) foreach (var segment in file.SegmentTable.Where(x=> x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) { //Function Definition Identification Pass - foreach (var disassemblyLine in segment.DisassemblyLines.Where(x=> x.BranchToRecords.Any(y=> y.BranchType == EnumBranchType.CallImport))) + //Loop through each Disassembly Line in the segment that has a BranchType of CallImport or SegAddrImport + foreach (var disassemblyLine in segment.DisassemblyLines.Where(x=> x.BranchToRecords.Any(y=> y.BranchType == EnumBranchType.CallImport || y.BranchType == EnumBranchType.SegAddrImport))) { - + //Get The Import on the Current Line var currentImport = - disassemblyLine.BranchToRecords.First(z => z.BranchType == EnumBranchType.CallImport); - + disassemblyLine.BranchToRecords.First(z => z.BranchType == EnumBranchType.CallImport || z.BranchType == EnumBranchType.SegAddrImport); + + //Find the module it maps to in the ImportedNameTable var currentModule = ModuleDefinitions.FirstOrDefault(x => x.Name == file.ImportedNameTable.FirstOrDefault(y => y.Ordinal == currentImport.Segment)?.Name); + //Usually DOS header stub will trigger this if (currentModule == null) continue; - var ord = currentImport.Offset; - var definition = currentModule.Exports.FirstOrDefault(x => x.Ord == ord); + //Find the matching export by ordinal in one of the loaded Module JSON files + var definition = currentModule.Exports.FirstOrDefault(x => x.Ord == currentImport.Offset); //Didn't have a definition for it? if (definition == null) @@ -100,13 +96,14 @@ private static void ImportedFunctionIdentification(NEFile file) ? definition.Signature : $"{currentModule.Name}.{definition.Name}"); - //Attempt to Resolve the actual Method Signature if we have the definitions + //Attempt to Resolve the actual Method Signature if we have the definition in the JSON doc for this method if (!string.IsNullOrEmpty(definition.SignatureFormat) && definition.PrecedingInstructions != null && definition.PrecedingInstructions.Count > 0) { var values = new List(); foreach (var pi in definition.PrecedingInstructions) { + //Check to see if the expected opcode is in the expected location var i = segment.DisassemblyLines.FirstOrDefault(x => x.Ordinal == disassemblyLine.Ordinal + pi.Offset && x.Disassembly.Mnemonic.ToString().ToUpper().EndsWith(pi.Op)); @@ -114,6 +111,7 @@ private static void ImportedFunctionIdentification(NEFile file) if (i == null) break; + //If we know the type, attempt to cast the operand switch (pi.Type) { case "int": @@ -127,6 +125,9 @@ private static void ImportedFunctionIdentification(NEFile file) resolvedStringComment.IndexOf('\"'))); } break; + case "char": + values.Add((char)i.Disassembly.Operands[0].LvalSDWord); + break; } } @@ -136,7 +137,7 @@ private static void ImportedFunctionIdentification(NEFile file) values.Select(x => x.ToString()).ToArray())); } - //Attempt to resolve a variable this method might be saving + //Attempt to resolve a variable this method might be saving as defined in the JSON doc if (definition.ReturnValues != null && definition.ReturnValues.Count > 0) { foreach (var rv in definition.ReturnValues) @@ -152,6 +153,7 @@ private static void ImportedFunctionIdentification(NEFile file) if(!string.IsNullOrEmpty(rv.Comment)) i.Comments.Add(rv.Comment); + //Add this to our tracked variables, we'll go back through and re-label all instances after this analysis pass trackedVariables.Add(new TrackedVariable() { Comment = rv.Comment, Segment = segment.Ordinal, Offset = i.Disassembly.Offset, Address = i.Disassembly.Operands[0].LvalUWord}); } @@ -190,7 +192,7 @@ private static void ForLoopIdentification(NEFile file) * So we'll search for this basic pattern */ - Console.WriteLine($"{DateTime.Now} Identifying FOR Loops"); + _logger.Info($"Identifying FOR Loops"); //Scan the code segments foreach (var segment in file.SegmentTable.Where(x => @@ -246,13 +248,13 @@ private static void ForLoopIdentification(NEFile file) /// /// This method scans the disassembled code and identifies subroutines, labeling them - /// appropriatley. This also allows for much more precise variable/argument tracking + /// appropriately. This also allows for much more precise variable/argument tracking /// if we properly know the scope of the routine. /// /// private static void SubroutineIdentification(NEFile file) { - Console.WriteLine($"{DateTime.Now} Identifying Subroutines"); + _logger.Info($"Identifying Subroutines"); //Scan the code segments diff --git a/MBBSDASM/Artifacts/StringRecord.cs b/MBBSDASM/Artifacts/StringRecord.cs index b5159a9..825419d 100644 --- a/MBBSDASM/Artifacts/StringRecord.cs +++ b/MBBSDASM/Artifacts/StringRecord.cs @@ -1,4 +1,6 @@ -namespace MBBSDASM.Artifacts +using System.Linq; + +namespace MBBSDASM.Artifacts { public class StringRecord { @@ -6,5 +8,13 @@ public class StringRecord public int Offset { get; set; } public int Length { get; set; } public string Value { get; set; } + + /// + /// Returns TRUE if the string contains printable characters + /// + public bool IsPrintable + { + get { return Value.ToCharArray().Any(x => x > 32 && x < 126); } + } } } \ No newline at end of file diff --git a/MBBSDASM/Constants.cs b/MBBSDASM/Constants.cs new file mode 100644 index 0000000..1d7a75e --- /dev/null +++ b/MBBSDASM/Constants.cs @@ -0,0 +1,10 @@ +namespace MBBSDASM +{ + internal static class Constants + { + internal const string ProgramName = "MBBSDASM"; + internal const string ProgramVersion = "1.5"; + + internal const int MAX_INSTRUCTION_LENGTH = 15; + } +} diff --git a/MBBSDASM/Dasm/Disassembler.cs b/MBBSDASM/Dasm/Disassembler.cs index da4aca9..e652c19 100644 --- a/MBBSDASM/Dasm/Disassembler.cs +++ b/MBBSDASM/Dasm/Disassembler.cs @@ -7,438 +7,513 @@ using System.Threading.Tasks; using MBBSDASM.Artifacts; using MBBSDASM.Enums; +using MBBSDASM.Logging; +using NLog; using SharpDisasm; using SharpDisasm.Udis86; namespace MBBSDASM.Dasm - { - /// - /// Main Disassembler Class for 16-Bit x86 NE Format EXE/DLL Files - /// - public static class Disassembler - { - /// - /// Takes the raw binary code segment and feeds it into the x86 disassembler library - /// - /// - /// - public static List Disassemble(Segment segment) - { - //Only Disassemble Code Segments - if (!segment.Flags.Contains(EnumSegmentFlags.Code)) - return new List(); - - var output = new List(); - - var disassembler = new SharpDisasm.Disassembler(segment.Data, ArchitectureMode.x86_16, 0, true); - - //Perform Raw Disassembly - var ordinal = 0; - foreach (var disassembly in disassembler.Disassemble()) - { - output.Add(new DisassemblyLine - { - Disassembly = disassembly, - Comments = new List(), - Ordinal = ordinal, - BranchFromRecords = new ConcurrentBag(), - BranchToRecords = new ConcurrentBag() - }); - ordinal++; - } - - return output; - } - - /// - /// Locates offsets for exported functions in the Entry table and labels them - /// - /// - public static void IdentifyEntryPoints(NEFile file) - { - foreach (var entry in file.EntryTable) - { - var seg = file.SegmentTable.First(x => x.Ordinal == entry.SegmentNumber); - - var fnName = file.NonResidentNameTable.FirstOrDefault(x => x.IndexIntoEntryTable == entry.Ordinal) - ?.Name; - - if (string.IsNullOrEmpty(fnName)) - fnName = file.ResidentNameTable.FirstOrDefault(x => x.IndexIntoEntryTable == entry.Ordinal)?.Name; - - seg.DisassemblyLines.Where(x => x.Disassembly.Offset == entry.Offset) - .FirstOrDefault(x => - { - x.ExportedFunction = new ExportedFunctionRecord() { Name = fnName}; - return true; - }); - - } - } - - /// - /// Reads the Relocation Table (if present) at the end of a segment and comments about the relocations that - /// are being applied. This identifies both internal and external function calls. - /// - /// - public static void ApplyRelocationInfo(NEFile file) - { - Parallel.ForEach(file.SegmentTable, (segment) => - { - if (!segment.Flags.Contains(EnumSegmentFlags.Code) && - !segment.Flags.Contains(EnumSegmentFlags.HasRelocationInfo)) - return; - Parallel.ForEach(segment.RelocationRecords, (relocationRecord) => - { - var disAsm = - segment.DisassemblyLines.FirstOrDefault(x => - x.Disassembly.Offset == relocationRecord.Offset - 1UL); - - if (disAsm == null) - return; - - switch (relocationRecord.Flag) - { - case EnumRecordsFlag.IMPORTORDINAL | EnumRecordsFlag.ADDITIVE: - case EnumRecordsFlag.IMPORTORDINAL: - disAsm.BranchToRecords.Add(new BranchRecord - { - IsRelocation = true, - BranchType = - disAsm.Disassembly.Mnemonic == ud_mnemonic_code.UD_Icall - ? EnumBranchType.CallImport - : EnumBranchType.SegAddrImport, - Segment = relocationRecord.TargetTypeValueTuple.Item2, - Offset = relocationRecord.TargetTypeValueTuple.Item3 - }); - break; - case EnumRecordsFlag.INTERNALREF | EnumRecordsFlag.ADDITIVE: - case EnumRecordsFlag.INTERNALREF: - if (disAsm.Disassembly.Mnemonic == ud_mnemonic_code.UD_Icall) - { - //Set Target - file.SegmentTable - .FirstOrDefault(x => x.Ordinal == relocationRecord.TargetTypeValueTuple.Item2) - ?.DisassemblyLines - .FirstOrDefault(y => - y.Disassembly.Offset == relocationRecord.TargetTypeValueTuple.Item4) - ?.BranchFromRecords - .Add(new BranchRecord() - { - Segment = segment.Ordinal, - Offset = disAsm.Disassembly.Offset, - IsRelocation = true, - BranchType = EnumBranchType.Call - }); - - //Set Origin - disAsm.BranchToRecords.Add(new BranchRecord() - { - Segment = relocationRecord.TargetTypeValueTuple.Item2, - Offset = relocationRecord.TargetTypeValueTuple.Item4, - BranchType = EnumBranchType.Call, - IsRelocation = true - }); - } - else - { - disAsm.BranchToRecords.Add(new BranchRecord() - { - IsRelocation = true, - BranchType = EnumBranchType.SegAddr, - Segment = relocationRecord.TargetTypeValueTuple.Item2 - }); - } - - break; - case EnumRecordsFlag.IMPORTNAME: - disAsm.BranchToRecords.Add(new BranchRecord - { - IsRelocation = true, - BranchType = EnumBranchType.CallImport, - Segment = relocationRecord.TargetTypeValueTuple.Item3 - }); - break; - case EnumRecordsFlag.TARGET_MASK: - break; - } - }); - }); - } - - /// - /// This looks at the op and operand of the instructions and makes a best guess at the instructions that are referencing string data - /// We inspect any instruction that interacts with the DX or DS regstiers, as these hold the data segments and then look at the address - /// being referenced by that instruction. If we find a string at the address specified in any of the data segments, we'll return it as a possibility. - /// - /// - public static void ResolveStringReferences(NEFile file) - { - var flagNext = false; - var dataSegmentToUse = 0; - foreach (var segment in file.SegmentTable) - { - if (!segment.Flags.Contains(EnumSegmentFlags.Code) || segment.DisassemblyLines == null || segment.DisassemblyLines.Count == 0) - continue; - - foreach (var disassemblyLine in segment.DisassemblyLines) - { - - //mov opcode - if (disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Imov && - //Filter out any mov's with relative register math, mostly false positives - !disassemblyLine.Disassembly.ToString().Contains("-") && - !disassemblyLine.Disassembly.ToString().Contains("+") && - !disassemblyLine.Disassembly.ToString().Contains(":")) - { - //MOV ax, SEG ADDR sets the current Data Segment to use - if (disassemblyLine.BranchToRecords.Any(x => - x.IsRelocation && x.BranchType == EnumBranchType.SegAddr)) - dataSegmentToUse = disassemblyLine.BranchToRecords.First().Segment; - - - if (dataSegmentToUse > 0) - { - //mov dx, #### - if (disassemblyLine.Disassembly.Operands[0].Base == ud_type.UD_R_DX && - disassemblyLine.Disassembly.Operands.Length == 2 && - disassemblyLine.Disassembly.Operands[1].LvalUWord > 0) - { - disassemblyLine.StringReference = file.SegmentTable - .First(x => x.Ordinal == dataSegmentToUse).StringRecords.FirstOrDefault(y => - y.Offset == disassemblyLine.Disassembly.Operands[1].LvalUWord); - - continue; - } - - //mov ax, #### - if (flagNext && disassemblyLine.Disassembly.Operands[0].Base == ud_type.UD_R_AX && - disassemblyLine.Disassembly.Operands.Length == 2 && - disassemblyLine.Disassembly.Operands[1].LvalUWord > 0) - { - flagNext = false; - - disassemblyLine.StringReference = file.SegmentTable - .First(x => x.Ordinal == dataSegmentToUse).StringRecords.FirstOrDefault(y => - y.Offset == disassemblyLine.Disassembly.Operands[1].LvalUWord); - - continue; - } - - //mov dx, ds is usually followed by a mov ax, #### which is a string reference - if (disassemblyLine.Disassembly.Operands.Length == 2 && - disassemblyLine.Disassembly.Operands[0].Base == ud_type.UD_R_DX && - disassemblyLine.Disassembly.Operands[1].Base == ud_type.UD_R_DS) - { - flagNext = true; - continue; - } - } - } - - if (dataSegmentToUse >= 0) - { - //push #### following a push ds - if (flagNext && disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ipush && - disassemblyLine.Disassembly.Operands[0].LvalUWord > 0) - { - flagNext = false; - - disassemblyLine.StringReference = file.SegmentTable - .First(x => x.Ordinal == dataSegmentToUse).StringRecords.FirstOrDefault(y => - y.Offset == disassemblyLine.Disassembly.Operands[0].LvalUWord); - continue; - } - - //push ds followed by a push #### - if (disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ipush && - disassemblyLine.Disassembly.Operands.Any(x => x.Base == ud_type.UD_R_DS)) - { - flagNext = true; - continue; - } - } - - flagNext = false; - } - } - } - - /// - /// Scans through the disassembled code and adds comments on any Conditional or Unconditional Jump - /// Labels the destination where the source came from - /// - /// - public static void ResolveJumpTargets(NEFile file) - { - - //Setup variables to make if/where clauses much easier to read - var jumpShortOps = new[] {0xEB, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 0xE3}; - var jumpNearOps1stByte = new[] {0xE9, 0x0F}; - var jumpNearOps2ndByte = new[] - {0x80, 0x81, 0x82, 0x83, 0x84, 0x5, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F}; - - foreach (var segment in file.SegmentTable.Where(x=> x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) - { - //Only op+operand <= 3 bytes, skip jmp word ptr because we won't be able to label those - foreach (var disassemblyLine in segment.DisassemblyLines.Where(x => MnemonicGroupings.JumpGroup.Contains(x.Disassembly.Mnemonic) && x.Disassembly.Bytes.Length <= 3)) - { - ulong target = 0; - - //Jump Short, Relative to next Instruction (8 bit) - if (jumpShortOps.Contains(disassemblyLine.Disassembly.Bytes[0])) - { - target = ToRelativeOffset8(disassemblyLine.Disassembly.Bytes[1], disassemblyLine.Disassembly.Offset, disassemblyLine.Disassembly.Bytes.Length); - } - - //Jump Near, Relative to next Instruction (16 bit) - //Check to see if it's a 1 byte unconditinoal or a 2 byte conditional - if (jumpNearOps1stByte.Contains(disassemblyLine.Disassembly.Bytes[0]) && - (disassemblyLine.Disassembly.Bytes[0] == 0xE9 || jumpNearOps2ndByte.Contains(disassemblyLine.Disassembly.Bytes[1]))) - { - target = ToRelativeOffset16(BitConverter.ToUInt16(disassemblyLine.Disassembly.Bytes, - disassemblyLine.Disassembly.Bytes[0] == 0xE9 ? 1 : 2), disassemblyLine.Disassembly.Offset, disassemblyLine.Disassembly.Bytes.Length); - } - - //Set Target - segment.DisassemblyLines.FirstOrDefault(x => x.Disassembly.Offset == target)?.BranchFromRecords - .Add(new BranchRecord - { - Segment = segment.Ordinal, - Offset = disassemblyLine.Disassembly.Offset, - BranchType = - disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ijmp - ? EnumBranchType.Unconditional - : EnumBranchType.Conditional, - IsRelocation = false - }); - - //Set Origin - disassemblyLine.BranchToRecords.Add(new BranchRecord - { - Segment = segment.Ordinal, - Offset = target, - BranchType = - disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ijmp - ? EnumBranchType.Unconditional - : EnumBranchType.Conditional, - IsRelocation = false - }); - } - } - } - - /// - /// Scans through the code and adds comments to any Call - /// Labels the destination where the source came from - /// - /// - public static void ResolveCallTargets(NEFile file) - { - foreach (var segment in file.SegmentTable.Where(x=> x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0 )) - { - //Only processing 3 byte calls - foreach (var j in segment.DisassemblyLines.Where(x => - x.Disassembly.Bytes[0] == 0xE8 && x.Disassembly.Bytes.Length <= 3)) - { - - ulong target = (ushort) (BitConverter.ToUInt16(j.Disassembly.Bytes, 1) + j.Disassembly.Offset + 3); - - //Set Target - segment.DisassemblyLines.FirstOrDefault(x => - x.Disassembly.Offset == target)?.BranchFromRecords.Add(new BranchRecord() - { - Segment = segment.Ordinal, - Offset = j.Disassembly.Offset, - BranchType = EnumBranchType.Call, - IsRelocation = false - }); - - //Set Origin - j.BranchToRecords.Add(new BranchRecord() - { - Segment = segment.Ordinal, - Offset = target, - BranchType = EnumBranchType.Call, - IsRelocation = false - }); - } - } - } - - - /// - /// Scans through DATA segments within the specified file extracting NULL terminated strings - /// - /// - public static void ProcessStrings(NEFile file) - { - //Filter down potential segments - foreach(var seg in file.SegmentTable.Where(x => x.Flags.Contains(EnumSegmentFlags.Data))) - { - seg.StringRecords = new List(); - var sbBuffer = new StringBuilder(); - for (var i = 0; i < seg.Length; i++) - { - if (seg.Data[i] == 0x0) - { - if (sbBuffer.Length > 0) - { - seg.StringRecords.Add(new StringRecord - { - Segment = seg.Ordinal, - Offset = i - sbBuffer.Length, - Length = sbBuffer.Length, - Value = sbBuffer.ToString() - }); - sbBuffer.Clear(); - } - continue; - } - sbBuffer.Append((char)seg.Data[i]); - } - } - } - - - /// - /// Calculates Relative Offset for 16bit Operand - /// - /// - /// - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong ToRelativeOffset16(ushort operand, ulong currentOffset, int instructionLength) - { - if (operand < 0x7FFF) - { - //Near Forward Jump - return operand + currentOffset + (ulong)instructionLength; - } - - //Near Backwards Jump - return currentOffset - (ushort) ~operand + (ulong)instructionLength; - } - - /// - /// Calculates Relative Offset for 8bit Operand - /// - /// - /// - /// - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong ToRelativeOffset8(byte operand, ulong currentOffset, int instructionLength) - { - if (operand <= 0x7F) - { - //Short Forward Jump - return operand + currentOffset + (ulong)instructionLength; - } - - //Short Backwards Jump - return (ulong) ((int) currentOffset + instructionLength - ((byte) ~operand + 1)); - } - } - } \ No newline at end of file +{ + /// + /// Main Disassembler Class for 16-Bit x86 NE Format EXE/DLL Files + /// + public class Disassembler : IDisposable + { + protected static readonly Logger _logger = LogManager.GetCurrentClassLogger(typeof(CustomLogger)); + private NEFile _inputFile; + + public Disassembler(string inputFile) + { + _inputFile = new NEFile(inputFile); + } + + + public NEFile Disassemble(bool minimal = false) + { + //Decompile Each Segment + foreach (var s in _inputFile.SegmentTable) + { + _logger.Info($"Performing Disassembly of Segment {s.Ordinal}"); + s.DisassemblyLines = DisassembleSegment(s); + } + + //Skip Additional Analysis if they selected minimal + if (!minimal) + { + _logger.Info($"Extracting Strings from DATA Segments"); + ProcessStrings(_inputFile); + + _logger.Info($"Applying Relocation Info "); + ApplyRelocationInfo(_inputFile); + + _logger.Info($"Applying String References"); + ResolveStringReferences(_inputFile); + + _logger.Info($"Resolving Jump Targets"); + ResolveJumpTargets(_inputFile); + + _logger.Info($"Resolving Call Targets"); + ResolveCallTargets(_inputFile); + + _logger.Info($"Identifying Entry Points"); + IdentifyEntryPoints(_inputFile); + } + + return _inputFile; + } + + /// + /// Takes the raw binary code segment and feeds it into the x86 disassembler library + /// + /// + /// + private List DisassembleSegment(Segment segment) + { + //Only DisassembleSegment Code Segments + if (!segment.Flags.Contains(EnumSegmentFlags.Code)) + return new List(); + + var output = new List(); + + var disassembler = new SharpDisasm.Disassembler(segment.Data, ArchitectureMode.x86_16, 0, true); + + //Perform Raw Disassembly + var ordinal = 0; + foreach (var disassembly in disassembler.Disassemble()) + { + output.Add(new DisassemblyLine + { + Disassembly = disassembly, + Comments = new List(), + Ordinal = ordinal, + BranchFromRecords = new ConcurrentBag(), + BranchToRecords = new ConcurrentBag() + }); + ordinal++; + } + + return output; + } + + /// + /// Locates offsets for exported functions in the Entry table and labels them + /// + /// + private void IdentifyEntryPoints(NEFile file) + { + foreach (var entry in file.EntryTable) + { + var seg = file.SegmentTable.First(x => x.Ordinal == entry.SegmentNumber); + + var fnName = file.NonResidentNameTable.FirstOrDefault(x => x.IndexIntoEntryTable == entry.Ordinal) + ?.Name; + + if (string.IsNullOrEmpty(fnName)) + fnName = file.ResidentNameTable.FirstOrDefault(x => x.IndexIntoEntryTable == entry.Ordinal)?.Name; + + seg.DisassemblyLines.Where(x => x.Disassembly.Offset == entry.Offset) + .FirstOrDefault(x => + { + x.ExportedFunction = new ExportedFunctionRecord() {Name = fnName}; + return true; + }); + + } + } + + /// + /// Reads the Relocation Table (if present) at the end of a segment and comments about the relocations that + /// are being applied. This identifies both internal and external function calls. + /// + /// + private void ApplyRelocationInfo(NEFile file) + { + Parallel.ForEach(file.SegmentTable, (segment) => + { + if (!segment.Flags.Contains(EnumSegmentFlags.Code) && + !segment.Flags.Contains(EnumSegmentFlags.HasRelocationInfo)) + return; + Parallel.ForEach(segment.RelocationRecords, (relocationRecord) => + { + var disAsm = + segment.DisassemblyLines.FirstOrDefault(x => + x.Disassembly.Offset == relocationRecord.Offset - 1UL); + + if (disAsm == null) + return; + + switch (relocationRecord.Flag) + { + case EnumRecordsFlag.IMPORTORDINAL | EnumRecordsFlag.ADDITIVE: + case EnumRecordsFlag.IMPORTORDINAL: + disAsm.BranchToRecords.Add(new BranchRecord + { + IsRelocation = true, + BranchType = + disAsm.Disassembly.Mnemonic == ud_mnemonic_code.UD_Icall + ? EnumBranchType.CallImport + : EnumBranchType.SegAddrImport, + Segment = relocationRecord.TargetTypeValueTuple.Item2, + Offset = relocationRecord.TargetTypeValueTuple.Item3 + }); + break; + case EnumRecordsFlag.INTERNALREF | EnumRecordsFlag.ADDITIVE: + case EnumRecordsFlag.INTERNALREF: + if (disAsm.Disassembly.Mnemonic == ud_mnemonic_code.UD_Icall) + { + //Set Target + file.SegmentTable + .FirstOrDefault(x => x.Ordinal == relocationRecord.TargetTypeValueTuple.Item2) + ?.DisassemblyLines + .FirstOrDefault(y => + y.Disassembly.Offset == relocationRecord.TargetTypeValueTuple.Item4) + ?.BranchFromRecords + .Add(new BranchRecord() + { + Segment = segment.Ordinal, + Offset = disAsm.Disassembly.Offset, + IsRelocation = true, + BranchType = EnumBranchType.Call + }); + + //Set Origin + disAsm.BranchToRecords.Add(new BranchRecord() + { + Segment = relocationRecord.TargetTypeValueTuple.Item2, + Offset = relocationRecord.TargetTypeValueTuple.Item4, + BranchType = EnumBranchType.Call, + IsRelocation = true + }); + } + else + { + disAsm.BranchToRecords.Add(new BranchRecord() + { + IsRelocation = true, + BranchType = EnumBranchType.SegAddr, + Segment = relocationRecord.TargetTypeValueTuple.Item2 + }); + } + + break; + case EnumRecordsFlag.IMPORTNAME: + disAsm.BranchToRecords.Add(new BranchRecord + { + IsRelocation = true, + BranchType = EnumBranchType.CallImport, + Segment = relocationRecord.TargetTypeValueTuple.Item3 + }); + break; + case EnumRecordsFlag.TARGET_MASK: + break; + } + }); + }); + } + + /// + /// This looks at the op and operand of the instructions and makes a best guess at the instructions that are referencing string data + /// We inspect any instruction that interacts with the DX or DS regstiers, as these hold the data segments and then look at the address + /// being referenced by that instruction. If we find a string at the address specified in any of the data segments, we'll return it as a possibility. + /// + /// + private void ResolveStringReferences(NEFile file) + { + var flagNext = false; + var dataSegmentToUse = 0; + foreach (var segment in file.SegmentTable) + { + if (!segment.Flags.Contains(EnumSegmentFlags.Code) || segment.DisassemblyLines == null || + segment.DisassemblyLines.Count == 0) + continue; + + foreach (var disassemblyLine in segment.DisassemblyLines) + { + + //mov opcode + if (disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Imov && + //Filter out any mov's with relative register math, mostly false positives + !disassemblyLine.Disassembly.ToString().Contains("-") && + !disassemblyLine.Disassembly.ToString().Contains("+") && + !disassemblyLine.Disassembly.ToString().Contains(":")) + { + //MOV ax, SEG ADDR sets the current Data Segment to use + if (disassemblyLine.BranchToRecords.Any(x => + x.IsRelocation && x.BranchType == EnumBranchType.SegAddr)) + dataSegmentToUse = disassemblyLine.BranchToRecords.First().Segment; + + + if (dataSegmentToUse > 0) + { + //mov dx, #### + if (disassemblyLine.Disassembly.Operands[0].Base == ud_type.UD_R_DX && + disassemblyLine.Disassembly.Operands.Length == 2 && + disassemblyLine.Disassembly.Operands[1].LvalUWord > 0) + { + disassemblyLine.StringReference = file.SegmentTable + .First(x => x.Ordinal == dataSegmentToUse).StringRecords.Where(y => + y.Offset == disassemblyLine.Disassembly.Operands[1].LvalUWord).ToList(); + + continue; + } + + //mov ax, #### + if (flagNext && disassemblyLine.Disassembly.Operands[0].Base == ud_type.UD_R_AX && + disassemblyLine.Disassembly.Operands.Length == 2 && + disassemblyLine.Disassembly.Operands[1].LvalUWord > 0) + { + flagNext = false; + + disassemblyLine.StringReference = file.SegmentTable + .First(x => x.Ordinal == dataSegmentToUse).StringRecords.Where(y => + y.Offset == disassemblyLine.Disassembly.Operands[1].LvalUWord).ToList(); + + continue; + } + + //mov dx, ds is usually followed by a mov ax, #### which is a string reference + if (disassemblyLine.Disassembly.Operands.Length == 2 && + disassemblyLine.Disassembly.Operands[0].Base == ud_type.UD_R_DX && + disassemblyLine.Disassembly.Operands[1].Base == ud_type.UD_R_DS) + { + flagNext = true; + continue; + } + } + } + + if (dataSegmentToUse >= 0) + { + //push #### following a push ds + if (flagNext && disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ipush && + disassemblyLine.Disassembly.Operands[0].LvalUWord > 0) + { + flagNext = false; + + var potential = new List(); + foreach (var s in file.SegmentTable.Where(x => x.StringRecords != null)) + { + if (s.StringRecords.Any(x => + x.Offset == disassemblyLine.Disassembly.Operands[0].LvalUWord)) + { + potential.Add(s.StringRecords.First(x => + x.Offset == disassemblyLine.Disassembly.Operands[0].LvalUWord)); + } + } + + disassemblyLine.StringReference = potential.Where(x => x.IsPrintable).ToList(); + continue; + } + + //push ds followed by a push #### + if (disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ipush && + disassemblyLine.Disassembly.Operands.Any(x => x.Base == ud_type.UD_R_DS)) + { + flagNext = true; + continue; + } + } + + flagNext = false; + } + } + } + + /// + /// Scans through the disassembled code and adds comments on any Conditional or Unconditional Jump + /// Labels the destination where the source came from + /// + /// + private void ResolveJumpTargets(NEFile file) + { + + //Setup variables to make if/where clauses much easier to read + var jumpShortOps = new[] + { + 0xEB, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + 0xE3 + }; + var jumpNearOps1stByte = new[] {0xE9, 0x0F}; + var jumpNearOps2ndByte = new[] + {0x80, 0x81, 0x82, 0x83, 0x84, 0x5, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F}; + + foreach (var segment in file.SegmentTable.Where(x => + x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) + { + //Only op+operand <= 3 bytes, skip jmp word ptr because we won't be able to label those + foreach (var disassemblyLine in segment.DisassemblyLines.Where(x => + MnemonicGroupings.JumpGroup.Contains(x.Disassembly.Mnemonic) && x.Disassembly.Bytes.Length <= 3)) + { + ulong target = 0; + + //Jump Short, Relative to next Instruction (8 bit) + if (jumpShortOps.Contains(disassemblyLine.Disassembly.Bytes[0])) + { + target = ToRelativeOffset8(disassemblyLine.Disassembly.Bytes[1], + disassemblyLine.Disassembly.Offset, disassemblyLine.Disassembly.Bytes.Length); + } + + //Jump Near, Relative to next Instruction (16 bit) + //Check to see if it's a 1 byte unconditinoal or a 2 byte conditional + if (jumpNearOps1stByte.Contains(disassemblyLine.Disassembly.Bytes[0]) && + (disassemblyLine.Disassembly.Bytes[0] == 0xE9 || + jumpNearOps2ndByte.Contains(disassemblyLine.Disassembly.Bytes[1]))) + { + target = ToRelativeOffset16(BitConverter.ToUInt16(disassemblyLine.Disassembly.Bytes, + disassemblyLine.Disassembly.Bytes[0] == 0xE9 ? 1 : 2), + disassemblyLine.Disassembly.Offset, + disassemblyLine.Disassembly.Bytes.Length); + } + + //Set Target + segment.DisassemblyLines.FirstOrDefault(x => x.Disassembly.Offset == target)?.BranchFromRecords + .Add(new BranchRecord + { + Segment = segment.Ordinal, + Offset = disassemblyLine.Disassembly.Offset, + BranchType = + disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ijmp + ? EnumBranchType.Unconditional + : EnumBranchType.Conditional, + IsRelocation = false + }); + + //Set Origin + disassemblyLine.BranchToRecords.Add(new BranchRecord + { + Segment = segment.Ordinal, + Offset = target, + BranchType = + disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ijmp + ? EnumBranchType.Unconditional + : EnumBranchType.Conditional, + IsRelocation = false + }); + } + } + } + + /// + /// Scans through the code and adds comments to any Call + /// Labels the destination where the source came from + /// + /// + private void ResolveCallTargets(NEFile file) + { + foreach (var segment in file.SegmentTable.Where(x => + x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) + { + //Only processing 3 byte calls + foreach (var j in segment.DisassemblyLines.Where(x => + x.Disassembly.Bytes[0] == 0xE8 && x.Disassembly.Bytes.Length <= 3)) + { + + ulong target = (ushort) (BitConverter.ToUInt16(j.Disassembly.Bytes, 1) + j.Disassembly.Offset + 3); + + //Set Target + segment.DisassemblyLines.FirstOrDefault(x => + x.Disassembly.Offset == target)?.BranchFromRecords.Add(new BranchRecord() + { + Segment = segment.Ordinal, + Offset = j.Disassembly.Offset, + BranchType = EnumBranchType.Call, + IsRelocation = false + }); + + //Set Origin + j.BranchToRecords.Add(new BranchRecord() + { + Segment = segment.Ordinal, + Offset = target, + BranchType = EnumBranchType.Call, + IsRelocation = false + }); + } + } + } + + + /// + /// Scans through DATA segments within the specified file extracting NULL terminated strings + /// + /// + private void ProcessStrings(NEFile file) + { + //Filter down potential segments + foreach (var seg in file.SegmentTable.Where(x => x.Flags.Contains(EnumSegmentFlags.Data))) + { + seg.StringRecords = new List(); + var sbBuffer = new StringBuilder(); + for (var i = 0; i < seg.Length; i++) + { + if (seg.Data[i] == 0x0) + { + if (sbBuffer.Length > 0) + { + seg.StringRecords.Add(new StringRecord + { + Segment = seg.Ordinal, + Offset = i - sbBuffer.Length, + Length = sbBuffer.Length, + Value = sbBuffer.ToString() + }); + sbBuffer.Clear(); + } + + continue; + } + + sbBuffer.Append((char) seg.Data[i]); + } + } + } + + + /// + /// Calculates Relative Offset for 16bit Operand + /// + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong ToRelativeOffset16(ushort operand, ulong currentOffset, int instructionLength) + { + if (operand < 0x7FFF) + { + //Near Forward Jump + return operand + currentOffset + (ulong) instructionLength; + } + + //Near Backwards Jump + return currentOffset - (ushort) ~operand + (ulong) instructionLength; + } + + /// + /// Calculates Relative Offset for 8bit Operand + /// + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong ToRelativeOffset8(byte operand, ulong currentOffset, int instructionLength) + { + if (operand <= 0x7F) + { + //Short Forward Jump + return operand + currentOffset + (ulong) instructionLength; + } + + //Short Backwards Jump + return (ulong) ((int) currentOffset + instructionLength - ((byte) ~operand + 1)); + } + + + /// + public void Dispose() + { + _inputFile = null; + } + } +} \ No newline at end of file diff --git a/MBBSDASM/Dasm/DisassemblyLine.cs b/MBBSDASM/Dasm/DisassemblyLine.cs index 1399bf6..4246172 100644 --- a/MBBSDASM/Dasm/DisassemblyLine.cs +++ b/MBBSDASM/Dasm/DisassemblyLine.cs @@ -17,7 +17,7 @@ public class DisassemblyLine public ExportedFunctionRecord ExportedFunction { get; set; } public ConcurrentBag BranchToRecords { get; set; } public ConcurrentBag BranchFromRecords { get; set; } - public StringRecord StringReference { get; set; } + public List StringReference { get; set; } public ushort SubroutineID { get; set; } } } \ No newline at end of file diff --git a/MBBSDASM/Logging/CustomLogger.cs b/MBBSDASM/Logging/CustomLogger.cs new file mode 100644 index 0000000..49208d0 --- /dev/null +++ b/MBBSDASM/Logging/CustomLogger.cs @@ -0,0 +1,24 @@ +using NLog; +using NLog.Layouts; + +namespace MBBSDASM.Logging +{ + public class CustomLogger : Logger + { + + static CustomLogger() + { + var config = new NLog.Config.LoggingConfiguration(); + + //Setup Console Logging + var logconsole = new NLog.Targets.ConsoleTarget("logconsole") + { + Layout = Layout.FromString("${shortdate}\t${time}\t${message}") + }; + config.AddTarget(logconsole); + config.AddRuleForAllLevels(logconsole); + + LogManager.Configuration = config; + } + } +} diff --git a/MBBSDASM/MBBSDASM.csproj b/MBBSDASM/MBBSDASM.csproj index ea40fac..dcc292d 100644 --- a/MBBSDASM/MBBSDASM.csproj +++ b/MBBSDASM/MBBSDASM.csproj @@ -5,15 +5,21 @@ 7.2 - - - + + + + + + + + + diff --git a/MBBSDASM/Program.cs b/MBBSDASM/Program.cs index abb7b14..dbd6f49 100644 --- a/MBBSDASM/Program.cs +++ b/MBBSDASM/Program.cs @@ -1,273 +1,25 @@ -using System; -using System.IO; -using System.Linq; -using System.Text; -using MBBSDASM.Artifacts; -using MBBSDASM.Enums; +using MBBSDASM.UI; +using MBBSDASM.UI.impl; namespace MBBSDASM { /// - /// Main Console Entrypoint + /// Main ConsoleUI Entrypoint /// class Program { - private const int _MAX_INSTRUCTION_LENGTH = 15; - static void Main(string[] args) - { - Console.WriteLine("------------------------------------------------------"); - Console.WriteLine("MBBSDASM v1.4"); - Console.WriteLine("GitHub: http://www.github.com/enusbaum/mbbsdasm/"); - Console.WriteLine("------------------------------------------------------"); - - if (args.Length == 0) - { - Console.WriteLine("Please use the -? option for help"); - return; - } - - try - { - //Command Line Values - var sInputFile = ""; - var sOutputFile = ""; - var bMinimal = false; - var bAnalysis = false; - var bStrings = false; - for (var i = 0; i < args.Length; i++) - { - switch (args[i].ToUpper()) - { - case "-I": - sInputFile = args[i + 1]; - i++; - break; - case "-O": - sOutputFile = args[i + 1]; - i++; - break; - case "-MINIMAL": - bMinimal = true; - break; - case "-ANALYSIS": - bAnalysis = true; - break; - case "-STRINGS": - bStrings = true; - break; - case "-?": - Console.WriteLine("-I -- Input File to Disassemble"); - Console.WriteLine("-O -- Output File for Disassembly (Default Console)"); - Console.WriteLine("-MINIMAL -- Minimal Disassembler Output"); - Console.WriteLine("-ANALYSIS -- Additional Analysis on Imported Functions (if available)"); - Console.WriteLine("-STRINGS -- Output all strings found in DATA segments at end of Disassembly"); - return; - } - } - - //Verify Input File is Valid - if (string.IsNullOrEmpty(sInputFile) || !File.Exists(sInputFile)) - throw new Exception("Error: Please specify a valid input file"); - - //Warn of Analysis not being available with minimal output - if (bMinimal && bAnalysis) - Console.WriteLine($"{DateTime.Now} Warning: Analysis Mode unavailable with minimal output option, ignoring"); - - Console.WriteLine($"{DateTime.Now} Inspecting File: {sInputFile}"); - - //Read the entire file to memory - var inputFile = new NEFile(sInputFile); - - //Decompile Each Segment - foreach (var s in inputFile.SegmentTable) - { - Console.WriteLine($"{DateTime.Now} Performing Disassembly of Segment {s.Ordinal}"); - s.DisassemblyLines = Dasm.Disassembler.Disassemble(s); - } - - //Skip Additional Analysis if they selected minimal - if (!bMinimal) - { - Console.WriteLine($"{DateTime.Now} Extracting Strings from DATA Segments"); - Dasm.Disassembler.ProcessStrings(inputFile); - - Console.WriteLine($"{DateTime.Now} Applying Relocation Info "); - Dasm.Disassembler.ApplyRelocationInfo(inputFile); - - Console.WriteLine($"{DateTime.Now} Applying String References"); - Dasm.Disassembler.ResolveStringReferences(inputFile); - - Console.WriteLine($"{DateTime.Now} Resolving Jump Targets"); - Dasm.Disassembler.ResolveJumpTargets(inputFile); - - Console.WriteLine($"{DateTime.Now} Resolving Call Targets"); - Dasm.Disassembler.ResolveCallTargets(inputFile); - Console.WriteLine($"{DateTime.Now} Identifying Entry Points"); - Dasm.Disassembler.IdentifyEntryPoints(inputFile); + private static IUserInterface _userInterface; - //Apply Selected Analysis - if (bAnalysis) - { - Console.WriteLine($"{DateTime.Now} Performing Additional Analysis"); - Analysis.Analyzer.Analyze(inputFile); - } - } - - Console.WriteLine($"{DateTime.Now} Writing Disassembly Output"); - - //Build Final Output - var output = new StringBuilder(); - - output.AppendLine($"; Disassembly of {inputFile.Path}{inputFile.FileName}"); - output.AppendLine($"; Description: {inputFile.NonResidentNameTable[0].Name}"); - output.AppendLine(";"); - output.AppendLine(";-------------------------------------------"); - output.AppendLine("; Segment Information"); - output.AppendLine($"; Number of Code/Data Segments = {inputFile.WindowsHeader.SegmentTableEntries}"); - output.AppendLine(";-------------------------------------------"); - foreach (var s in inputFile.SegmentTable) - { - output.AppendLine( - $"; Segment #{s.Ordinal:0000}\tOffset: {s.Offset:X8}\tSize: {s.Data.Length:X4}\t Flags: 0x{s.Flag:X4} -> {(s.Flags.Contains(EnumSegmentFlags.Code) ? "CODE" : "DATA")}, {(s.Flags.Contains(EnumSegmentFlags.Fixed) ? "FIXED" : "MOVABLE")}"); - } - - output.AppendLine(";-------------------------------------------"); - output.AppendLine("; Entry Table Information"); - output.AppendLine($"; Number of Entry Table Functions = {inputFile.EntryTable.Count}"); - output.AppendLine(";-------------------------------------------"); - foreach (var t in inputFile.NonResidentNameTable) - { - if (t.IndexIntoEntryTable == 0) - continue; - - output.AppendLine($"; Addr:{inputFile.EntryTable.FirstOrDefault(x=> x.Ordinal == t.IndexIntoEntryTable)?.SegmentNumber:0000}.{inputFile.EntryTable.FirstOrDefault(x=> x.Ordinal == t.IndexIntoEntryTable)?.Offset:X4}\tOrd:{t.IndexIntoEntryTable:0000}d\tName: {t.Name}"); - } - foreach (var t in inputFile.ResidentNameTable) - { - if (t.IndexIntoEntryTable == 0) - continue; - - output.AppendLine($"; Addr:{inputFile.EntryTable.FirstOrDefault(x=> x.Ordinal == t.IndexIntoEntryTable)?.SegmentNumber:0000}.{inputFile.EntryTable.FirstOrDefault(x=> x.Ordinal == t.IndexIntoEntryTable)?.Offset:X4}\tOrd:{t.IndexIntoEntryTable:0000}d\tName: {t.Name}"); - } - - output.AppendLine(";"); - - //Write Disassembly to output - foreach (var s in inputFile.SegmentTable.Where(x => x.Flags.Contains(EnumSegmentFlags.Code))) - { - output.AppendLine(";-------------------------------------------"); - output.AppendLine($"; Start of Code for Segment {s.Ordinal}"); - output.AppendLine("; FILE_OFFSET:SEG_NUM.SEG_OFFSET BYTES DISASSEMBLY"); - output.AppendLine(";-------------------------------------------"); - - //Allows us to line up all the comments in a segment along the same column - var maxDecodeLength = s.DisassemblyLines.Max(x => x.Disassembly.ToString().Length + _MAX_INSTRUCTION_LENGTH + 1) + 21; - - //Write each line of the disassembly to the output stream - foreach (var d in s.DisassemblyLines) - { - //Label Entrypoints/Exported Functions - if (d.ExportedFunction != null) - { - d.Comments.Add($"Exported Function: {d.ExportedFunction.Name}"); - } - - //Label Branch Targets - foreach (var b in d.BranchFromRecords) - { - switch (b.BranchType) - { - case EnumBranchType.Call: - d.Comments.Add( - $"Referenced by CALL at address: {b.Segment:0000}.{b.Offset:X4}h {(b.IsRelocation ? "(Relocation)" : string.Empty)}"); - break; - case EnumBranchType.Conditional: - case EnumBranchType.Unconditional: - d.Comments.Add( - $"{(b.BranchType == EnumBranchType.Conditional ? "Conditional" : "Unconditional")} jump from {b.Segment:0000}:{b.Offset:X4}h"); - break; - } - } - - //Label Branch Origins (Relocation) - foreach (var b in d.BranchToRecords.Where(x => x.IsRelocation && x.BranchType == EnumBranchType.Call)) - d.Comments.Add($"CALL {b.Segment:0000}.{b.Offset:X4}h (Relocation)"); - - //Label Refereces by SEG ADDR (Internal) - foreach(var b in d.BranchToRecords.Where(x=> x.IsRelocation && x.BranchType == EnumBranchType.SegAddr)) - d.Comments.Add($"SEG ADDR of Segment {b.Segment}"); - - //Label String References - if(d.StringReference != null) - d.Comments.Add($"Possible String reference from SEG {d.StringReference.Segment} -> \"{d.StringReference.Value}\""); - - //Only label Imports if Analysis is off, because Analysis does much more in-depth labeling - if (!bAnalysis) - { - foreach(var b in d.BranchToRecords?.Where(x=> x.IsRelocation && (x.BranchType == EnumBranchType.CallImport || x.BranchType == EnumBranchType.SegAddrImport))) - d.Comments.Add($"{(b.BranchType == EnumBranchType.CallImport ? "call" : "SEG ADDR of" )} {inputFile.ImportedNameTable.FirstOrDefault(x => x.Ordinal == b.Segment)?.Name}.Ord({b.Offset:X4}h)"); - } - - var sOutputLine = $"{d.Disassembly.Offset + s.Offset:X8}h:{s.Ordinal:0000}.{d.Disassembly.Offset:X4}h {BitConverter.ToString(d.Disassembly.Bytes).Replace("-", string.Empty).PadRight(_MAX_INSTRUCTION_LENGTH, ' ')} {d.Disassembly}"; - if (d.Comments != null && d.Comments.Count > 0) - { - var newLine = false; - var firstCommentIndex = 0; - foreach (var c in d.Comments) - { - if (!newLine) - { - sOutputLine += $"{new string(' ', maxDecodeLength - sOutputLine.Length)}; {c}"; - - //Set variables to help us keep the following comments lined up with the first one - firstCommentIndex = sOutputLine.IndexOf(';'); - newLine = true; - continue; - } - sOutputLine += $"\r\n{new string(' ', firstCommentIndex) }; {c}"; - } - } - output.AppendLine(sOutputLine); - } - output.AppendLine(); - } - - //Write Strings to Output - if (bStrings) - { - Console.WriteLine($"{DateTime.Now} Writing Strings Output"); - - foreach (var seg in inputFile.SegmentTable.Where(x => - x.Flags.Contains(EnumSegmentFlags.Data) && x.StringRecords?.Count > 0)) - { - output.AppendLine(";-------------------------------------------"); - output.AppendLine($"; Start of Data for Segment {seg.Ordinal}"); - output.AppendLine("; FILE_OFFSET:SEG_NUM.SEG_OFFSET"); - output.AppendLine(";-------------------------------------------"); - foreach (var str in seg.StringRecords) - output.AppendLine( - $"{str.Offset + str.Offset:X8}h:{seg.Ordinal:0000}.{str.Offset:X4}h '{str.Value}'"); - } - } + static void Main(string[] args) + { + //Set the interface based on the args passed in + _userInterface = args.Length == 0 ? (IUserInterface) new InteractiveUI() : new ConsoleUI(args); - if (string.IsNullOrEmpty(sOutputFile)) - { - Console.WriteLine(output.ToString()); - } - else - { - Console.WriteLine($"{DateTime.Now} Writing Disassembly to {sOutputFile}"); - File.WriteAllText(sOutputFile, output.ToString()); - } - Console.WriteLine($"{DateTime.Now} Done!"); - } - catch (Exception e) - { - Console.WriteLine(e); - Console.WriteLine($"{DateTime.Now} Fatal Exception -- Exiting"); - } + _userInterface.Run(); } + + } } \ No newline at end of file diff --git a/MBBSDASM/Renderer/impl/StringRenderer.cs b/MBBSDASM/Renderer/impl/StringRenderer.cs new file mode 100644 index 0000000..1a7ea5b --- /dev/null +++ b/MBBSDASM/Renderer/impl/StringRenderer.cs @@ -0,0 +1,211 @@ +using System; +using System.Linq; +using System.Text; +using MBBSDASM.Artifacts; +using MBBSDASM.Enums; + +namespace MBBSDASM.Renderer.impl +{ + /// + /// String Renderer + /// + /// Used to generate human readable string output of the disassembly. Mainly used to output to a text file + /// + public class StringRenderer + { + /// + /// Input File + /// + private readonly NEFile _inputFile; + + /// + /// Default Constructor + /// + /// + public StringRenderer(NEFile inputFile) + { + _inputFile = inputFile; + } + + /// + /// Renders the Segment Information as readable text + /// + /// + public string RenderSegmentInformation() + { + var output = new StringBuilder(); + + output.AppendLine(";-------------------------------------------"); + output.AppendLine("; Segment Information"); + output.AppendLine($"; Number of Code/Data Segments = {_inputFile.WindowsHeader.SegmentTableEntries}"); + output.AppendLine(";-------------------------------------------"); + foreach (var s in _inputFile.SegmentTable) + { + output.AppendLine( + $"; Segment #{s.Ordinal:0000}\tOffset: {s.Offset:X8}\tSize: {s.Data.Length:X4}\t Flags: 0x{s.Flag:X4} -> {(s.Flags.Contains(EnumSegmentFlags.Code) ? "CODE" : "DATA")}, {(s.Flags.Contains(EnumSegmentFlags.Fixed) ? "FIXED" : "MOVABLE")}"); + } + + return output.ToString(); + } + + /// + /// Renders the Entry Table as readable text + /// + /// + public string RenderEntryTable() + { + var output = new StringBuilder(); + + output.AppendLine(";-------------------------------------------"); + output.AppendLine("; Entry Table Information"); + output.AppendLine($"; Number of Entry Table Functions = {_inputFile.EntryTable.Count}"); + output.AppendLine(";-------------------------------------------"); + foreach (var t in _inputFile.NonResidentNameTable) + { + if (t.IndexIntoEntryTable == 0) + continue; + + output.AppendLine( + $"; Addr:{_inputFile.EntryTable.FirstOrDefault(x => x.Ordinal == t.IndexIntoEntryTable)?.SegmentNumber:0000}.{_inputFile.EntryTable.FirstOrDefault(x => x.Ordinal == t.IndexIntoEntryTable)?.Offset:X4}\tOrd:{t.IndexIntoEntryTable:0000}d\tName: {t.Name}"); + } + + foreach (var t in _inputFile.ResidentNameTable) + { + if (t.IndexIntoEntryTable == 0) + continue; + + output.AppendLine( + $"; Addr:{_inputFile.EntryTable.FirstOrDefault(x => x.Ordinal == t.IndexIntoEntryTable)?.SegmentNumber:0000}.{_inputFile.EntryTable.FirstOrDefault(x => x.Ordinal == t.IndexIntoEntryTable)?.Offset:X4}\tOrd:{t.IndexIntoEntryTable:0000}d\tName: {t.Name}"); + } + + return output.ToString(); + } + + /// + /// Renders the Disassembly output as readable text + /// + /// + /// + public string RenderDisassembly(bool analysis = false) + { + var output = new StringBuilder(); + + //Write Disassembly to output + foreach (var s in _inputFile.SegmentTable.Where(x => x.Flags.Contains(EnumSegmentFlags.Code))) + { + output.AppendLine(";-------------------------------------------"); + output.AppendLine($"; Start of Code for Segment {s.Ordinal}"); + output.AppendLine("; FILE_OFFSET:SEG_NUM.SEG_OFFSET BYTES DISASSEMBLY"); + output.AppendLine(";-------------------------------------------"); + + //Allows us to line up all the comments in a segment along the same column + var maxDecodeLength = + s.DisassemblyLines.Max(x => + x.Disassembly.ToString().Length + Constants.MAX_INSTRUCTION_LENGTH + 1) + 21; + + //Write each line of the disassembly to the output stream + foreach (var d in s.DisassemblyLines) + { + //Label Entrypoints/Exported Functions + if (d.ExportedFunction != null) + { + d.Comments.Add($"Exported Function: {d.ExportedFunction.Name}"); + } + + //Label Branch Targets + foreach (var b in d.BranchFromRecords) + { + switch (b.BranchType) + { + case EnumBranchType.Call: + d.Comments.Add( + $"Referenced by CALL at address: {b.Segment:0000}.{b.Offset:X4}h {(b.IsRelocation ? "(Relocation)" : string.Empty)}"); + break; + case EnumBranchType.Conditional: + case EnumBranchType.Unconditional: + d.Comments.Add( + $"{(b.BranchType == EnumBranchType.Conditional ? "Conditional" : "Unconditional")} jump from {b.Segment:0000}:{b.Offset:X4}h"); + break; + } + } + + //Label Branch Origins (Relocation) + foreach (var b in d.BranchToRecords.Where(x => + x.IsRelocation && x.BranchType == EnumBranchType.Call)) + d.Comments.Add($"CALL {b.Segment:0000}.{b.Offset:X4}h (Relocation)"); + + //Label Refereces by SEG ADDR (Internal) + foreach (var b in d.BranchToRecords.Where(x => + x.IsRelocation && x.BranchType == EnumBranchType.SegAddr)) + d.Comments.Add($"SEG ADDR of Segment {b.Segment}"); + + //Label String References + if (d.StringReference != null) + foreach (var sr in d.StringReference) + d.Comments.Add($"Possible String reference from SEG {sr.Segment} -> \"{sr.Value}\""); + + //Only label Imports if Analysis is off, because Analysis does much more in-depth labeling + if (!analysis) + { + foreach (var b in d.BranchToRecords?.Where(x => + x.IsRelocation && (x.BranchType == EnumBranchType.CallImport || + x.BranchType == EnumBranchType.SegAddrImport))) + d.Comments.Add( + $"{(b.BranchType == EnumBranchType.CallImport ? "call" : "SEG ADDR of")} {_inputFile.ImportedNameTable.FirstOrDefault(x => x.Ordinal == b.Segment)?.Name}.Ord({b.Offset:X4}h)"); + } + + var sOutputLine = + $"{d.Disassembly.Offset + s.Offset:X8}h:{s.Ordinal:0000}.{d.Disassembly.Offset:X4}h {BitConverter.ToString(d.Disassembly.Bytes).Replace("-", string.Empty).PadRight(Constants.MAX_INSTRUCTION_LENGTH, ' ')} {d.Disassembly}"; + if (d.Comments != null && d.Comments.Count > 0) + { + var newLine = false; + var firstCommentIndex = 0; + foreach (var c in d.Comments) + { + if (!newLine) + { + sOutputLine += $"{new string(' ', maxDecodeLength - sOutputLine.Length)}; {c}"; + + //Set variables to help us keep the following comments lined up with the first one + firstCommentIndex = sOutputLine.IndexOf(';'); + newLine = true; + continue; + } + + sOutputLine += $"\r\n{new string(' ', firstCommentIndex)}; {c}"; + } + } + + output.AppendLine(sOutputLine); + } + + output.AppendLine(); + } + + return output.ToString(); + } + + /// + /// Renders strings in DATA segments as readable text + /// + /// + public string RenderStrings() + { + var output = new StringBuilder(); + + foreach (var seg in _inputFile.SegmentTable.Where(x => + x.Flags.Contains(EnumSegmentFlags.Data) && x.StringRecords?.Count > 0)) + { + output.AppendLine(";-------------------------------------------"); + output.AppendLine($"; Start of Data for Segment {seg.Ordinal}"); + output.AppendLine("; FILE_OFFSET:SEG_NUM.SEG_OFFSET"); + output.AppendLine(";-------------------------------------------"); + foreach (var str in seg.StringRecords) + output.AppendLine( + $"{str.Offset + str.Offset:X8}h:{seg.Ordinal:0000}.{str.Offset:X4}h '{str.Value}'"); + } + + return output.ToString(); + } + } +} diff --git a/MBBSDASM/UI/IUserInterface.cs b/MBBSDASM/UI/IUserInterface.cs new file mode 100644 index 0000000..b003a5d --- /dev/null +++ b/MBBSDASM/UI/IUserInterface.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace MBBSDASM.UI +{ + internal interface IUserInterface + { + void Run(); + } +} diff --git a/MBBSDASM/UI/impl/ConsoleUI.cs b/MBBSDASM/UI/impl/ConsoleUI.cs new file mode 100644 index 0000000..00f7783 --- /dev/null +++ b/MBBSDASM/UI/impl/ConsoleUI.cs @@ -0,0 +1,177 @@ +using System; +using System.IO; +using System.Linq; +using System.Text; +using MBBSDASM.Dasm; +using MBBSDASM.Enums; +using MBBSDASM.Logging; +using MBBSDASM.Renderer.impl; +using NLog; + +namespace MBBSDASM.UI.impl +{ + /// + /// ConsoleUI Class + /// + /// This class is used when command line switches are passed into MBBSDASM, allowing users + /// to bypass the interactive UI functionality and work strictly with command line arguments + /// + public class ConsoleUI : IUserInterface + { + /// + /// Logger Implementation + /// + protected static readonly Logger _logger = LogManager.GetCurrentClassLogger(typeof(CustomLogger)); + + /// + /// Args passed in via command line + /// + private readonly string[] _args; + + /// + /// Input File + /// Specified by the -i argument + /// + private string _sInputFile = string.Empty; + + /// + /// Output File + /// Specified with the -o argument + /// + private string _sOutputFile = string.Empty; + + /// + /// Minimal Disassembly + /// Specified with the -minimal argument + /// Will only do basic disassembly of opcodes + /// + private bool _bMinimal; + + /// + /// MBBS Analysis Mode + /// Specified with the -analysis argument + /// Will perform in-depth analysis of imported MBBS/WG functions and include detailed information and labeling + /// + private bool _bAnalysis; + + /// + /// Strings Analysis + /// Specified with the -string argument + /// Includes all strings discovered in DATA segments at the end of the disassembly output + /// + private bool _bStrings; + + /// + /// Default Constructor + /// + /// string - Command Line Arguments + public ConsoleUI(string[] args) + { + _args = args; + } + + /// + /// (IUserInterface) Runs the specified User Interface + /// + public void Run() + { + try + { + //Command Line Values + + for (var i = 0; i < _args.Length; i++) + { + switch (_args[i].ToUpper()) + { + case "-I": + _sInputFile = _args[i + 1]; + i++; + break; + case "-O": + _sOutputFile = _args[i + 1]; + i++; + break; + case "-MINIMAL": + _bMinimal = true; + break; + case "-ANALYSIS": + _bAnalysis = true; + break; + case "-STRINGS": + _bStrings = true; + break; + case "-?": + Console.WriteLine("-I -- Input File to DisassembleSegment"); + Console.WriteLine("-O -- Output File for Disassembly (Default ConsoleUI)"); + Console.WriteLine("-MINIMAL -- Minimal Disassembler Output"); + Console.WriteLine( + "-ANALYSIS -- Additional Analysis on Imported Functions (if available)"); + Console.WriteLine( + "-STRINGS -- Output all strings found in DATA segments at end of Disassembly"); + return; + } + } + + //Verify Input File is Valid + if (string.IsNullOrEmpty(_sInputFile) || !File.Exists(_sInputFile)) + throw new Exception("Error: Please specify a valid input file"); + + //Warn of Analysis not being available with minimal output + if (_bMinimal && _bAnalysis) + _logger.Warn( + $"Warning: Analysis Mode unavailable with minimal output option, ignoring"); + + _logger.Info($"Inspecting File: {_sInputFile}"); + + //Perform Disassmebly + var dasm = new Disassembler(_sInputFile); + var inputFile = dasm.Disassemble(_bMinimal); + + //Apply Selected Analysis + if (_bAnalysis) + { + _logger.Info($"Performing Additional Analysis"); + Analysis.MBBS.Analyze(inputFile); + } + + _logger.Info($"Writing Disassembly Output"); + + //Build Final Output + var renderer = new StringRenderer(inputFile); + var output = new StringBuilder(); + output.AppendLine($"; Disassembly of {inputFile.Path}{inputFile.FileName}"); + output.AppendLine($"; Description: {inputFile.NonResidentNameTable[0].Name}"); + output.AppendLine(";"); + + //Render Segment Information to output + output.Append(renderer.RenderSegmentInformation()); + output.Append(renderer.RenderEntryTable()); + output.AppendLine(";"); + output.Append(renderer.RenderDisassembly(_bAnalysis)); + + //Write Strings to Output + if (_bStrings) + { + output.Append(renderer.RenderStrings()); + } + + if (string.IsNullOrEmpty(_sOutputFile)) + { + _logger.Info(output.ToString()); + } + else + { + _logger.Info($"{DateTime.Now} Writing Disassembly to {_sOutputFile}"); + File.WriteAllText(_sOutputFile, output.ToString()); + } + + _logger.Info($"{DateTime.Now} Done!"); + } + catch (Exception e) + { + _logger.Error(e); + _logger.Error($"{DateTime.Now} Fatal Exception -- Exiting"); + } + } + } +} \ No newline at end of file diff --git a/MBBSDASM/UI/impl/InteractiveUI.cs b/MBBSDASM/UI/impl/InteractiveUI.cs new file mode 100644 index 0000000..5f9f20d --- /dev/null +++ b/MBBSDASM/UI/impl/InteractiveUI.cs @@ -0,0 +1,178 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using MBBSDASM.Dasm; +using MBBSDASM.Renderer.impl; +using Terminal.Gui; + +namespace MBBSDASM.UI.impl +{ + public class InteractiveUI : IUserInterface + { + private string _selectedFile; + private bool _DisassemblyLoaded; + + private bool _optionMBBSAnalysis; + private bool _optionStrings; + private bool _optionMinimal; + private string _outputFile; + + private MenuBar _topMenuBar; + private Window _mainWindow; + private readonly ProgressBar _progressBar; + private readonly Label _statusLabel; + internal InteractiveUI() + { + Application.Init(); + + //Define Main Window + _mainWindow = new Window(new Rect(0, 1, Application.Top.Frame.Width, Application.Top.Frame.Height - 1), null); + _mainWindow.Add(new Label(0, 0, $"--=[About {Constants.ProgramName}]=--")); + _mainWindow.Add(new Label(0, 1, $"{Constants.ProgramName} is a x86 16-Bit NE Disassembler with advanced analysis for MajorBBS/Worldgroup modules")); + _mainWindow.Add(new Label(0, 3, $"--=[Credits]=--")); + _mainWindow.Add(new Label(0, 4, $"{Constants.ProgramName} is Copyright (c) 2019 Eric Nusbaum and is distributed under the 2-clause \"Simplified BSD License\". ")); + _mainWindow.Add(new Label(0, 5, "SharpDisam is Copyright (c) 2015 Justin Stenning and is distributed under the 2-clause \"Simplified BSD License\". ")); + _mainWindow.Add(new Label(0, 6, "Terminal.Gui is Copyright (c) 2017 Microsoft Corp and is distributed under the MIT License")); + _mainWindow.Add(new Label(0, 8, $"--=[Code]=--")); + _mainWindow.Add(new Label(0, 9, "http://www.github.com/enusbaum/mbbsdasm")); + _progressBar = + new ProgressBar(new Rect(1, Application.Top.Frame.Height - 5, Application.Top.Frame.Width - 4, 1)); + _mainWindow.Add(_progressBar); + _statusLabel = new Label(1, Application.Top.Frame.Height - 7, "Ready!"); + _mainWindow.Add(_statusLabel); + Application.Top.Add(_mainWindow); + + //Draw Menu Items + // Creates a menubar, the item "New" has a help menu. + var menuItems = new List(); + + menuItems.Add( + new MenuBarItem("_File", new MenuItem[] + { + new MenuItem("_Disassemble", "", OpenFile), + new MenuItem("_Exit", "", () => { Application.Top.Running = false; }) + })); + + _topMenuBar = new MenuBar(menuItems.ToArray()); + Application.Top.Add(_topMenuBar); + + } + + public void Run() + { + //Run it + Application.Run(); + } + + private void OpenFile() + { + //Show Open File Dialog + var fOpenDialog = new OpenDialog("Open File for Disassembly", "DisassembleSegment File") + { + CanChooseFiles = true, + AllowsMultipleSelection = false, + CanChooseDirectories = false, + AllowedFileTypes = new[] {"dll", "exe", "DLL", "EXE"} + }; + + Application.Run(fOpenDialog); + + //Get Selected File + _selectedFile = fOpenDialog.FilePaths.FirstOrDefault(); + + //If nothing is selected, bail + if (string.IsNullOrEmpty(_selectedFile)) + return; + + _outputFile = $"{_selectedFile.Substring(0, _selectedFile.Length -3)}asm"; + + //Show Disassembly Options + var analysisCheckBox = new CheckBox(20, 0, "Enhanced MBBS/WG Analysis") {Checked = true}; + var stringsCheckBox = new CheckBox(20, 1, "Process All Strings") { Checked = true }; + var disassemblyRadioGroup = new RadioGroup(0, 0, new[] {"_Minimal", "_Normal"}) {Selected = 1}; + + var disOptionsDialog = new Dialog("Disassembly Options", 60, 16) + { + new Label(0, 0, "Input File:"), + new TextField(0, 1, 55, _selectedFile), + new Label(0, 2, "Output File:"), + new TextField(0, 3, 55, _outputFile), + new FrameView(new Rect(0, 5, 55, 6), "Disassembly Options") + { + disassemblyRadioGroup, + analysisCheckBox, + stringsCheckBox + } + }; + disOptionsDialog.AddButton(new Button("OK", true) { Clicked = () => + { + Application.RequestStop(); + _optionMBBSAnalysis = analysisCheckBox.Checked; + _optionStrings = stringsCheckBox.Checked; + _optionMinimal = disassemblyRadioGroup.Selected == 0; + Task.Factory.StartNew(() => DoDisassembly()); + } + }); + disOptionsDialog.AddButton(new Button("Cancel", true) { Clicked = Application.RequestStop }); + + Application.Run(disOptionsDialog); + } + + private void DoDisassembly() + { + using (var dasm = new Disassembler(_selectedFile)) + { + if (File.Exists(_outputFile)) + File.Delete(_outputFile); + + _statusLabel.Text = "Performing Disassembly..."; + var inputFile = dasm.Disassemble(_optionMinimal); + + //Apply Selected Analysis + if (_optionMBBSAnalysis) + { + _statusLabel.Text = "Performing Additional Analysis..."; + Analysis.MBBS.Analyze(inputFile); + } + _progressBar.Fraction = .25f; + + + var _stringRenderer = new StringRenderer(inputFile); + + _statusLabel.Text = "Processing Segment Information..."; + File.AppendAllText(_outputFile, _stringRenderer.RenderSegmentInformation()); + _progressBar.Fraction = .50f; + + + _statusLabel.Text = "Processing Entry Table..."; + File.AppendAllText(_outputFile, _stringRenderer.RenderEntryTable()); + _progressBar.Fraction = .75f; + + + + _statusLabel.Text = "Processing Disassembly..."; + File.AppendAllText(_outputFile, _stringRenderer.RenderDisassembly(_optionMBBSAnalysis)); + _progressBar.Fraction = .85f; + + + if (_optionStrings) + { + _statusLabel.Text = "Processing Strings..."; + File.AppendAllText(_outputFile, _stringRenderer.RenderStrings()); + } + + _statusLabel.Text = "Done!"; + _progressBar.Fraction = 1f; + } + + var d = new Dialog($"Disassembly Complete!", 50, 12) + { + new Label(0, 0, $"Output File: {_outputFile}"), + new Label(0, 1, $"Bytes Written: {new FileInfo(_outputFile).Length}") + }; + d.AddButton(new Button("OK", true) { Clicked = Application.RequestStop }); + Application.Run(d); + } + } +} diff --git a/changelog.md b/changelog.md index c3df144..80c56c4 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,18 @@ # Changelog +## v1.5 +* Minor Bug Fixes +* Added Cross-Plaform Text UI Mode + * Using Terminal.Gui Library to provide interactive text UI + * Accessible by specifying no command line arguments +* Cleaned up Module Definition Auto-Documentation for **GALGSBL** and **MAJORBBS** +* More Liberal String Guessing + * Looks for any matching string in **any** DATA Segment at specified offset + * While less accurate (multiple candidates), prevents misses from incorrect DATA Segment identification +* Updated NuGet Packages +* Added nLog for Console Logging +* Additional Module Definiton Auto-Documentation + * **DOSCALLS** : 10 functions documented of 145 defined + ## v1.4 * Added bytes to Disassembly output * Implemented TPL for processing Relocation Records (Thread-Safe) diff --git a/mbbsdasm_ui.png b/mbbsdasm_ui.png new file mode 100644 index 0000000000000000000000000000000000000000..aec5d27589a7f21e3ce9df2d399343c45aadb3cf GIT binary patch literal 25618 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 literal 0 HcmV?d00001 diff --git a/readme.md b/readme.md index b2ccd3e..5beb390 100644 --- a/readme.md +++ b/readme.md @@ -2,15 +2,25 @@ ![](http://forthebadge.com/images/badges/made-with-c-sharp.svg) ![](http://forthebadge.com/images/badges/60-percent-of-the-time-works-every-time.svg) +![MajorBBS Disassembler (MBBSDASM) Preview](./mbbsdasm_ui.png) + **MBBSDASM** is a Disassembler for 16-bit Segmented Executable File Format ("New Executable", or just NE) files. The Disassembler itself is written in C# using .Net Core. -It was created to assist in my own personal education of The MajorBBS (MBBS) Bulletin Board System, which was one of the first multi-line, multi-user BBS systems available at the time of its hayday. MBBS loaded modules that were an early version of DLL's files built with Borland Turbo C++. +It was created to assist in my own personal education of The MajorBBS (MBBS) Bulletin Board System by GALACTICOMM, which was one of the first multi-line, multi-user commercial BBS systems available at the time of its hayday. MBBS loaded modules that were an early version of DLL's files built with Borland Turbo C++. For more information on The Major BBS and Worldgroup by GALACTICOMM, check out the Wikipedia article [[here](https://en.wikipedia.org/wiki/The_Major_BBS)]. While **MBBSDASM** targets Major BBS/Worldgroup files for analysis, any 16-bit NE EXE/DLL file is supported and should disassemble without issue. I've tested this with both Solitaire and Calculator from Windows 3.1 to verify. +# Text UI + +**MBBSDASM** provides support for a cross-platform Text-Based UI (TUI) thanks to the fantastic Terminal.Gui library! To access the TUI, simply run MBBSDASM with no command line arguments. + # Example Command Line + +**MBBSDASM** supports disassembly of MajorBBS/Worldgroup modules via command line as well. + +An example command line to disassemble a DLL and perform enhanced MajorBBS/Worldgroup Analysis: ``` -i c:\bbsv6\example.dll -o c:\bbsv6\output.txt -strings -analysis ``` @@ -40,7 +50,7 @@ Normal will output the disassembled x86 code segments labeled with SEGMENT:OFFSE * Processing Segment Relocation Table Entries * Resolve External References * String Reference Resolution (best guess) -* Identify and Label Conditional/Unconditional Jumps as well as Call's +* Identify and Label Conditional/Unconditional Jumps as well as Function Calls ```asm 00000C68h:0002.0068h 83C408 add sp, 0x8 00000C6Bh:0002.006Bh 68FF7F push 0x7fff @@ -94,6 +104,7 @@ The Enhanced Analysis mode can be extended through pull requests by adding Modul * This would allow disassembly of the MajorBBS/WG EXE files * Add support for Worldgroup 3.0+ * Requires additional support for disassembly of 32-bit PE format EXE/DLL files + * The best tool for this is probably IDA Freeware, which disassembles PE files with ease # Contribute I'm always looking for updated/new information on several related topics. If you have any first hand knowledge, documentation or files you can send me related to: @@ -111,10 +122,10 @@ The project makes use of [SharpDiasm](https://github.com/spazzarama/SharpDisasm) A big shoutout to the grey beards keeping this archaic software alive and still available 25+ years later, folks I've interacted with related to MBBS/WG over the years (you know who you are), and the people involved with The BBS Documentary [[link](http://www.bbsdocumentary.com/)] -# License +# License / Copyright MBBSDASM is Copyright (c) 2017 Eric Nusbaum and is distributed under the 2-clause "Simplified BSD License". - SharpDisam is Copyright (c) 2015 Justin Stenning and is distributed under the 2-clause "Simplified BSD License". +Terminal.Gui is Copyright (c) 2017 Microsoft Corp and is distributed under the MIT License Portions of the project are ported from Udis86 Copyright (c) 2002-2012, Vivek Thampi https://github.com/vmt/udis86 distributed under the 2-clause "Simplified BSD License".