├── .github └── FUNDING.yml ├── .gitignore ├── LICENSE ├── MBBSDASM.sln ├── MBBSDASM ├── Analysis │ ├── Artifacts │ │ ├── Export.cs │ │ ├── Instruction.cs │ │ ├── ModuleDefinition.cs │ │ ├── ReturnValue.cs │ │ └── TrackedVariable.cs │ ├── Assets │ │ ├── DOSCALLS_def.json │ │ ├── GALGSBL_def.json │ │ └── MajorBBS_def.json │ └── MBBS.cs ├── Artifacts │ ├── Entry.cs │ ├── ImportedName.cs │ ├── MZHeader.cs │ ├── ModuleReference.cs │ ├── NEFile.cs │ ├── NEHeader.cs │ ├── NonResidentName.cs │ ├── RelocationRecord.cs │ ├── ResidentName.cs │ ├── ResourceEntry.cs │ ├── ResourceRecord.cs │ ├── Segment.cs │ └── StringRecord.cs ├── Constants.cs ├── Dasm │ ├── BranchRecord.cs │ ├── Disassembler.cs │ ├── DisassemblyLine.cs │ ├── ExportedFunctionRecord.cs │ └── MnemonicGroupings.cs ├── Enums │ ├── EnumBranchType.cs │ ├── EnumEntryFlags.cs │ ├── EnumRecordsFlag.cs │ └── EnumSegmentFlags.cs ├── Logging │ └── CustomLogger.cs ├── MBBSDASM.csproj ├── Program.cs ├── Renderer │ └── impl │ │ └── StringRenderer.cs └── UI │ ├── IUserInterface.cs │ └── impl │ ├── ConsoleUI.cs │ └── InteractiveUI.cs ├── changelog.md ├── mbbsdasm_ui.png └── readme.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: enusbaum 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # .NET Core 46 | project.lock.json 47 | project.fragment.lock.json 48 | # artifacts/ 49 | **/Properties/launchSettings.json 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # Visual Studio code coverage results 117 | *.coverage 118 | *.coveragexml 119 | 120 | # NCrunch 121 | _NCrunch_* 122 | .*crunch*.local.xml 123 | nCrunchTemp_* 124 | 125 | # MightyMoose 126 | *.mm.* 127 | AutoTest.Net/ 128 | 129 | # Web workbench (sass) 130 | .sass-cache/ 131 | 132 | # Installshield output folder 133 | [Ee]xpress/ 134 | 135 | # DocProject is a documentation generator add-in 136 | DocProject/buildhelp/ 137 | DocProject/Help/*.HxT 138 | DocProject/Help/*.HxC 139 | DocProject/Help/*.hhc 140 | DocProject/Help/*.hhk 141 | DocProject/Help/*.hhp 142 | DocProject/Help/Html2 143 | DocProject/Help/html 144 | 145 | # Click-Once directory 146 | publish/ 147 | 148 | # Publish Web Output 149 | *.[Pp]ublish.xml 150 | *.azurePubxml 151 | # TODO: Comment the next line if you want to checkin your web deploy settings 152 | # but database connection strings (with potential passwords) will be unencrypted 153 | *.pubxml 154 | *.publishproj 155 | 156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 157 | # checkin your Azure Web App publish settings, but sensitive information contained 158 | # in these scripts will be unencrypted 159 | PublishScripts/ 160 | 161 | # NuGet Packages 162 | *.nupkg 163 | # The packages folder can be ignored because of Package Restore 164 | **/packages/* 165 | # except build/, which is used as an MSBuild target. 166 | !**/packages/build/ 167 | # Uncomment if necessary however generally it will be regenerated when needed 168 | #!**/packages/repositories.config 169 | # NuGet v3's project.json files produces more ignorable files 170 | *.nuget.props 171 | *.nuget.targets 172 | 173 | # Microsoft Azure Build Output 174 | csx/ 175 | *.build.csdef 176 | 177 | # Microsoft Azure Emulator 178 | ecf/ 179 | rcf/ 180 | 181 | # Windows Store app package directories and files 182 | AppPackages/ 183 | BundleArtifacts/ 184 | Package.StoreAssociation.xml 185 | _pkginfo.txt 186 | 187 | # Visual Studio cache files 188 | # files ending in .cache can be ignored 189 | *.[Cc]ache 190 | # but keep track of directories ending in .cache 191 | !*.[Cc]ache/ 192 | 193 | # Others 194 | ClientBin/ 195 | ~$* 196 | *~ 197 | *.dbmdl 198 | *.dbproj.schemaview 199 | *.jfm 200 | *.pfx 201 | *.publishsettings 202 | orleans.codegen.cs 203 | 204 | # Since there are multiple workflows, uncomment next line to ignore bower_components 205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 206 | #bower_components/ 207 | 208 | # RIA/Silverlight projects 209 | Generated_Code/ 210 | 211 | # Backup & report files from converting an old project file 212 | # to a newer Visual Studio version. Backup files are not needed, 213 | # because we have git ;-) 214 | _UpgradeReport_Files/ 215 | Backup*/ 216 | UpgradeLog*.XML 217 | UpgradeLog*.htm 218 | 219 | # SQL Server files 220 | *.mdf 221 | *.ldf 222 | *.ndf 223 | 224 | # Business Intelligence projects 225 | *.rdl.data 226 | *.bim.layout 227 | *.bim_*.settings 228 | 229 | # Microsoft Fakes 230 | FakesAssemblies/ 231 | 232 | # GhostDoc plugin setting file 233 | *.GhostDoc.xml 234 | 235 | # Node.js Tools for Visual Studio 236 | .ntvs_analysis.dat 237 | node_modules/ 238 | 239 | # Typescript v1 declaration files 240 | typings/ 241 | 242 | # Visual Studio 6 build log 243 | *.plg 244 | 245 | # Visual Studio 6 workspace options file 246 | *.opt 247 | 248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 249 | *.vbw 250 | 251 | # Visual Studio LightSwitch build output 252 | **/*.HTMLClient/GeneratedArtifacts 253 | **/*.DesktopClient/GeneratedArtifacts 254 | **/*.DesktopClient/ModelManifest.xml 255 | **/*.Server/GeneratedArtifacts 256 | **/*.Server/ModelManifest.xml 257 | _Pvt_Extensions 258 | 259 | # Paket dependency manager 260 | .paket/paket.exe 261 | paket-files/ 262 | 263 | # FAKE - F# Make 264 | .fake/ 265 | 266 | # JetBrains Rider 267 | .idea/ 268 | *.sln.iml 269 | 270 | # CodeRush 271 | .cr/ 272 | 273 | # Python Tools for Visual Studio (PTVS) 274 | __pycache__/ 275 | *.pyc 276 | 277 | # Cake - Uncomment if you are using it 278 | # tools/** 279 | # !tools/packages.config 280 | 281 | # Telerik's JustMock configuration file 282 | *.jmconfig 283 | 284 | # BizTalk build output 285 | *.btp.cs 286 | *.btm.cs 287 | *.odx.cs 288 | *.xsd.cs 289 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2018, Eric P. Nusbaum 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /MBBSDASM.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MBBSDASM", "MBBSDASM\MBBSDASM.csproj", "{AA98FDB5-ACB6-4828-9A7F-C78318CD0E54}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {AA98FDB5-ACB6-4828-9A7F-C78318CD0E54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {AA98FDB5-ACB6-4828-9A7F-C78318CD0E54}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {AA98FDB5-ACB6-4828-9A7F-C78318CD0E54}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {AA98FDB5-ACB6-4828-9A7F-C78318CD0E54}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /MBBSDASM/Analysis/Artifacts/Export.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MBBSDASM.Analysis.Artifacts 4 | { 5 | public class Export 6 | { 7 | public string Name { get; set; } 8 | public ushort Ord { get; set; } 9 | public string Signature { get; set; } 10 | public string SignatureFormat { get; set; } 11 | public List Comments { get; set; } 12 | public List PrecedingInstructions { get; set; } 13 | public List ReturnValues { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /MBBSDASM/Analysis/Artifacts/Instruction.cs: -------------------------------------------------------------------------------- 1 | namespace MBBSDASM.Analysis.Artifacts 2 | { 3 | public class Instruction 4 | { 5 | public short Offset { get; set; } 6 | public string Op { get; set; } 7 | public string Type { get; set; } 8 | public string Name { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /MBBSDASM/Analysis/Artifacts/ModuleDefinition.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MBBSDASM.Analysis.Artifacts 4 | { 5 | public class ModuleDefinition 6 | { 7 | public string Name { get; set; } 8 | public string Comment { get; set; } 9 | public List Exports { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /MBBSDASM/Analysis/Artifacts/ReturnValue.cs: -------------------------------------------------------------------------------- 1 | namespace MBBSDASM.Analysis.Artifacts 2 | { 3 | public class ReturnValue 4 | { 5 | public string Op { get; set; } 6 | public int Offset { get; set; } 7 | public string Name { get; set; } 8 | public string Comment { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /MBBSDASM/Analysis/Artifacts/TrackedVariable.cs: -------------------------------------------------------------------------------- 1 | namespace MBBSDASM.Analysis.Artifacts 2 | { 3 | public class TrackedVariable 4 | { 5 | public ushort Segment { get; set; } 6 | public ulong Offset { get; set; } 7 | public ushort Address { get; set; } 8 | public string Name { get; set; } 9 | public string Comment { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /MBBSDASM/Analysis/Assets/DOSCALLS_def.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "DOSCALLS", 3 | "Comment" : "Export definitions for DOSCALLS.H which is a C++ OS/2 library for DOS APIs", 4 | "Exports": [ 5 | { 6 | "name": "DOSCWAIT", 7 | "ord": 2, 8 | "Signature": "USHORT rc = DosCwait(USHORT ActionCode, USHORT WaitOption, PRESULTCODES ReturnCodes, PPID ProcessIDWord, PID ProcessID);", 9 | "Comments": [ 10 | "Places the current thread in a wait state until an asynchronous child process ends.", 11 | "When the process ends, its process ID and termination code are returned to the caller." 12 | ] 13 | }, 14 | { 15 | "name": "DOSENTERCRITSEC", 16 | "ord": 3, 17 | "Signature": "", 18 | "Comments": [] 19 | }, 20 | { 21 | "name": "DOSEXIT", 22 | "ord": 5, 23 | "Signature": "", 24 | "Comments": [] 25 | }, 26 | { 27 | "name": "DOSEXITCRITSEC", 28 | "ord": 6, 29 | "Signature": "", 30 | "Comments": [] 31 | }, 32 | { 33 | "name": "DOSEXITLIST", 34 | "ord": 7, 35 | "Signature": "", 36 | "Comments": [] 37 | }, 38 | { 39 | "name": "DOSGETINFOSEG", 40 | "ord": 8, 41 | "Signature": "", 42 | "Comments": [] 43 | }, 44 | { 45 | "name": "DOSGETPRTY", 46 | "ord": 9, 47 | "Signature": "", 48 | "Comments": [] 49 | }, 50 | { 51 | "name": "DOSKILLPROCESS", 52 | "ord": 10, 53 | "Signature": "", 54 | "Comments": [] 55 | }, 56 | { 57 | "name": "DOSKILLPROCESS", 58 | "ord": 11, 59 | "Signature": "", 60 | "Comments": [] 61 | }, 62 | { 63 | "name": "DOSPTRACE", 64 | "ord": 12, 65 | "Signature": "", 66 | "Comments": [] 67 | }, 68 | { 69 | "name": "DOSHOLDSIGNAL", 70 | "ord": 13, 71 | "Signature": "", 72 | "Comments": [] 73 | }, 74 | { 75 | "name": "DOSSETSIGHANDLER", 76 | "ord": 14, 77 | "Signature": "", 78 | "Comments": [ 79 | "Registers signal handler" 80 | ] 81 | }, 82 | { 83 | "name": "DOSFLAGPROCESS", 84 | "ord": 15, 85 | "Signature": "", 86 | "Comments": [] 87 | }, 88 | { 89 | "name": "DOSMAKEPIPE", 90 | "ord": 16, 91 | "Signature": "", 92 | "Comments": [] 93 | }, 94 | { 95 | "name": "DOSSEMSETWAIT", 96 | "ord": 20, 97 | "Signature": "", 98 | "Comments": [] 99 | }, 100 | { 101 | "name": "DOSMUXSEMWAIT", 102 | "ord": 22, 103 | "Signature": "", 104 | "Comments": [] 105 | }, 106 | { 107 | "name": "DOSCLOSESEM", 108 | "ord": 23, 109 | "Signature": "", 110 | "Comments": [] 111 | }, 112 | { 113 | "name": "DOSCREATESEM", 114 | "ord": 24, 115 | "Signature": "", 116 | "Comments": [] 117 | }, 118 | { 119 | "name": "DOSOPENSEM", 120 | "ord": 25, 121 | "Signature": "", 122 | "Comments": [] 123 | }, 124 | { 125 | "name": "DOSRESUMETHREAD", 126 | "ord": 26, 127 | "Signature": "", 128 | "Comments": [] 129 | }, 130 | { 131 | "name": "DOSSUSPENDTHREAD", 132 | "ord": 27, 133 | "Signature": "", 134 | "Comments": [] 135 | }, 136 | { 137 | "name": "DOSSETDATETIME", 138 | "ord": 28, 139 | "Signature": "", 140 | "Comments": [] 141 | }, 142 | { 143 | "name": "DOSTIMERASYNC", 144 | "ord": 29, 145 | "Signature": "", 146 | "Comments": [] 147 | }, 148 | { 149 | "name": "DOSTIMERSTART", 150 | "ord": 30, 151 | "Signature": "", 152 | "Comments": [] 153 | }, 154 | { 155 | "name": "DOSTIMERSTOP", 156 | "ord": 31, 157 | "Signature": "", 158 | "Comments": [] 159 | }, 160 | { 161 | "name": "DOSTIMERSTOP", 162 | "ord": 31, 163 | "Signature": "", 164 | "Comments": [] 165 | }, 166 | { 167 | "name": "DOSSLEEP", 168 | "ord": 32, 169 | "Signature": "", 170 | "Comments": [] 171 | }, 172 | { 173 | "name": "DOSGETDATETIME", 174 | "ord": 33, 175 | "Signature": "", 176 | "Comments": [] 177 | }, 178 | { 179 | "name": "DOSALLOCSEG", 180 | "ord": 34, 181 | "Signature": "", 182 | "Comments": [] 183 | }, 184 | { 185 | "name": "DOSALLOCSHRSEG", 186 | "ord": 35, 187 | "Signature": "", 188 | "Comments": [] 189 | }, 190 | { 191 | "name": "DOSGETSHRSEG", 192 | "ord": 36, 193 | "Signature": "", 194 | "Comments": [] 195 | }, 196 | { 197 | "name": "DOSGIVESEG", 198 | "ord": 37, 199 | "Signature": "", 200 | "Comments": [] 201 | }, 202 | { 203 | "name": "DOSREALLOCSEG", 204 | "ord": 38, 205 | "Signature": "", 206 | "Comments": [] 207 | }, 208 | { 209 | "name": "DOSFREESEG", 210 | "ord": 39, 211 | "Signature": "", 212 | "Comments": [] 213 | }, 214 | { 215 | "name": "DOSALLOCHUGE", 216 | "ord": 40, 217 | "Signature": "", 218 | "Comments": [] 219 | }, 220 | { 221 | "name": "DOSGETHUGESHIFT", 222 | "ord": 41, 223 | "Signature": "", 224 | "Comments": [] 225 | }, 226 | { 227 | "name": "DOSREALLOCHUGE", 228 | "ord": 42, 229 | "Signature": "", 230 | "Comments": [] 231 | }, 232 | { 233 | "name": "DOSCREATECSALIAS", 234 | "ord": 43, 235 | "Signature": "", 236 | "Comments": [] 237 | }, 238 | { 239 | "name": "DOSLOADMODULE", 240 | "ord": 44, 241 | "Signature": "", 242 | "Comments": [] 243 | }, 244 | { 245 | "name": "DOSGETPROCADDR", 246 | "ord": 45, 247 | "Signature": "", 248 | "Comments": [] 249 | }, 250 | { 251 | "name": "DOSFREEMODULE", 252 | "ord": 46, 253 | "Signature": "", 254 | "Comments": [] 255 | }, 256 | { 257 | "name": "DOSGETMODHANDLE", 258 | "ord": 47, 259 | "Signature": "", 260 | "Comments": [] 261 | }, 262 | { 263 | "name": "DOSGETMODNAME", 264 | "ord": 48, 265 | "Signature": "", 266 | "Comments": [] 267 | }, 268 | { 269 | "name": "DOSGETMACHINEMODE", 270 | "ord": 49, 271 | "Signature": "", 272 | "Comments": [] 273 | }, 274 | { 275 | "name": "DOSBEEP", 276 | "ord": 50, 277 | "Signature": "", 278 | "Comments": [] 279 | }, 280 | { 281 | "name": "DOSCLIACCESS", 282 | "ord": 51, 283 | "Signature": "", 284 | "Comments": [] 285 | }, 286 | { 287 | "name": "DOSDEVCONFIG", 288 | "ord": 52, 289 | "Signature": "", 290 | "Comments": [] 291 | }, 292 | { 293 | "name": "DOSDEVIOCTL", 294 | "ord": 53, 295 | "Signature": "", 296 | "Comments": [] 297 | }, 298 | { 299 | "name": "DOSBUFRESET", 300 | "ord": 56, 301 | "Signature": "", 302 | "Comments": [] 303 | }, 304 | { 305 | "name": "DOSCHDIR", 306 | "ord": 57, 307 | "Signature": "", 308 | "Comments": [] 309 | }, 310 | { 311 | "name": "DOSCHGFILEPTR", 312 | "ord": 58, 313 | "Signature": "", 314 | "Comments": [] 315 | }, 316 | { 317 | "name": "DOSCLOSE", 318 | "ord": 59, 319 | "Signature": "", 320 | "Comments": [] 321 | }, 322 | { 323 | "name": "DOSDELETE", 324 | "ord": 60, 325 | "Signature": "", 326 | "Comments": [] 327 | }, 328 | { 329 | "name": "DOSDUPHANDLE", 330 | "ord": 61, 331 | "Signature": "", 332 | "Comments": [] 333 | }, 334 | { 335 | "name": "DOSFILELOCKS", 336 | "ord": 62, 337 | "Signature": "", 338 | "Comments": [] 339 | }, 340 | { 341 | "name": "DOSFINDCLOSE", 342 | "ord": 63, 343 | "Signature": "", 344 | "Comments": [] 345 | }, 346 | { 347 | "name": "DOSFINDFIRST", 348 | "ord": 64, 349 | "Signature": "", 350 | "Comments": [] 351 | }, 352 | { 353 | "name": "DOSFINDNEXT", 354 | "ord": 65, 355 | "Signature": "", 356 | "Comments": [] 357 | }, 358 | { 359 | "name": "DOSMKDIR", 360 | "ord": 66, 361 | "Signature": "", 362 | "Comments": [] 363 | }, 364 | { 365 | "name": "DOSMOVE", 366 | "ord": 67, 367 | "Signature": "", 368 | "Comments": [] 369 | }, 370 | { 371 | "name": "DOSNEWSIZE", 372 | "ord": 68, 373 | "Signature": "", 374 | "Comments": [] 375 | }, 376 | { 377 | "name": "DOSPORTACCESS", 378 | "ord": 69, 379 | "Signature": "", 380 | "Comments": [] 381 | }, 382 | { 383 | "name": "DOSOPEN", 384 | "ord": 70, 385 | "Signature": "", 386 | "Comments": [] 387 | }, 388 | { 389 | "name": "DOSQCURDIR", 390 | "ord": 71, 391 | "Signature": "", 392 | "Comments": [] 393 | }, 394 | { 395 | "name": "DOSQCURDISK", 396 | "ord": 72, 397 | "Signature": "", 398 | "Comments": [] 399 | }, 400 | { 401 | "name": "DOSQFHANDSTATE", 402 | "ord": 73, 403 | "Signature": "", 404 | "Comments": [] 405 | }, 406 | { 407 | "name": "DOSQFILEINFO", 408 | "ord": 74, 409 | "Signature": "", 410 | "Comments": [] 411 | }, 412 | { 413 | "name": "DOSQFILEMODE", 414 | "ord": 75, 415 | "Signature": "", 416 | "Comments": [] 417 | }, 418 | { 419 | "name": "DOSQFSINFO", 420 | "ord": 76, 421 | "Signature": "", 422 | "Comments": [] 423 | }, 424 | { 425 | "name": "DOSQHANDTYPE", 426 | "ord": 77, 427 | "Signature": "", 428 | "Comments": [] 429 | }, 430 | { 431 | "name": "DOSQVERIFY", 432 | "ord": 78, 433 | "Signature": "", 434 | "Comments": [] 435 | }, 436 | { 437 | "name": "DOSRMDIR", 438 | "ord": 80, 439 | "Signature": "", 440 | "Comments": [] 441 | }, 442 | { 443 | "name": "DOSSELECTDISK", 444 | "ord": 81, 445 | "Signature": "", 446 | "Comments": [] 447 | }, 448 | { 449 | "name": "DOSSETFHANDSTATE", 450 | "ord": 82, 451 | "Signature": "", 452 | "Comments": [] 453 | }, 454 | { 455 | "name": "DOSSETFILEINFO", 456 | "ord": 83, 457 | "Signature": "", 458 | "Comments": [] 459 | }, 460 | { 461 | "name": "DOSSETFILEMODE", 462 | "ord": 84, 463 | "Signature": "", 464 | "Comments": [] 465 | }, 466 | { 467 | "name": "DOSSETMAXFH", 468 | "ord": 85, 469 | "Signature": "", 470 | "Comments": [] 471 | }, 472 | { 473 | "name": "DOSSETVERIFY", 474 | "ord": 86, 475 | "Signature": "APIRET DosSetVerify (BOOL32 fVerifySetting);", 476 | "Comments": [ 477 | "Sets system read-after-write flag" 478 | ] 479 | }, 480 | { 481 | "name": "DOSSYSTEMSERVICE", 482 | "ord": 88, 483 | "Signature": "", 484 | "Comments": [] 485 | }, 486 | { 487 | "name": "DOSSETVEC", 488 | "ord": 89, 489 | "Signature": "USHORT rc = DosSetVec(USHORT VecNum, PFN Routine, PFN PrevAddress);", 490 | "Comments": [ 491 | "Registers handler for hardware exception", 492 | "The DosSetVec process is analogous to setting an address in the interrupt vector table when running in 8086 mode." 493 | ] 494 | }, 495 | { 496 | "name": "DOSSYSTRACE", 497 | "ord": 90, 498 | "Signature": "", 499 | "Comments": [] 500 | }, 501 | { 502 | "name": "DOSGETENV", 503 | "ord": 91, 504 | "Signature": "", 505 | "Comments": [] 506 | }, 507 | { 508 | "name": "DOSGETVERSION", 509 | "ord": 92, 510 | "Signature": "", 511 | "Comments": [] 512 | }, 513 | { 514 | "name": "DOSGETPID", 515 | "ord": 94, 516 | "Signature": "", 517 | "Comments": [] 518 | }, 519 | { 520 | "name": "DOSOPEN2", 521 | "ord": 95, 522 | "Signature": "", 523 | "Comments": [] 524 | }, 525 | { 526 | "name": "DOSLIBINIT", 527 | "ord": 96, 528 | "Signature": "", 529 | "Comments": [] 530 | }, 531 | { 532 | "name": "DOSSETFSINFO", 533 | "ord": 97, 534 | "Signature": "", 535 | "Comments": [] 536 | }, 537 | { 538 | "name": "DOSQPATHINFO", 539 | "ord": 98, 540 | "Signature": "", 541 | "Comments": [] 542 | }, 543 | { 544 | "name": "DOSDEVIOCTL2", 545 | "ord": 99, 546 | "Signature": "", 547 | "Comments": [] 548 | }, 549 | { 550 | "name": "DOSSETPATHINFO", 551 | "ord": 104, 552 | "Signature": "", 553 | "Comments": [] 554 | }, 555 | { 556 | "name": "DOSERROR", 557 | "ord": 120, 558 | "Signature": "", 559 | "Comments": [] 560 | }, 561 | { 562 | "name": "DOSGETSEG", 563 | "ord": 121, 564 | "Signature": "", 565 | "Comments": [] 566 | }, 567 | { 568 | "name": "DOSLOCKSEG", 569 | "ord": 122, 570 | "Signature": "", 571 | "Comments": [] 572 | }, 573 | { 574 | "name": "DOSUNLOCKSEG", 575 | "ord": 123, 576 | "Signature": "", 577 | "Comments": [] 578 | }, 579 | { 580 | "name": "DOSSIZESEG", 581 | "ord": 126, 582 | "Signature": "", 583 | "Comments": [] 584 | }, 585 | { 586 | "name": "DOSMEMAVAIL", 587 | "ord": 127, 588 | "Signature": "", 589 | "Comments": [] 590 | }, 591 | { 592 | "name": "DOSPHYSICALDISK", 593 | "ord": 129, 594 | "Signature": "", 595 | "Comments": [] 596 | }, 597 | { 598 | "name": "DOSGETCP", 599 | "ord": 130, 600 | "Signature": "", 601 | "Comments": [] 602 | }, 603 | { 604 | "name": "DOSSENDSIGNAL", 605 | "ord": 134, 606 | "Signature": "", 607 | "Comments": [] 608 | }, 609 | { 610 | "name": "DOSHUGESHIFT", 611 | "ord": 135, 612 | "Signature": "", 613 | "Comments": [] 614 | }, 615 | { 616 | "name": "DOSHUGEINCR", 617 | "ord": 136, 618 | "Signature": "", 619 | "Comments": [] 620 | }, 621 | { 622 | "name": "DOSREAD", 623 | "ord": 137, 624 | "Signature": "", 625 | "Comments": [] 626 | }, 627 | { 628 | "name": "DOSWRITE", 629 | "ord": 138, 630 | "Signature": "", 631 | "Comments": [] 632 | }, 633 | { 634 | "name": "DOSERRCLASS", 635 | "ord": 139, 636 | "Signature": "", 637 | "Comments": [] 638 | }, 639 | { 640 | "name": "DOSSEMREQUEST", 641 | "ord": 140, 642 | "Signature": "", 643 | "Comments": [] 644 | }, 645 | { 646 | "name": "DOSSEMCLEAR", 647 | "ord": 141, 648 | "Signature": "", 649 | "Comments": [] 650 | }, 651 | { 652 | "name": "DOSSEMWAIT", 653 | "ord": 142, 654 | "Signature": "", 655 | "Comments": [] 656 | }, 657 | { 658 | "name": "DOSSEMSET", 659 | "ord": 143, 660 | "Signature": "", 661 | "Comments": [] 662 | }, 663 | { 664 | "name": "DOSEXECPGM", 665 | "ord": 144, 666 | "Signature": "", 667 | "Comments": [] 668 | }, 669 | { 670 | "name": "DOSCREATETHREAD", 671 | "ord": 145, 672 | "Signature": "", 673 | "Comments": [] 674 | }, 675 | { 676 | "name": "DOSSUBSET", 677 | "ord": 146, 678 | "Signature": "", 679 | "Comments": [] 680 | }, 681 | { 682 | "name": "DOSSUBALLOC", 683 | "ord": 147, 684 | "Signature": "", 685 | "Comments": [] 686 | }, 687 | { 688 | "name": "DOSSUBFREE", 689 | "ord": 148, 690 | "Signature": "", 691 | "Comments": [] 692 | }, 693 | { 694 | "name": "DOSREADASYNC", 695 | "ord": 149, 696 | "Signature": "", 697 | "Comments": [] 698 | }, 699 | { 700 | "name": "DOSWRITEASYNC", 701 | "ord": 150, 702 | "Signature": "", 703 | "Comments": [] 704 | }, 705 | { 706 | "name": "DOSSEARCHPATH", 707 | "ord": 151, 708 | "Signature": "", 709 | "Comments": [] 710 | }, 711 | { 712 | "name": "DOSSCANENV", 713 | "ord": 152, 714 | "Signature": "", 715 | "Comments": [] 716 | }, 717 | { 718 | "name": "DOSSETCP", 719 | "ord": 153, 720 | "Signature": "", 721 | "Comments": [] 722 | }, 723 | { 724 | "name": "DOSGETRESOURCE", 725 | "ord": 155, 726 | "Signature": "", 727 | "Comments": [] 728 | }, 729 | { 730 | "name": "DOSGETPPID", 731 | "ord": 156, 732 | "Signature": "", 733 | "Comments": [] 734 | }, 735 | { 736 | "name": "DOSCALLBACK", 737 | "ord": 157, 738 | "Signature": "", 739 | "Comments": [] 740 | }, 741 | { 742 | "name": "DOSR2STACKREALLOC", 743 | "ord": 160, 744 | "Signature": "", 745 | "Comments": [] 746 | }, 747 | { 748 | "name": "DOSFSRAMSEMREQUEST", 749 | "ord": 161, 750 | "Signature": "", 751 | "Comments": [] 752 | }, 753 | { 754 | "name": "DOSFSRAMSEMCLEAR", 755 | "ord": 162, 756 | "Signature": "", 757 | "Comments": [] 758 | }, 759 | { 760 | "name": "DOSQAPPTYPE", 761 | "ord": 163, 762 | "Signature": "", 763 | "Comments": [] 764 | }, 765 | { 766 | "name": "DOSSETPROCCP", 767 | "ord": 164, 768 | "Signature": "", 769 | "Comments": [] 770 | }, 771 | { 772 | "name": "DOSDYNAMICTRACE", 773 | "ord": 165, 774 | "Signature": "", 775 | "Comments": [] 776 | }, 777 | { 778 | "name": "DOSQSYSINFO", 779 | "ord": 166, 780 | "Signature": "", 781 | "Comments": [] 782 | }, 783 | { 784 | "name": "DOSFSATTACH", 785 | "ord": 181, 786 | "Signature": "", 787 | "Comments": [] 788 | }, 789 | { 790 | "name": "DOSQFSATTACH", 791 | "ord": 182, 792 | "Signature": "", 793 | "Comments": [] 794 | }, 795 | { 796 | "name": "DOSFSCTL", 797 | "ord": 183, 798 | "Signature": "", 799 | "Comments": [] 800 | }, 801 | { 802 | "name": "DOSFINDFIRST2", 803 | "ord": 184, 804 | "Signature": "", 805 | "Comments": [] 806 | }, 807 | { 808 | "name": "DOSMKDIR2", 809 | "ord": 185, 810 | "Signature": "", 811 | "Comments": [] 812 | }, 813 | { 814 | "name": "DOSFILEIO", 815 | "ord": 186, 816 | "Signature": "", 817 | "Comments": [] 818 | }, 819 | { 820 | "name": "DOSFINDNOTIFYCLOSE", 821 | "ord": 187, 822 | "Signature": "", 823 | "Comments": [] 824 | }, 825 | { 826 | "name": "DOSFINDNOTIFYFIRST", 827 | "ord": 188, 828 | "Signature": "", 829 | "Comments": [] 830 | }, 831 | { 832 | "name": "DOSFINDNOTIFYNEXT", 833 | "ord": 189, 834 | "Signature": "", 835 | "Comments": [] 836 | }, 837 | { 838 | "name": "DOSEDITNAME", 839 | "ord": 191, 840 | "Signature": "", 841 | "Comments": [] 842 | }, 843 | { 844 | "name": "DOSLOGREGISTER", 845 | "ord": 195, 846 | "Signature": "", 847 | "Comments": [] 848 | }, 849 | { 850 | "name": "DOSLOGREAD", 851 | "ord": 196, 852 | "Signature": "", 853 | "Comments": [] 854 | }, 855 | { 856 | "name": "DOSCOPY", 857 | "ord": 201, 858 | "Signature": "", 859 | "Comments": [] 860 | }, 861 | { 862 | "name": "DOSFORCEDELETE", 863 | "ord": 203, 864 | "Signature": "", 865 | "Comments": [] 866 | }, 867 | { 868 | "name": "DOSENUMATTRIBUTE", 869 | "ord": 204, 870 | "Signature": "", 871 | "Comments": [] 872 | }, 873 | { 874 | "name": "DOSSHUTDOWN", 875 | "ord": 206, 876 | "Signature": "", 877 | "Comments": [] 878 | }, 879 | { 880 | "name": "DOSGETRESOURCE2", 881 | "ord": 207, 882 | "Signature": "", 883 | "Comments": [] 884 | }, 885 | { 886 | "name": "DOSFREERESOURCE", 887 | "ord": 208, 888 | "Signature": "", 889 | "Comments": [] 890 | } 891 | ] 892 | } -------------------------------------------------------------------------------- /MBBSDASM/Analysis/Assets/GALGSBL_def.json: -------------------------------------------------------------------------------- 1 | { 2 | "Name": "GALGSBL", 3 | "Comment": "Export definitions for the Galacticomm Software Breakout Library (GSBL)", 4 | "Exports": [ 5 | { 6 | "name": "_BTUBSE", 7 | "ord": 1, 8 | "Signature": "int btubse(int chan,char bschar);", 9 | "Comments": [ 10 | "Set backspace-echo character" 11 | ] 12 | }, 13 | { 14 | "name": "_BTUBSZ", 15 | "ord": 2, 16 | "Signature": "int btubsz(int chan,int isiz,int osiz);", 17 | "Comments": [ 18 | "Respecify input and output buffer sizes" 19 | ] 20 | }, 21 | { 22 | "name": "_BTUCHE", 23 | "ord": 3, 24 | "Signature": "int err=btuche(int chan, int onoff);", 25 | "Comments": [ 26 | "Enables calling of btuchi() when echo buffer becomes empty", 27 | "onoff 1 == enables calls, 0 == disables" 28 | ] 29 | }, 30 | { 31 | "name": "_BTUCHI", 32 | "ord": 4, 33 | "Signature": "int err=btuchi(int chan, char (*rouadr)());", 34 | "Comments": [ 35 | "Sets Input Character Interceptor" 36 | ] 37 | }, 38 | { 39 | "name": "_BTUCLC", 40 | "ord": 5, 41 | "Signature": "int btuclc(int chan);", 42 | "Comments": [ 43 | "Clear command output buffer" 44 | ] 45 | }, 46 | { 47 | "name": "_BTUCLI", 48 | "ord": 6, 49 | "Signature": "int btucli(int chan);", 50 | "Comments": [ 51 | "Cleat data input buffer", 52 | "-10 == Channel not defined, -11 == channel number is out of range, 0 == all is well" 53 | ] 54 | }, 55 | { 56 | "name": "_BTUCLO", 57 | "ord": 7, 58 | "Signature": "int btuclo(int chan);", 59 | "Comments": [ 60 | "Clear data output buffer", 61 | "-10 == Channel not defined, -11 == channel number is out of range, 0 == all is well" 62 | ] 63 | }, 64 | { 65 | "name": "_BTUCLS", 66 | "ord": 8, 67 | "Signature": "int btucls(int chan);", 68 | "Comments": [ 69 | "Clear status input buffer", 70 | "-10 == Channel not defined, -11 == channel number is out of range, 0 == all is well" 71 | ] 72 | }, 73 | { 74 | "name": "_BTUCMD", 75 | "ord": 9, 76 | "Signature": "int btucmd(int chan,char *cmdstg);", 77 | "Comments": [ 78 | "Command channel", 79 | "This routine controls functions of the UART and (if used) the modem on a channel" 80 | ] 81 | }, 82 | { 83 | "name": "_BTUDEF", 84 | "ord": 10, 85 | "Signature": "int btudef(int schan,int sport,int n);", 86 | "Comments": [ 87 | "Define Channels" 88 | ] 89 | }, 90 | { 91 | "name": "_BTECH", 92 | "ord": 11, 93 | "Signature": "int btuech(int chan, int mode);", 94 | "Comments": [ 95 | "Set Echo on/off", 96 | "0 == disable echo, 1 == enable echo (echo-plex on X.25 channels), 2 == enable echo (GSBL echo on X.25 channels)" 97 | ] 98 | }, 99 | { 100 | "name": "_BTUEND", 101 | "ord": 12, 102 | "Signature": "void btuend(void);", 103 | "Comments": [ 104 | "Shut down the Software Breakthrough", 105 | "Prepare the PC for return to DOS. This routine must be called as part of your exit cleanup procedure." 106 | ] 107 | }, 108 | { 109 | "name": "_BTUERP", 110 | "ord": 13, 111 | "Signature": "int btuerp(int chan,int onoff);", 112 | "Comments": [ 113 | "Pass/Block input bytes with errors", 114 | "1 == accept characters with PE/FE/OE errors, setting the high order bit of each (default), 0 == ignore characters with PE/EE/OE errors" 115 | ] 116 | }, 117 | { 118 | "name": "_BTUFFO", 119 | "ord": 14, 120 | "Signature": "int btuffo(int chan,int onoff);", 121 | "Comments": [ 122 | "Enable receiver FIFO on 16550 UART", 123 | "1 == enable 16-byte FIFOs, 0 == disable (for exact 16450 compatibility)" 124 | ] 125 | }, 126 | { 127 | "name": "_BTUHCR", 128 | "ord": 15, 129 | "Signature": "int btuhcr(int chan,char hardcr);", 130 | "Comments": [ 131 | "Set the hard-CR character (for output wordwrap)" 132 | ] 133 | }, 134 | { 135 | "name": "_BTUHDR", 136 | "ord": 16, 137 | "Signature": "int btuhdr(int sapchn,int bufsiz,void *buffer);", 138 | "Comments": [ 139 | "Capture information on X.25 or LAN channel" 140 | ] 141 | }, 142 | { 143 | "name": "_BTUHPK", 144 | "ord": 17, 145 | "Signature": "int btuhpk(int chan,int far (*hpkrou)(int chan,char c));", 146 | "Comments": [ 147 | "Handle keystrokes during screen-pause mode" 148 | ] 149 | }, 150 | { 151 | "name": "_BTUHWH", 152 | "ord": 18, 153 | "Signature": "int btuhwh(int chan,int inpcut);", 154 | "Comments": [ 155 | "Enable hardware handshaking using RTS/CTS" 156 | ] 157 | }, 158 | { 159 | "name": "_BTUIBW", 160 | "ord": 19, 161 | "Signature": "int btuibw(int chan);", 162 | "Comments": [ 163 | "Input Bytes Waiting", 164 | "Report the number of bytes received and waiting in the input buffer" 165 | ] 166 | }, 167 | { 168 | "name": "_BTUICT", 169 | "ord": 20, 170 | "Signature": "int btuict(int chan,char *rdbptr);", 171 | "Comments": [ 172 | "Input from a channel - by byte count prearranged with btutrg()" 173 | ] 174 | }, 175 | { 176 | "name": "_BTUINJ", 177 | "ord": 21, 178 | "Signature": "int btuinj(int chan,int status);", 179 | "Comments": [ 180 | "Inject a status code into a channel" 181 | ] 182 | }, 183 | { 184 | "name": "_BTUINP", 185 | "ord": 22, 186 | "Signature": "void chiinp(int chan,char c);", 187 | "Comments": [ 188 | "Input from a channel (ASCIIZ string)" 189 | ] 190 | }, 191 | { 192 | "name": "_BTUIRP", 193 | "ord": 23, 194 | "Singnature": "int btuirp(int comno);", 195 | "Comments": [ 196 | "Define alternate GSBL timing source using COM1/2/3/4" 197 | ] 198 | }, 199 | { 200 | "name": "_BTUITZ", 201 | "ord": 24, 202 | "Signature": "int btuitz(void *region);", 203 | "Comments": [ 204 | "Initialize the Software Breakthrough", 205 | "This routine initializes the Software Breakthrough package" 206 | ] 207 | }, 208 | { 209 | "name": "_BTULFD", 210 | "ord": 25, 211 | "Signature": "int btulfd(int chan,char lfchar);", 212 | "Comments": [ 213 | "Set linefeed character (what follows every carriage return)" 214 | ] 215 | }, 216 | { 217 | "name": "_BTULOK", 218 | "ord": 26, 219 | "Signature": "int btulok(int chan,int onoff);", 220 | "Comments": [ 221 | "Set input lockout on/off" 222 | ] 223 | }, 224 | { 225 | "name": "_BTULSZ", 226 | "ord": 27, 227 | "Signature": "long btulsz(int nchan,int isiz,int osiz);", 228 | "Comments": [ 229 | "Size of dynamic memory needed (long version, used when more than 64K bytes are needed)" 230 | ] 231 | }, 232 | { 233 | "name": "_BTUMDS", 234 | "ord": 28, 235 | "Signature": "int btumds(void);", 236 | "Comments": [ 237 | "Get next displayed character from the monitored channel (as specified by btumon())" 238 | ] 239 | }, 240 | { 241 | "name": "_BTUMDS2", 242 | "ord": 29, 243 | "Signature": "int btumds2(void);", 244 | "Comments": [ 245 | "Get next displayed character from the monitored channel (as specified by btumon2()" 246 | ] 247 | }, 248 | { 249 | "name": "_BTUMIL", 250 | "ord": 30, 251 | "Signature": "int err=btumil(int chan, int maxinl);", 252 | "Comments": [ 253 | "Sets maximum input line length, sets word wrap on/off", 254 | "maxinl > 0 word wrap disabled and maxinl is max input line length", 255 | "maxinl == 0 word wrap is disabled and there is no limit to length of input", 256 | "maxinl < 0 input word wrap is enabled and abs(maxinl) is the max input line length" 257 | ] 258 | }, 259 | { 260 | "name": "_BTUMKS", 261 | "ord": 31, 262 | "Signature": "void btumks(char kyschr);", 263 | "Comments": [ 264 | "Simulate a keystroke on the monitored channel (as specified by btumon())" 265 | ] 266 | }, 267 | { 268 | "name": "_BTMKS2", 269 | "ord": 32, 270 | "Signature": "void btumks2(char kyschr);", 271 | "Comments": [ 272 | "Simulate a keystroke on the monitored channel (as specified by btomon2())" 273 | ] 274 | }, 275 | { 276 | "name": "_BTUMON", 277 | "ord": 33, 278 | "Signature": "int btumon(int chan);", 279 | "Comments": [ 280 | "Start/Stop monitoring a channel" 281 | ] 282 | }, 283 | { 284 | "name": "_BTUMON2", 285 | "ord": 34, 286 | "Signature": "int btumon2(int chan);", 287 | "Comments": [ 288 | "Start/Stop monitoring a channel", 289 | "This function is a clone of btumon(), for emulating a second channel" 290 | ] 291 | }, 292 | { 293 | "name": "_BTUMXS", 294 | "ord": 35, 295 | "Signature": "int btumxs(unsigned bdrate);", 296 | "Comments": [ 297 | "Set maximum data speed" 298 | ] 299 | }, 300 | { 301 | "name": "_BTUOBA", 302 | "ord": 36, 303 | "Signature": "int btuoba(int chan);", 304 | "Comments": [ 305 | "Output Bytes Available", 306 | "Report the amount of space (number of bytes) available in the output buffer" 307 | ] 308 | }, 309 | { 310 | "name": "_BTUOES", 311 | "ord": 37, 312 | "Signature": "int btuoes(int chan,int onoff);", 313 | "Comments": [ 314 | "Enable/Disable Output-Empty status codes" 315 | ] 316 | }, 317 | { 318 | "name": "_BTUOLK", 319 | "ord": 38, 320 | "Signature": "int btuolk(int chan, int onoff);", 321 | "Comments": [ 322 | "Set output pausing on/off" 323 | ] 324 | }, 325 | { 326 | "name": "_BTUPBC", 327 | "ord": 39, 328 | "Signature": "int err=btupbc(int chan, char pausch);", 329 | "Comments": [ 330 | "Set screen-pause character", 331 | "Pauses the screen when in the output stream" 332 | ] 333 | }, 334 | { 335 | "name": "_BTUPMT", 336 | "ord": 40, 337 | "Signature": "int btupmt(int chan, char pmchar);", 338 | "SignatureFormat": "int btupmt(??,'{0}');", 339 | "PrecedingInstructions": [ 340 | { 341 | "Offset": -4, 342 | "Op": "PUSH", 343 | "Type": "char", 344 | "Name": "Prompt Character" 345 | } 346 | ], 347 | "Comments": [ 348 | "Set prompt character" 349 | ] 350 | }, 351 | { 352 | "name": "_BTURST", 353 | "ord": 41, 354 | "Signature": "int bturst(int chan);", 355 | "Comments": [ 356 | "Reset a channel" 357 | ] 358 | }, 359 | { 360 | "name": "_BTURTI", 361 | "ord": 42, 362 | "Signature": "int bturti(int n,void (*rtirou)(void));", 363 | "Comments": [ 364 | "Define routine to be called in real-time" 365 | ] 366 | }, 367 | { 368 | "name": "_BTUSCN", 369 | "ord": 43, 370 | "Signature": "int btuscn(void);", 371 | "Comments": [ 372 | "Scan for channels in need of service (those with nonzero status)" 373 | ] 374 | }, 375 | { 376 | "name": "_BTUSCR", 377 | "ord": 44, 378 | "Signature": "int btuscr(int chan,char softcr);", 379 | "Comments": [ 380 | "Set the soft-CR character (for output wordwrap)" 381 | ] 382 | }, 383 | { 384 | "name": "_BTUSDF", 385 | "ord": 45, 386 | "Signature": "int btusdf(int schan,int nchan,int chtype,...);", 387 | "Comments": [ 388 | "Super-define channel groups" 389 | ] 390 | }, 391 | { 392 | "name": "_BTUSET", 393 | "ord": 46, 394 | "Signature": "long btuset(int chan,int stid,long newval);", 395 | "Comments": [ 396 | "Set and report channel statistics" 397 | ] 398 | }, 399 | { 400 | "name": "_BTUSIZ", 401 | "ord": 47, 402 | "Signature": "unsigned btusiz(int nchan,int isiz,int osiz);", 403 | "Comments": [ 404 | "Size of dynamic memory needed (only if < 64K)" 405 | ] 406 | }, 407 | { 408 | "name": "_BTUSTS", 409 | "ord": 48, 410 | "Signature": "int btusts(int chan);", 411 | "Comments": [ 412 | "Status of a channel" 413 | ] 414 | }, 415 | { 416 | "name": "_BTUTRG", 417 | "ord": 49, 418 | "Signature": "int btutrg(int chan,int nbyt);", 419 | "Comments": [ 420 | "Set the input byte trigger quantity (used in conjunction with btuict())" 421 | ] 422 | }, 423 | { 424 | "name": "_BTUTRM", 425 | "ord": 50, 426 | "Signature": "int btutrm(int chan,char crchar);", 427 | "Comments": [ 428 | "Set input line terminator character" 429 | ] 430 | }, 431 | { 432 | "name": "_BTUTRS", 433 | "ord": 51, 434 | "Signature": "int btutrs(int chan,int onoff);", 435 | "Comments": [ 436 | "Generate status 6 when output aborted by user?" 437 | ] 438 | }, 439 | { 440 | "name": "_BTUTRU", 441 | "ord": 52, 442 | "Signature": "int btutru(int chan,char trunch);", 443 | "Comments": [ 444 | "Sets output-abort character", 445 | "Truncates current output block" 446 | ] 447 | }, 448 | { 449 | "name": "_BTUTSW", 450 | "ord": 53, 451 | "Signature": "int btutsw(int chan,int width);", 452 | "Comments": [ 453 | "Set terminal screen width, and select output word wrap" 454 | ] 455 | }, 456 | { 457 | "name": "_BTUTSW", 458 | "ord": 53, 459 | "Signature": "int err=btutsw(int chan, int width);", 460 | "Comments": [ 461 | "Sets terminal screen width and select output word wrap" 462 | ] 463 | }, 464 | { 465 | "name": "_BTUUDF", 466 | "ord": 54, 467 | "Signature": "int btuudf(int schan,int n);", 468 | "Comments": [ 469 | "Un-Define channels", 470 | "This command undoes the effects of btudef()" 471 | ] 472 | }, 473 | { 474 | "name": "_BTUX29", 475 | "ord": 55, 476 | "Signature": "int btux29(int chan,int nbyt,char *data);", 477 | "Comments": [] 478 | }, 479 | { 480 | "name": "_BTUXCT", 481 | "ord": 56, 482 | "Signature": "int btuxct(int chan,int nbyt,char *datstg);", 483 | "Comments": [ 484 | "Transmit to channel (by byte count)" 485 | ] 486 | }, 487 | { 488 | "name": "_BTUXLT", 489 | "ord": 57, 490 | "Signature": "void btuxlt(char oldchr,char newchr);", 491 | "Comments": [ 492 | "Set input translation table" 493 | ] 494 | }, 495 | { 496 | "name": "_BTUXMN", 497 | "ord": 58, 498 | "Signature": "int btuxmn(int chan,char *datstg);", 499 | "Comments": [ 500 | "Transmit ASCII string that btuclo() will not be able to clear", 501 | "btubsz() will clear, however" 502 | ] 503 | }, 504 | { 505 | "name": "_BTUXMT", 506 | "ord": 59, 507 | "Signature": "int btuxmt(int chan,char *datstg);", 508 | "Comments": [ 509 | "Transmit to channel (ASCIIZ string)" 510 | ] 511 | }, 512 | { 513 | "name": "_BTUXNF", 514 | "ord": 60, 515 | "Signature": "int btuxnf(int chan,int xon,int xoff,...);", 516 | "Comments": [ 517 | "Set XON/XOFF characters, select page mode" 518 | ] 519 | }, 520 | { 521 | "name": "_CHIINJ", 522 | "ord": 61, 523 | "Signature": "void chiinj(int chan,int status);", 524 | "Comments": [ 525 | "Status Inject Utility" 526 | ] 527 | }, 528 | { 529 | "name": "_CHIINP", 530 | "ord": 62, 531 | "Signature": "void chiinp(int chan,char c);", 532 | "Comments": [ 533 | "Character Input Utility" 534 | ] 535 | }, 536 | { 537 | "name": "_CHIOUS", 538 | "ord": 63, 539 | "Signature": "void chious(int chan,char *stg);", 540 | "Comments": [ 541 | "String Output (via Echo Buffer)" 542 | ] 543 | }, 544 | { 545 | "name": "_CHIOUT", 546 | "ord": 64, 547 | "Signature": "void chiout(int chan,char c);", 548 | "Comments": [ 549 | "Character Output (via Echo Buffer)" 550 | ] 551 | }, 552 | { 553 | "name": "_TICKER", 554 | "ord": 65, 555 | "Signature": "volatile unsigned ticker;", 556 | "Comments": [ 557 | "Increments once per second" 558 | ] 559 | }, 560 | { 561 | "name": "_BTRHRT", 562 | "ord": 66, 563 | "Signature": "unsigned long btuhrt;", 564 | "Comments": [ 565 | "32-bit integer increments at approximately 65536hz" 566 | ] 567 | }, 568 | { 569 | "name": "_BTULAN", 570 | "ord": 67, 571 | "Signature": "int btulan;;", 572 | "Comments": [ 573 | "LAN Capability Status Flags", 574 | "0x0001 IPX Direct circuits supported", 575 | "0x0002 IPX Virtual circuits supported", 576 | "0x0004 SPX circuits supported", 577 | "0x0100 IPX driver is loaded", 578 | "0x0200 SPX is loaded" 579 | ] 580 | }, 581 | { 582 | "name": "_BTUSRS", 583 | "ord": 68, 584 | "Signature": "int btusrs;", 585 | "Comments": [ 586 | "The number of users licensed for this copy of GSBL" 587 | ] 588 | }, 589 | { 590 | "name": "_BTUX25", 591 | "ord": 69, 592 | "Signature": "int btux25;", 593 | "Comments": [ 594 | "Whether or not GSBL supports X.25" 595 | ] 596 | }, 597 | { 598 | "name": "_BTUBRT", 599 | "ord": 70, 600 | "Signature": "int btubrt(int chan,unsigned bdrate);", 601 | "Comments": [ 602 | "Set channels baud rate" 603 | ] 604 | }, 605 | { 606 | "name": "_SUSLCK", 607 | "ord": 71, 608 | "Comments": [] 609 | }, 610 | { 611 | "name": "_BTURNO", 612 | "ord": 72, 613 | "Signature": "char bturno[];", 614 | "Comments": [ 615 | "8 digit + NULL GSBL Registration Number" 616 | ] 617 | }, 618 | { 619 | "name": "_BTUDTR", 620 | "ord": 73, 621 | "Signature": "int btudtr;", 622 | "Comments": [ 623 | "Set 1 to disable DTR-dropping during reset" 624 | ] 625 | }, 626 | { 627 | "name": "_X25UDT", 628 | "ord": 74, 629 | "Signature": "int x25udt;", 630 | "Comments": [ 631 | "Set to 1 to capture User Data Field" 632 | ] 633 | }, 634 | { 635 | "name": "_X25HEAP", 636 | "ord": 75, 637 | "Comments": [] 638 | }, 639 | { 640 | "name": "_X25IGN", 641 | "ord": 76, 642 | "Signature": "int x25ign;", 643 | "Comments": [ 644 | "Count of received packets ignored by GSBL" 645 | ] 646 | }, 647 | { 648 | "name": "_LANREV", 649 | "ord": 77, 650 | "Signature": "char lanrev[2];", 651 | "Comments": [ 652 | "SPX revision number" 653 | ] 654 | }, 655 | { 656 | "name": "_LANSOP", 657 | "ord": 78, 658 | "Signature": "int lansop;", 659 | "Comments": [ 660 | "Socket actually opened by btusdf() call" 661 | ] 662 | }, 663 | { 664 | "name": "_LANSCA", 665 | "ord": 79, 666 | "Signature": "int lansca;", 667 | "Comments": [ 668 | "SPX connections available" 669 | ] 670 | }, 671 | { 672 | "name": "_PLORTI", 673 | "ord": 80, 674 | "Comments": [] 675 | }, 676 | { 677 | "name": "_BTUVER", 678 | "ord": 81, 679 | "Signature": "char btuver[];", 680 | "Comments": [ 681 | "Software revision for the GSBL" 682 | ] 683 | }, 684 | { 685 | "name": "_ICTACT", 686 | "ord": 82, 687 | "Signature": "int ictact;", 688 | "Comments": [ 689 | "After btuict() or btuica(): # of bytes available" 690 | ] 691 | }, 692 | { 693 | "name": "_BTUEBA", 694 | "ord": 83, 695 | "Signature": "int btueba(int chan);", 696 | "Comments": [ 697 | "Echo buffer space available, in bytes" 698 | ] 699 | }, 700 | { 701 | "name": "_BTUHIT", 702 | "ord": 84, 703 | "Signature": "int btuhit(int comint);", 704 | "Comments": [ 705 | "Hook into a COM port interrupt and use it to invoke channel servicing" 706 | ] 707 | }, 708 | { 709 | "name": "_BTUITM", 710 | "ord": 85, 711 | "Signature": "int btuitm(void *region);", 712 | "Comments": [ 713 | "Initialize the Software Breakthrough for use in a multi-tasking environment" 714 | ] 715 | }, 716 | { 717 | "name": "_BTUCPC", 718 | "ord": 86, 719 | "Signature": "int btucpc(int chan,char clrpch);", 720 | "Comments": [ 721 | "Set the clear pause-counter character (puts off screen pauses when in output stream)" 722 | ] 723 | }, 724 | { 725 | "name": "_BTUICA", 726 | "ord": 87, 727 | "Signature": "int btuica(int chan,char *rdbptr,int max);", 728 | "Comments": [ 729 | "Input from a channel - reading in whatever bytes are available, up to a limit" 730 | ] 731 | }, 732 | { 733 | "name": "_BTUPFL", 734 | "ord": 88, 735 | "Signature": "int btupfl(void (*pflrou)(int type,unsigned off,unsigned sel));", 736 | "Comments": [] 737 | }, 738 | { 739 | "name": "_PPFLREAL", 740 | "ord": 89, 741 | "Signature": "extern long far *ppflreal;", 742 | "Comments": [] 743 | }, 744 | { 745 | "name": "_PFLGSLB", 746 | "ord": 90, 747 | "Signature": "extern long pflgsbl;", 748 | "Comments": [ 749 | "Possible type of PFGSBL in Module Definition" 750 | ] 751 | }, 752 | { 753 | "name": "_PFLRNG3", 754 | "ord": 91, 755 | "Signature": "extern long pflrng3;", 756 | "Comments": [] 757 | }, 758 | { 759 | "name": "_PFLPLAP", 760 | "ord": 92, 761 | "Signature": "extern long pflplap;", 762 | "Comments": [] 763 | }, 764 | { 765 | "name": "_PFN", 766 | "ord": 93, 767 | "Signature": "extern char pfn;", 768 | "Comments": [] 769 | }, 770 | { 771 | "name": "_BTUUSP", 772 | "ord": 94, 773 | "Signature": "int btuusp(int chan,int onoff);", 774 | "Comments": [ 775 | "Special UART polling method" 776 | ] 777 | }, 778 | { 779 | "name": "_BTUREP", 780 | "ord": 95, 781 | "Signature": "long bturep(int chan,int stid);", 782 | "Comments": [ 783 | "Report Channel Statistics" 784 | ] 785 | }, 786 | { 787 | "name": "_PFLMYS3", 788 | "ord": 96, 789 | "Signature": "extern long pflmys3;", 790 | "Comments": [] 791 | }, 792 | { 793 | "name": "_BTUCDI", 794 | "ord": 97, 795 | "Signature": "struct datstm far *btucdi(int chan,struct datstm far *outsnk);", 796 | "Comments": [] 797 | }, 798 | { 799 | "name": "_BTUOPL", 800 | "ord": 98, 801 | "Signature": "int btuopl(int chan);", 802 | "Comments": [] 803 | }, 804 | { 805 | "name": "_BTUPCC", 806 | "ord": 99, 807 | "Signature": "int btupcc(int chan,int mode);", 808 | "Comments": [] 809 | }, 810 | { 811 | "name": "_BTUBBR", 812 | "ord": 56, 813 | "Signature": "int btubbr(int chan,long bdrate);", 814 | "Comments": [] 815 | } 816 | ] 817 | } -------------------------------------------------------------------------------- /MBBSDASM/Analysis/MBBS.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Reflection; 6 | using MBBSDASM.Analysis.Artifacts; 7 | using MBBSDASM.Artifacts; 8 | using MBBSDASM.Dasm; 9 | using MBBSDASM.Enums; 10 | using MBBSDASM.Logging; 11 | using Newtonsoft.Json; 12 | using NLog; 13 | using SharpDisasm.Udis86; 14 | 15 | namespace MBBSDASM.Analysis 16 | { 17 | /// 18 | /// Performs Analysis on Imported Functions using defined Module Definiton JSON files 19 | /// 20 | public static class MBBS 21 | { 22 | private static readonly Logger _logger = LogManager.GetCurrentClassLogger(typeof(CustomLogger)); 23 | private static readonly List ModuleDefinitions; 24 | 25 | /// 26 | /// Default Constructor 27 | /// 28 | static MBBS() 29 | { 30 | ModuleDefinitions = new List(); 31 | 32 | //Load Definitions 33 | var assembly = typeof(MBBS).GetTypeInfo().Assembly; 34 | foreach (var def in assembly.GetManifestResourceNames().Where(x => x.EndsWith("_def.json"))) 35 | { 36 | using (var reader = new StreamReader(assembly.GetManifestResourceStream(def))) 37 | { 38 | ModuleDefinitions.Add(JsonConvert.DeserializeObject(reader.ReadToEnd())); 39 | } 40 | } 41 | } 42 | 43 | public static void Analyze(NEFile file) 44 | { 45 | ImportedFunctionIdentification(file); 46 | SubroutineIdentification(file); 47 | ForLoopIdentification(file); 48 | } 49 | 50 | /// 51 | /// Identification Routine for MBBS/WG Imported Functions 52 | /// s 53 | /// 54 | private static void ImportedFunctionIdentification(NEFile file) 55 | { 56 | _logger.Info($"Identifying Imported Functions"); 57 | 58 | if (!file.ImportedNameTable.Any(nt => ModuleDefinitions.Select(md => md.Name).Contains(nt.Name))) 59 | { 60 | _logger.Info($"No known Module Definitions found in target file, skipping Imported Function Identification"); 61 | return; 62 | } 63 | 64 | var trackedVariables = new List(); 65 | 66 | //Identify Functions and Label them with the module defition file 67 | foreach (var segment in file.SegmentTable.Where(x=> x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) 68 | { 69 | //Function Definition Identification Pass 70 | //Loop through each Disassembly Line in the segment that has a BranchType of CallImport or SegAddrImport 71 | foreach (var disassemblyLine in segment.DisassemblyLines.Where(x=> x.BranchToRecords.Any(y=> y.BranchType == EnumBranchType.CallImport || y.BranchType == EnumBranchType.SegAddrImport))) 72 | { 73 | //Get The Import on the Current Line 74 | var currentImport = 75 | disassemblyLine.BranchToRecords.First(z => z.BranchType == EnumBranchType.CallImport || z.BranchType == EnumBranchType.SegAddrImport); 76 | 77 | //Find the module it maps to in the ImportedNameTable 78 | var currentModule = 79 | ModuleDefinitions.FirstOrDefault(x => 80 | x.Name == file.ImportedNameTable.FirstOrDefault(y => 81 | y.Ordinal == currentImport.Segment)?.Name); 82 | 83 | //Usually DOS header stub will trigger this 84 | if (currentModule == null) 85 | continue; 86 | 87 | //Find the matching export by ordinal in one of the loaded Module JSON files 88 | var definition = currentModule.Exports.FirstOrDefault(x => x.Ord == currentImport.Offset); 89 | 90 | //Didn't have a definition for it? 91 | if (definition == null) 92 | continue; 93 | 94 | //We'll replace the old external reference with ordinal with the actual function name/sig 95 | disassemblyLine.Comments.Add(!string.IsNullOrEmpty(definition.Signature) 96 | ? definition.Signature 97 | : $"{currentModule.Name}.{definition.Name}"); 98 | 99 | //Attempt to Resolve the actual Method Signature if we have the definition in the JSON doc for this method 100 | if (!string.IsNullOrEmpty(definition.SignatureFormat) && definition.PrecedingInstructions != null && 101 | definition.PrecedingInstructions.Count > 0) 102 | { 103 | var values = new List(); 104 | foreach (var pi in definition.PrecedingInstructions) 105 | { 106 | //Check to see if the expected opcode is in the expected location 107 | var i = segment.DisassemblyLines.FirstOrDefault(x => 108 | x.Ordinal == disassemblyLine.Ordinal + pi.Offset && 109 | x.Disassembly.Mnemonic.ToString().ToUpper().EndsWith(pi.Op)); 110 | 111 | if (i == null) 112 | break; 113 | 114 | //If we know the type, attempt to cast the operand 115 | switch (pi.Type) 116 | { 117 | case "int": 118 | values.Add(i.Disassembly.Operands[0].LvalSDWord); 119 | break; 120 | case "string": 121 | if (i.Comments.Any(x => x.Contains("reference"))) 122 | { 123 | var resolvedStringComment = i.Comments.First(x => x.Contains("reference")); 124 | values.Add(resolvedStringComment.Substring( 125 | resolvedStringComment.IndexOf('\"'))); 126 | } 127 | break; 128 | case "char": 129 | values.Add((char)i.Disassembly.Operands[0].LvalSDWord); 130 | break; 131 | } 132 | } 133 | 134 | //Only add the resolved signature if we correctly identified all the values we were expecting 135 | if (values.Count == definition.PrecedingInstructions.Count) 136 | disassemblyLine.Comments.Add(string.Format($"Resolved Signature: {definition.SignatureFormat}", 137 | values.Select(x => x.ToString()).ToArray())); 138 | } 139 | 140 | //Attempt to resolve a variable this method might be saving as defined in the JSON doc 141 | if (definition.ReturnValues != null && definition.ReturnValues.Count > 0) 142 | { 143 | foreach (var rv in definition.ReturnValues) 144 | { 145 | var i = segment.DisassemblyLines.FirstOrDefault(x => 146 | x.Ordinal == disassemblyLine.Ordinal + rv.Offset && 147 | x.Disassembly.Mnemonic.ToString().ToUpper().EndsWith(rv.Op)); 148 | 149 | if (i == null) 150 | break; 151 | 152 | i.Comments.Add($"Return value saved to 0x{i.Disassembly.Operands[0].LvalUWord:X}h"); 153 | 154 | if(!string.IsNullOrEmpty(rv.Comment)) 155 | i.Comments.Add(rv.Comment); 156 | 157 | //Add this to our tracked variables, we'll go back through and re-label all instances after this analysis pass 158 | trackedVariables.Add(new TrackedVariable() { Comment = rv.Comment, Segment = segment.Ordinal, Offset = i.Disassembly.Offset, Address = i.Disassembly.Operands[0].LvalUWord}); 159 | } 160 | } 161 | 162 | //Finally, append any comments that accompany the function definition 163 | if (definition.Comments != null && definition.Comments.Count > 0) 164 | disassemblyLine.Comments.AddRange(definition.Comments); 165 | } 166 | 167 | //Variable Tracking Labeling Pass 168 | foreach (var v in trackedVariables) 169 | { 170 | foreach (var disassemblyLine in segment.DisassemblyLines.Where(x => x.Disassembly.ToString().Contains($"[0x{v.Address:X}]".ToLower()) && x.Disassembly.Offset != v.Offset)) 171 | { 172 | 173 | disassemblyLine.Comments.Add($"Reference to variable created at {v.Segment:0000}.{v.Offset:X4}h"); 174 | } 175 | } 176 | } 177 | } 178 | 179 | /// 180 | /// This method scans the disassembly for signatures of Turbo C++ FOR loops 181 | /// and labels them appropriatley 182 | /// 183 | /// 184 | private static void ForLoopIdentification(NEFile file) 185 | { 186 | /* 187 | * Borland C++ compiled i++/i-- FOR loops look like: 188 | * inc word [var] 189 | * cmp word [var], condition (unconditional jump here from before beginning of for loop logic) 190 | * conditional jump to beginning of for 191 | * 192 | * So we'll search for this basic pattern 193 | */ 194 | 195 | _logger.Info($"Identifying FOR Loops"); 196 | 197 | //Scan the code segments 198 | foreach (var segment in file.SegmentTable.Where(x => 199 | x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) 200 | { 201 | //Function Definition Identification Pass 202 | foreach (var disassemblyLine in segment.DisassemblyLines.Where(x => 203 | x.Disassembly.Mnemonic == ud_mnemonic_code.UD_Icmp && 204 | x.BranchFromRecords.Any(y => y.BranchType == EnumBranchType.Unconditional))) 205 | { 206 | 207 | 208 | if (MnemonicGroupings.IncrementDecrementGroup.Contains(segment.DisassemblyLines 209 | .First(x => x.Ordinal == disassemblyLine.Ordinal - 1).Disassembly.Mnemonic) 210 | && segment.DisassemblyLines 211 | .First(x => x.Ordinal == disassemblyLine.Ordinal + 1).BranchToRecords.Count > 0 212 | && segment.DisassemblyLines 213 | .First(x => x.Ordinal == disassemblyLine.Ordinal + 1).BranchToRecords.First(x => x.BranchType == EnumBranchType.Conditional) 214 | .Offset < disassemblyLine.Disassembly.Offset) 215 | { 216 | 217 | if (MnemonicGroupings.IncrementGroup.Contains(segment.DisassemblyLines 218 | .First(x => x.Ordinal == disassemblyLine.Ordinal - 1).Disassembly 219 | .Mnemonic)) 220 | { 221 | segment.DisassemblyLines 222 | .First(x => x.Ordinal == disassemblyLine.Ordinal - 1).Comments 223 | .Add("[FOR] Increment Value"); 224 | } 225 | else 226 | { 227 | segment.DisassemblyLines 228 | .First(x => x.Ordinal == disassemblyLine.Ordinal - 1).Comments 229 | .Add("[FOR] Decrement Value"); 230 | } 231 | 232 | disassemblyLine.Comments.Add("[FOR] Evaluate Break Condition"); 233 | 234 | //Label beginning of FOR logic by labeling source of unconditional jump 235 | segment.DisassemblyLines 236 | .First(x => x.Disassembly.Offset == disassemblyLine.BranchFromRecords 237 | .First(y => y.BranchType == EnumBranchType.Unconditional).Offset).Comments 238 | .Add("[FOR] Beginning of FOR logic"); 239 | 240 | segment.DisassemblyLines 241 | .First(x => x.Ordinal == disassemblyLine.Ordinal + 1).Comments 242 | .Add("[FOR] Branch based on evaluation"); 243 | } 244 | } 245 | 246 | } 247 | } 248 | 249 | /// 250 | /// This method scans the disassembled code and identifies subroutines, labeling them 251 | /// appropriately. This also allows for much more precise variable/argument tracking 252 | /// if we properly know the scope of the routine. 253 | /// 254 | /// 255 | private static void SubroutineIdentification(NEFile file) 256 | { 257 | _logger.Info($"Identifying Subroutines"); 258 | 259 | 260 | //Scan the code segments 261 | foreach (var segment in file.SegmentTable.Where(x => 262 | x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) 263 | { 264 | ushort subroutineId = 0; 265 | var bInSubroutine = false; 266 | for (var i = 0; i < segment.DisassemblyLines.Count; i++) 267 | { 268 | if (bInSubroutine) 269 | segment.DisassemblyLines[i].SubroutineID = subroutineId; 270 | 271 | if (segment.DisassemblyLines[i].Disassembly.Mnemonic == ud_mnemonic_code.UD_Ienter || 272 | segment.DisassemblyLines[i].BranchFromRecords.Any(x => x.BranchType == EnumBranchType.Call) || 273 | segment.DisassemblyLines[i].ExportedFunction != null || 274 | //Previous instruction was the end of a subroutine, we must be starting one that's not 275 | //referenced anywhere in the code 276 | (i > 0 && 277 | (segment.DisassemblyLines[i - 1].Disassembly.Mnemonic == ud_mnemonic_code.UD_Iretf || 278 | segment.DisassemblyLines[i - 1].Disassembly.Mnemonic == ud_mnemonic_code.UD_Iret))) 279 | { 280 | subroutineId++; 281 | bInSubroutine = true; 282 | segment.DisassemblyLines[i].SubroutineID = subroutineId; 283 | segment.DisassemblyLines[i].Comments.Insert(0, $"/---- BEGIN SUBROUTINE {subroutineId}"); 284 | continue; 285 | } 286 | 287 | if (bInSubroutine && (segment.DisassemblyLines[i].Disassembly.Mnemonic == ud_mnemonic_code.UD_Iret || 288 | segment.DisassemblyLines[i].Disassembly.Mnemonic == ud_mnemonic_code.UD_Iretf)) 289 | { 290 | bInSubroutine = false; 291 | segment.DisassemblyLines[i].Comments.Insert(0, $"\\---- END SUBROUTINE {subroutineId}"); 292 | } 293 | } 294 | } 295 | } 296 | } 297 | } -------------------------------------------------------------------------------- /MBBSDASM/Artifacts/Entry.cs: -------------------------------------------------------------------------------- 1 | namespace MBBSDASM.Artifacts 2 | { 3 | public class Entry 4 | { 5 | public ushort Ordinal { get; set; } 6 | public byte SegmentNumber { get; set; } 7 | public byte Flag { get; set; } 8 | public ushort Offset { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /MBBSDASM/Artifacts/ImportedName.cs: -------------------------------------------------------------------------------- 1 | namespace MBBSDASM.Artifacts 2 | { 3 | /// 4 | /// Represents a single record in the Imported Name Table 5 | /// 6 | public class ImportedName 7 | { 8 | public ushort Ordinal { get; set; } 9 | public ushort Offset { get; set; } 10 | public ushort FileOffset { get; set; } 11 | public string Name { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /MBBSDASM/Artifacts/MZHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MBBSDASM.Artifacts 4 | { 5 | /// 6 | /// One Block == 512 Bytes 7 | /// One Paragraph == 16 Bytes 8 | /// 9 | public class MZHeader 10 | { 11 | public ushort Signature { get; set; } 12 | public ushort BytesInLastBlock { get; set; } 13 | public ushort BlocksInFile { get; set; } 14 | public ushort RelocationEntries { get; set; } 15 | public ushort HeaderParagraphs { get; set; } 16 | public ushort MinExtraParagraphs { get; set; } 17 | public ushort MaxExtraParagraphs { get; set; } 18 | public ushort RelativeSS { get; set; } 19 | public ushort InitialSP { get; set; } 20 | public ushort Checksum { get; set; } 21 | public ushort InitialIP { get; set; } 22 | public ushort InitialCS { get; set; } 23 | public ushort RelocationOffset { get; set; } 24 | public ushort OverlayNumber { get; set; } 25 | 26 | public MZHeader(byte[] fileContent) 27 | { 28 | Signature = BitConverter.ToUInt16(fileContent, 0); 29 | BytesInLastBlock = BitConverter.ToUInt16(fileContent, 2); 30 | BlocksInFile = BitConverter.ToUInt16(fileContent, 4); 31 | RelocationEntries = BitConverter.ToUInt16(fileContent, 6); 32 | HeaderParagraphs = BitConverter.ToUInt16(fileContent, 8); 33 | MinExtraParagraphs = BitConverter.ToUInt16(fileContent, 10); 34 | MaxExtraParagraphs = BitConverter.ToUInt16(fileContent, 12); 35 | RelativeSS = BitConverter.ToUInt16(fileContent, 14); 36 | InitialSP = BitConverter.ToUInt16(fileContent, 16); 37 | Checksum = BitConverter.ToUInt16(fileContent, 18); 38 | InitialIP = BitConverter.ToUInt16(fileContent, 20); 39 | InitialCS = BitConverter.ToUInt16(fileContent, 22); 40 | RelocationOffset = BitConverter.ToUInt16(fileContent, 24); 41 | OverlayNumber = BitConverter.ToUInt16(fileContent, 26); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /MBBSDASM/Artifacts/ModuleReference.cs: -------------------------------------------------------------------------------- 1 | namespace MBBSDASM.Artifacts 2 | { 3 | /// 4 | /// Represents a single record in the Module Reference Table 5 | /// 6 | public class ModuleReference 7 | { 8 | public ushort ImportNameTableOffset { get; set; } 9 | public string Name { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /MBBSDASM/Artifacts/NEFile.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | using MBBSDASM.Enums; 6 | 7 | namespace MBBSDASM.Artifacts 8 | { 9 | /// 10 | /// Class Represents the Parsed Content of a 16-bit NE Format EXE/DLL file 11 | /// 12 | public class NEFile 13 | { 14 | //File Metadata 15 | public string Path { get; set; } 16 | public string FileName { get; set; } 17 | 18 | //Contains the entire contents of the file to be disassembled 19 | public readonly byte[] FileContent; 20 | 21 | //Artifacts of the NE Header 22 | public MZHeader DOSHeader; 23 | public NEHeader WindowsHeader; 24 | public List SegmentTable; 25 | public List ResourceTable; 26 | public List ResidentNameTable; 27 | public List ModuleReferenceTable; 28 | public List ImportedNameTable; 29 | public List EntryTable; 30 | public List NonResidentNameTable; 31 | 32 | public NEFile(string file) 33 | { 34 | FileContent = File.ReadAllBytes(file); 35 | var f = new FileInfo(file); 36 | Path = f.DirectoryName + "\\"; 37 | FileName = f.Name; 38 | Load(); 39 | } 40 | 41 | private void Load() 42 | { 43 | var data = new Span(FileContent); 44 | 45 | DOSHeader = new MZHeader(FileContent); 46 | 47 | //Verify old DOS header is correct 48 | if(DOSHeader.Signature != 23117) 49 | throw new Exception("Invaid Header"); 50 | 51 | //Locate Windows Header 52 | ushort windowsHeaderOffset; 53 | if (data[0x18] >= 0x40) 54 | { 55 | windowsHeaderOffset = BitConverter.ToUInt16(FileContent, 0x3C); 56 | } 57 | else 58 | { 59 | throw new Exception("Unable to locate Windows Header location"); 60 | } 61 | 62 | //Load Windows Header 63 | WindowsHeader = new NEHeader(data.Slice(windowsHeaderOffset, 0x3F).ToArray()) { FileOffset = windowsHeaderOffset }; 64 | 65 | //Adjust Offsets According to Spec (Offset from beginning of Windows Header, not file) 66 | WindowsHeader.SegmentTableOffset += windowsHeaderOffset; 67 | WindowsHeader.ResourceTableOffset += windowsHeaderOffset; 68 | WindowsHeader.ResidentNameTableOffset += windowsHeaderOffset; 69 | WindowsHeader.ModleReferenceTableOffset += windowsHeaderOffset; 70 | WindowsHeader.ImportedNamesTableOffset += windowsHeaderOffset; 71 | WindowsHeader.EntryTableOffset += windowsHeaderOffset; 72 | 73 | //Load Segment Table 74 | SegmentTable = new List(WindowsHeader.SegmentTableEntries); 75 | for (var i = 0; i < WindowsHeader.SegmentTableEntries; i++) 76 | { 77 | //Load Segment Header (8 bytes per record) 78 | var segment = 79 | new Segment(data.Slice(WindowsHeader.SegmentTableOffset + (i * 8), 8).ToArray()) 80 | { 81 | Ordinal = (ushort) (i + 1) 82 | }; 83 | segment.Offset <<= WindowsHeader.LogicalSectorAlignmentShift; 84 | 85 | //Attach Segment Data 86 | segment.Data = data.Slice((int)segment.Offset, segment.Length).ToArray(); 87 | 88 | //Attach Relocation Records 89 | if (segment.Flags.Contains(EnumSegmentFlags.HasRelocationInfo)) 90 | { 91 | var relocationInfoCursor = (int)segment.Offset + segment.Length; 92 | var relocationRecordEntries = BitConverter.ToUInt16(FileContent, relocationInfoCursor); 93 | relocationInfoCursor += 2; 94 | var records = new List(); 95 | for (var j = 0; j < relocationRecordEntries; j++) 96 | { 97 | records.Add(new RelocationRecord {Data = data.Slice(relocationInfoCursor + j * 8, 8).ToArray()}); 98 | } 99 | segment.RelocationRecords = records; 100 | } 101 | SegmentTable.Add(segment); 102 | } 103 | 104 | //Load Resource Table 105 | ResourceTable = new List(); 106 | //TODO -- Resource Table isn't used by MBBS modules so we'll skip loading this for now 107 | //TODO -- Implement this in a future version 108 | 109 | //Load Resident Name Table 110 | ResidentNameTable = new List(); 111 | for (var i = 0; i < WindowsHeader.ModleReferenceTableOffset; i +=2) 112 | { 113 | var residentName = new ResidentName(); 114 | var residentNameLength = data[WindowsHeader.ResidentNameTableOffset + i]; 115 | 116 | //End of Names 117 | if (residentNameLength == 0) 118 | break; 119 | 120 | i++; 121 | residentName.Name = 122 | Encoding.ASCII.GetString(data.Slice(WindowsHeader.ResidentNameTableOffset + i, residentNameLength) 123 | .ToArray()); 124 | i += residentNameLength; 125 | residentName.IndexIntoEntryTable = BitConverter.ToUInt16(FileContent, WindowsHeader.ResidentNameTableOffset + i); 126 | ResidentNameTable.Add(residentName); 127 | } 128 | 129 | //Load Module & Imported Name Reference Tables 130 | ModuleReferenceTable = new List(WindowsHeader.ModuleReferenceTableEntries); 131 | ImportedNameTable = new List(); 132 | for (var i = 0; i < WindowsHeader.ModuleReferenceTableEntries; i++) 133 | { 134 | var nameOffset = 135 | BitConverter.ToUInt16(FileContent, WindowsHeader.ModleReferenceTableOffset + i * 2); 136 | 137 | var fileOffset = (ushort)(nameOffset + WindowsHeader.ImportedNamesTableOffset); 138 | var module = new ModuleReference(); 139 | var importedName = new ImportedName() { Offset = nameOffset, FileOffset = fileOffset}; 140 | 141 | var name = Encoding.ASCII.GetString(data.Slice(fileOffset + 1, data[fileOffset]).ToArray()); 142 | 143 | module.Name = name; 144 | importedName.Name = name; 145 | importedName.Ordinal = (ushort)(i + 1); //Ordinal Index in Resource Tables start with 1 146 | 147 | ModuleReferenceTable.Add(module); 148 | ImportedNameTable.Add(importedName); 149 | } 150 | 151 | //Load Entry Table 152 | EntryTable = new List(data[WindowsHeader.EntryTableOffset]); 153 | 154 | //Value of 0 denotes no segment data 155 | if (data[WindowsHeader.EntryTableOffset] > 0) 156 | { 157 | var entryByteOffset = 0; 158 | ushort entryOrdinal = 1; 159 | while (WindowsHeader.EntryTableOffset + entryByteOffset < WindowsHeader.NonResidentNameTableOffset) 160 | { 161 | //0xFF is moveable (6 bytes), anything else is fixed as it becomes the segment number 162 | var entryCount = data[WindowsHeader.EntryTableOffset + entryByteOffset]; 163 | var entrySegment = data[WindowsHeader.EntryTableOffset + entryByteOffset + 1]; 164 | 165 | if (entryCount == 1 && entrySegment == 0) 166 | { 167 | entryByteOffset += 2; 168 | entryOrdinal += 1; 169 | continue; 170 | } 171 | 172 | var entrySize = entrySegment == 0xFF ? 6 : 3; 173 | 174 | for (var i = 0; i < entryCount; i++) 175 | { 176 | var entry = new Entry { SegmentNumber = entrySegment}; 177 | if (entrySize == 3) 178 | { 179 | entry.Flag = data[WindowsHeader.EntryTableOffset + entryByteOffset + 2 + entrySize * i]; 180 | entry.Offset = BitConverter.ToUInt16(FileContent, 181 | WindowsHeader.EntryTableOffset + entryByteOffset + 3 + entrySize * i); 182 | entry.SegmentNumber = entrySegment; 183 | entry.Ordinal = entryOrdinal; //First Entry is the Resident Name table is the module name, so we shift the ordinals by 1 to line up 184 | } 185 | else 186 | { 187 | entry.Flag = data[WindowsHeader.EntryTableOffset + entryByteOffset + 2 + entrySize * i]; 188 | entry.SegmentNumber = data[WindowsHeader.EntryTableOffset + entryByteOffset + 5 + (entrySize * i)]; 189 | entry.Offset = 190 | BitConverter.ToUInt16(FileContent, 191 | WindowsHeader.EntryTableOffset + entryByteOffset + 6 + entrySize * i); 192 | } 193 | entryOrdinal++; 194 | EntryTable.Add(entry); 195 | } 196 | 197 | entryByteOffset += (entryCount * entrySize) + 2; 198 | } 199 | } 200 | 201 | //Load Non-Resident Name Table 202 | NonResidentNameTable = new List(); 203 | for (var i = (int)WindowsHeader.NonResidentNameTableOffset; i < (WindowsHeader.NonResidentNameTableOffset + WindowsHeader.NonResidentNameTableLength); i += 2) 204 | { 205 | var nameLength = data[i]; 206 | i++; 207 | var name = Encoding.ASCII.GetString(data.Slice(i, nameLength).ToArray()); 208 | i += nameLength; 209 | var indexIntoEntryTable = BitConverter.ToUInt16(FileContent, i); 210 | NonResidentNameTable.Add(new NonResidentName() { Name = name, IndexIntoEntryTable = indexIntoEntryTable}); 211 | } 212 | } 213 | } 214 | } -------------------------------------------------------------------------------- /MBBSDASM/Artifacts/NEHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MBBSDASM.Artifacts 4 | { 5 | public class NEHeader 6 | { 7 | public ushort FileOffset { get; set; } 8 | public byte LinkerVersion { get; set; } 9 | public byte LinkerRevision { get; set; } 10 | public ushort EntryTableOffset { get; set; } 11 | public ushort EntryTableLength { get; set; } 12 | public uint FileCRC32 { get; set; } 13 | public ushort FlagWord { get; set; } 14 | public ushort AutomaticDataSegment { get; set; } 15 | public ushort DataSegmentHeapInitialSize { get; set; } 16 | public ushort DataSegmentStackInitialSize { get; set; } 17 | public uint SegmentNumberOffsetCSIP { get; set; } 18 | public uint SegmentNumberOffsetSSSP { get; set; } 19 | public ushort SegmentTableEntries { get; set; } 20 | public ushort ModuleReferenceTableEntries { get; set; } 21 | public ushort NonResidentNameTableLength { get; set; } 22 | public ushort SegmentTableOffset { get; set; } 23 | public ushort ResourceTableOffset { get; set; } 24 | public ushort ResidentNameTableOffset { get; set; } 25 | public ushort ModleReferenceTableOffset { get; set; } 26 | public ushort ImportedNamesTableOffset { get; set; } 27 | public uint NonResidentNameTableOffset { get; set; } 28 | public ushort MovableEntrypoints { get; set; } 29 | public ushort LogicalSectorAlignmentShift { get; set; } 30 | public ushort ResourceEntries { get; set; } 31 | public byte ExecutableType { get; set; } 32 | public byte[] Reserved { get; set; } 33 | 34 | public NEHeader(byte[] headerContents) 35 | { 36 | if (headerContents[0] != 'N' || headerContents[1] != 'E') 37 | throw new Exception("Invalid Windows Header Signature Word"); 38 | 39 | LinkerVersion = headerContents[0x02]; 40 | LinkerRevision = headerContents[0x03]; 41 | EntryTableOffset = BitConverter.ToUInt16(headerContents, 0x04); 42 | EntryTableLength = BitConverter.ToUInt16(headerContents, 0x06); 43 | FileCRC32 = BitConverter.ToUInt32(headerContents, 0x08); 44 | FlagWord = BitConverter.ToUInt16(headerContents, 0x0C); 45 | AutomaticDataSegment = BitConverter.ToUInt16(headerContents, 0x0E); 46 | DataSegmentHeapInitialSize = BitConverter.ToUInt16(headerContents, 0x10); 47 | DataSegmentStackInitialSize = BitConverter.ToUInt16(headerContents, 0x12); 48 | SegmentNumberOffsetCSIP = BitConverter.ToUInt32(headerContents, 0x14); 49 | SegmentNumberOffsetSSSP = BitConverter.ToUInt32(headerContents, 0x18); 50 | SegmentTableEntries = BitConverter.ToUInt16(headerContents, 0x1C); 51 | ModuleReferenceTableEntries = BitConverter.ToUInt16(headerContents, 0x1E); 52 | NonResidentNameTableLength = BitConverter.ToUInt16(headerContents, 0x20); 53 | SegmentTableOffset = BitConverter.ToUInt16(headerContents, 0x22); 54 | ResourceTableOffset = BitConverter.ToUInt16(headerContents, 0x24); 55 | ResidentNameTableOffset = BitConverter.ToUInt16(headerContents, 0x26); 56 | ModleReferenceTableOffset = BitConverter.ToUInt16(headerContents, 0x28); 57 | ImportedNamesTableOffset = BitConverter.ToUInt16(headerContents, 0x2A); 58 | NonResidentNameTableOffset = BitConverter.ToUInt32(headerContents, 0x2C); 59 | MovableEntrypoints = BitConverter.ToUInt16(headerContents, 0x30); 60 | LogicalSectorAlignmentShift = BitConverter.ToUInt16(headerContents, 0x32); 61 | ResourceEntries = BitConverter.ToUInt16(headerContents, 0x34); 62 | ExecutableType = headerContents[0x36]; 63 | Reserved = new byte[8]; 64 | Array.Copy(headerContents, 0x37, Reserved, 0, 8); 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /MBBSDASM/Artifacts/NonResidentName.cs: -------------------------------------------------------------------------------- 1 | namespace MBBSDASM.Artifacts 2 | { 3 | /// 4 | /// Represents a single record in the Non-Resident Name Table 5 | /// 6 | public class NonResidentName 7 | { 8 | public string Name { get; set; } 9 | public ushort IndexIntoEntryTable { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /MBBSDASM/Artifacts/RelocationRecord.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MBBSDASM.Enums; 3 | 4 | namespace MBBSDASM.Artifacts 5 | { 6 | /// 7 | /// Represents a single Relocation Record 8 | /// 9 | public class RelocationRecord 10 | { 11 | public byte[] Data; 12 | public byte SourceType => Data[0]; 13 | public EnumRecordsFlag Flag => (EnumRecordsFlag) Data[1]; 14 | public ushort Offset => BitConverter.ToUInt16(Data, 2); 15 | 16 | public Tuple TargetTypeValueTuple 17 | { 18 | get 19 | { 20 | switch (Flag) 21 | { 22 | case EnumRecordsFlag.INTERNALREF | EnumRecordsFlag.ADDITIVE: 23 | case EnumRecordsFlag.INTERNALREF: 24 | return new Tuple(EnumRecordsFlag.INTERNALREF, Data[4], 25 | Data[5], BitConverter.ToUInt16(Data, 6)); 26 | 27 | case EnumRecordsFlag.IMPORTORDINAL | EnumRecordsFlag.ADDITIVE: 28 | case EnumRecordsFlag.IMPORTORDINAL: 29 | return new Tuple(EnumRecordsFlag.IMPORTORDINAL, 30 | BitConverter.ToUInt16(Data, 4), BitConverter.ToUInt16(Data, 6), 0); 31 | 32 | case EnumRecordsFlag.IMPORTNAME | EnumRecordsFlag.ADDITIVE: 33 | case EnumRecordsFlag.IMPORTNAME: 34 | return new Tuple(EnumRecordsFlag.IMPORTNAME, 35 | BitConverter.ToUInt16(Data, 4), BitConverter.ToUInt16(Data, 6), 0); 36 | default: 37 | break; 38 | } 39 | 40 | return null; 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /MBBSDASM/Artifacts/ResidentName.cs: -------------------------------------------------------------------------------- 1 | namespace MBBSDASM.Artifacts 2 | { 3 | /// 4 | /// Represents a single record in the Resident Name Table 5 | /// 6 | public class ResidentName 7 | { 8 | public string Name { get; set; } 9 | public ushort IndexIntoEntryTable { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /MBBSDASM/Artifacts/ResourceEntry.cs: -------------------------------------------------------------------------------- 1 | namespace MBBSDASM.Artifacts 2 | { 3 | /// 4 | /// Represents a single Resource Entry in a Resource Record 5 | /// 6 | public class ResourceEntry 7 | { 8 | 9 | } 10 | } -------------------------------------------------------------------------------- /MBBSDASM/Artifacts/ResourceRecord.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace MBBSDASM.Artifacts 4 | { 5 | /// 6 | /// Represents a single record in the Resource Table 7 | /// 8 | public class ResourceRecord 9 | { 10 | public List ResourceEntries { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /MBBSDASM/Artifacts/Segment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MBBSDASM.Dasm; 4 | using MBBSDASM.Enums; 5 | 6 | namespace MBBSDASM.Artifacts 7 | { 8 | /// 9 | /// Represents a single segment of the segmented EXE/DLL file 10 | /// 11 | public class Segment 12 | { 13 | private ushort _flag; 14 | 15 | public ushort Ordinal { get; set; } 16 | public uint Offset { get; set; } 17 | public ushort Length { get; set; } 18 | public ushort MinLength { get; set; } 19 | 20 | public ushort Flag 21 | { 22 | get => _flag; 23 | set 24 | { 25 | _flag = value; 26 | Flags = new List(); 27 | Flags.Add((_flag | (short) EnumSegmentFlags.Data) == _flag 28 | ? EnumSegmentFlags.Data 29 | : EnumSegmentFlags.Code); 30 | 31 | if ((_flag | (short) EnumSegmentFlags.Iterated) == _flag) 32 | Flags.Add(EnumSegmentFlags.Iterated); 33 | 34 | Flags.Add((_flag | (short) EnumSegmentFlags.Movable) == _flag 35 | ? EnumSegmentFlags.Movable 36 | : EnumSegmentFlags.Fixed); 37 | 38 | Flags.Add((_flag | (short) EnumSegmentFlags.Pure) == _flag 39 | ? EnumSegmentFlags.Pure 40 | : EnumSegmentFlags.Impure); 41 | 42 | Flags.Add((_flag | (short) EnumSegmentFlags.Preload) == _flag 43 | ? EnumSegmentFlags.Preload 44 | : EnumSegmentFlags.LoadOnCall); 45 | 46 | Flags.Add((_flag | (short) EnumSegmentFlags.ExecuteOnly) == _flag 47 | ? EnumSegmentFlags.ExecuteOnly 48 | : EnumSegmentFlags.ReadOnly); 49 | 50 | if ((_flag | (short) EnumSegmentFlags.HasRelocationInfo) == _flag) 51 | Flags.Add(EnumSegmentFlags.HasRelocationInfo); 52 | 53 | if ((_flag | (short) EnumSegmentFlags.HasDebuggingInfo) == _flag) 54 | Flags.Add(EnumSegmentFlags.HasDebuggingInfo); 55 | } 56 | } 57 | 58 | public List Flags { get; private set; } 59 | 60 | public byte[] Data { get; set; } 61 | 62 | public List RelocationRecords { get; set; } 63 | 64 | public List DisassemblyLines { get; set; } 65 | 66 | public List StringRecords { get; set; } 67 | 68 | public Segment() {} 69 | 70 | public Segment(byte[] segmentHeader) 71 | { 72 | Offset = BitConverter.ToUInt16(segmentHeader, 0); 73 | Length = BitConverter.ToUInt16(segmentHeader, 2); 74 | Flag = BitConverter.ToUInt16(segmentHeader, 4); 75 | MinLength = BitConverter.ToUInt16(segmentHeader, 6); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /MBBSDASM/Artifacts/StringRecord.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | 3 | namespace MBBSDASM.Artifacts 4 | { 5 | public class StringRecord 6 | { 7 | public int Segment { get; set; } 8 | public int Offset { get; set; } 9 | public int Length { get; set; } 10 | public string Value { get; set; } 11 | 12 | /// 13 | /// Returns TRUE if the string contains printable characters 14 | /// 15 | public bool IsPrintable 16 | { 17 | get { return Value.ToCharArray().Any(x => x > 32 && x < 126); } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /MBBSDASM/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace MBBSDASM 2 | { 3 | internal static class Constants 4 | { 5 | internal const string ProgramName = "MBBSDASM"; 6 | internal const string ProgramVersion = "1.5"; 7 | 8 | internal const int MAX_INSTRUCTION_LENGTH = 15; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /MBBSDASM/Dasm/BranchRecord.cs: -------------------------------------------------------------------------------- 1 | using MBBSDASM.Enums; 2 | 3 | namespace MBBSDASM.Dasm 4 | { 5 | public class BranchRecord 6 | { 7 | public ushort Segment { get; set; } 8 | public ulong Offset { get; set; } 9 | public EnumBranchType BranchType { get; set; } 10 | public bool IsRelocation { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /MBBSDASM/Dasm/Disassembler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Runtime.CompilerServices; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using MBBSDASM.Artifacts; 9 | using MBBSDASM.Enums; 10 | using MBBSDASM.Logging; 11 | using NLog; 12 | using SharpDisasm; 13 | using SharpDisasm.Udis86; 14 | 15 | namespace MBBSDASM.Dasm 16 | { 17 | /// 18 | /// Main Disassembler Class for 16-Bit x86 NE Format EXE/DLL Files 19 | /// 20 | public class Disassembler : IDisposable 21 | { 22 | protected static readonly Logger _logger = LogManager.GetCurrentClassLogger(typeof(CustomLogger)); 23 | private NEFile _inputFile; 24 | 25 | public Disassembler(string inputFile) 26 | { 27 | _inputFile = new NEFile(inputFile); 28 | } 29 | 30 | 31 | public NEFile Disassemble(bool minimal = false) 32 | { 33 | //Decompile Each Segment 34 | foreach (var s in _inputFile.SegmentTable) 35 | { 36 | _logger.Info($"Performing Disassembly of Segment {s.Ordinal}"); 37 | s.DisassemblyLines = DisassembleSegment(s); 38 | } 39 | 40 | //Skip Additional Analysis if they selected minimal 41 | if (!minimal) 42 | { 43 | _logger.Info($"Extracting Strings from DATA Segments"); 44 | ProcessStrings(_inputFile); 45 | 46 | _logger.Info($"Applying Relocation Info "); 47 | ApplyRelocationInfo(_inputFile); 48 | 49 | _logger.Info($"Applying String References"); 50 | ResolveStringReferences(_inputFile); 51 | 52 | _logger.Info($"Resolving Jump Targets"); 53 | ResolveJumpTargets(_inputFile); 54 | 55 | _logger.Info($"Resolving Call Targets"); 56 | ResolveCallTargets(_inputFile); 57 | 58 | _logger.Info($"Identifying Entry Points"); 59 | IdentifyEntryPoints(_inputFile); 60 | } 61 | 62 | return _inputFile; 63 | } 64 | 65 | /// 66 | /// Takes the raw binary code segment and feeds it into the x86 disassembler library 67 | /// 68 | /// 69 | /// 70 | private List DisassembleSegment(Segment segment) 71 | { 72 | //Only DisassembleSegment Code Segments 73 | if (!segment.Flags.Contains(EnumSegmentFlags.Code)) 74 | return new List(); 75 | 76 | var output = new List(); 77 | 78 | var disassembler = new SharpDisasm.Disassembler(segment.Data, ArchitectureMode.x86_16, 0, true); 79 | 80 | //Perform Raw Disassembly 81 | var ordinal = 0; 82 | foreach (var disassembly in disassembler.Disassemble()) 83 | { 84 | output.Add(new DisassemblyLine 85 | { 86 | Disassembly = disassembly, 87 | Comments = new List(), 88 | Ordinal = ordinal, 89 | BranchFromRecords = new ConcurrentBag(), 90 | BranchToRecords = new ConcurrentBag() 91 | }); 92 | ordinal++; 93 | } 94 | 95 | return output; 96 | } 97 | 98 | /// 99 | /// Locates offsets for exported functions in the Entry table and labels them 100 | /// 101 | /// 102 | private void IdentifyEntryPoints(NEFile file) 103 | { 104 | foreach (var entry in file.EntryTable) 105 | { 106 | var seg = file.SegmentTable.First(x => x.Ordinal == entry.SegmentNumber); 107 | 108 | var fnName = file.NonResidentNameTable.FirstOrDefault(x => x.IndexIntoEntryTable == entry.Ordinal) 109 | ?.Name; 110 | 111 | if (string.IsNullOrEmpty(fnName)) 112 | fnName = file.ResidentNameTable.FirstOrDefault(x => x.IndexIntoEntryTable == entry.Ordinal)?.Name; 113 | 114 | seg.DisassemblyLines.Where(x => x.Disassembly.Offset == entry.Offset) 115 | .FirstOrDefault(x => 116 | { 117 | x.ExportedFunction = new ExportedFunctionRecord() {Name = fnName}; 118 | return true; 119 | }); 120 | 121 | } 122 | } 123 | 124 | /// 125 | /// Reads the Relocation Table (if present) at the end of a segment and comments about the relocations that 126 | /// are being applied. This identifies both internal and external function calls. 127 | /// 128 | /// 129 | private void ApplyRelocationInfo(NEFile file) 130 | { 131 | Parallel.ForEach(file.SegmentTable, (segment) => 132 | { 133 | if (!segment.Flags.Contains(EnumSegmentFlags.Code) && 134 | !segment.Flags.Contains(EnumSegmentFlags.HasRelocationInfo)) 135 | return; 136 | Parallel.ForEach(segment.RelocationRecords, (relocationRecord) => 137 | { 138 | var disAsm = 139 | segment.DisassemblyLines.FirstOrDefault(x => 140 | x.Disassembly.Offset == relocationRecord.Offset - 1UL); 141 | 142 | if (disAsm == null) 143 | return; 144 | 145 | switch (relocationRecord.Flag) 146 | { 147 | case EnumRecordsFlag.IMPORTORDINAL | EnumRecordsFlag.ADDITIVE: 148 | case EnumRecordsFlag.IMPORTORDINAL: 149 | disAsm.BranchToRecords.Add(new BranchRecord 150 | { 151 | IsRelocation = true, 152 | BranchType = 153 | disAsm.Disassembly.Mnemonic == ud_mnemonic_code.UD_Icall 154 | ? EnumBranchType.CallImport 155 | : EnumBranchType.SegAddrImport, 156 | Segment = relocationRecord.TargetTypeValueTuple.Item2, 157 | Offset = relocationRecord.TargetTypeValueTuple.Item3 158 | }); 159 | break; 160 | case EnumRecordsFlag.INTERNALREF | EnumRecordsFlag.ADDITIVE: 161 | case EnumRecordsFlag.INTERNALREF: 162 | if (disAsm.Disassembly.Mnemonic == ud_mnemonic_code.UD_Icall) 163 | { 164 | //Set Target 165 | file.SegmentTable 166 | .FirstOrDefault(x => x.Ordinal == relocationRecord.TargetTypeValueTuple.Item2) 167 | ?.DisassemblyLines 168 | .FirstOrDefault(y => 169 | y.Disassembly.Offset == relocationRecord.TargetTypeValueTuple.Item4) 170 | ?.BranchFromRecords 171 | .Add(new BranchRecord() 172 | { 173 | Segment = segment.Ordinal, 174 | Offset = disAsm.Disassembly.Offset, 175 | IsRelocation = true, 176 | BranchType = EnumBranchType.Call 177 | }); 178 | 179 | //Set Origin 180 | disAsm.BranchToRecords.Add(new BranchRecord() 181 | { 182 | Segment = relocationRecord.TargetTypeValueTuple.Item2, 183 | Offset = relocationRecord.TargetTypeValueTuple.Item4, 184 | BranchType = EnumBranchType.Call, 185 | IsRelocation = true 186 | }); 187 | } 188 | else 189 | { 190 | disAsm.BranchToRecords.Add(new BranchRecord() 191 | { 192 | IsRelocation = true, 193 | BranchType = EnumBranchType.SegAddr, 194 | Segment = relocationRecord.TargetTypeValueTuple.Item2 195 | }); 196 | } 197 | 198 | break; 199 | case EnumRecordsFlag.IMPORTNAME: 200 | disAsm.BranchToRecords.Add(new BranchRecord 201 | { 202 | IsRelocation = true, 203 | BranchType = EnumBranchType.CallImport, 204 | Segment = relocationRecord.TargetTypeValueTuple.Item3 205 | }); 206 | break; 207 | case EnumRecordsFlag.TARGET_MASK: 208 | break; 209 | } 210 | }); 211 | }); 212 | } 213 | 214 | /// 215 | /// This looks at the op and operand of the instructions and makes a best guess at the instructions that are referencing string data 216 | /// We inspect any instruction that interacts with the DX or DS regstiers, as these hold the data segments and then look at the address 217 | /// 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. 218 | /// 219 | /// 220 | private void ResolveStringReferences(NEFile file) 221 | { 222 | var flagNext = false; 223 | var dataSegmentToUse = 0; 224 | foreach (var segment in file.SegmentTable) 225 | { 226 | if (!segment.Flags.Contains(EnumSegmentFlags.Code) || segment.DisassemblyLines == null || 227 | segment.DisassemblyLines.Count == 0) 228 | continue; 229 | 230 | foreach (var disassemblyLine in segment.DisassemblyLines) 231 | { 232 | 233 | //mov opcode 234 | if (disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Imov && 235 | //Filter out any mov's with relative register math, mostly false positives 236 | !disassemblyLine.Disassembly.ToString().Contains("-") && 237 | !disassemblyLine.Disassembly.ToString().Contains("+") && 238 | !disassemblyLine.Disassembly.ToString().Contains(":")) 239 | { 240 | //MOV ax, SEG ADDR sets the current Data Segment to use 241 | if (disassemblyLine.BranchToRecords.Any(x => 242 | x.IsRelocation && x.BranchType == EnumBranchType.SegAddr)) 243 | dataSegmentToUse = disassemblyLine.BranchToRecords.First().Segment; 244 | 245 | 246 | if (dataSegmentToUse > 0) 247 | { 248 | //mov dx, #### 249 | if (disassemblyLine.Disassembly.Operands[0].Base == ud_type.UD_R_DX && 250 | disassemblyLine.Disassembly.Operands.Length == 2 && 251 | disassemblyLine.Disassembly.Operands[1].LvalUWord > 0) 252 | { 253 | var f = file.SegmentTable 254 | .First(x => x.Ordinal == dataSegmentToUse); 255 | if (f.StringRecords != null) { 256 | var filt = f.StringRecords.Where(y => 257 | y.Offset == disassemblyLine.Disassembly.Operands[1].LvalUWord); 258 | if (filt != null) 259 | disassemblyLine.StringReference = filt.ToList(); 260 | } 261 | 262 | continue; 263 | } 264 | 265 | //mov ax, #### 266 | if (flagNext && disassemblyLine.Disassembly.Operands[0].Base == ud_type.UD_R_AX && 267 | disassemblyLine.Disassembly.Operands.Length == 2 && 268 | disassemblyLine.Disassembly.Operands[1].LvalUWord > 0) 269 | { 270 | flagNext = false; 271 | 272 | disassemblyLine.StringReference = file.SegmentTable 273 | .First(x => x.Ordinal == dataSegmentToUse).StringRecords.Where(y => 274 | y.Offset == disassemblyLine.Disassembly.Operands[1].LvalUWord).ToList(); 275 | 276 | continue; 277 | } 278 | 279 | //mov dx, ds is usually followed by a mov ax, #### which is a string reference 280 | if (disassemblyLine.Disassembly.Operands.Length == 2 && 281 | disassemblyLine.Disassembly.Operands[0].Base == ud_type.UD_R_DX && 282 | disassemblyLine.Disassembly.Operands[1].Base == ud_type.UD_R_DS) 283 | { 284 | flagNext = true; 285 | continue; 286 | } 287 | } 288 | } 289 | 290 | if (dataSegmentToUse >= 0) 291 | { 292 | //push #### following a push ds 293 | if (flagNext && disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ipush && 294 | disassemblyLine.Disassembly.Operands[0].LvalUWord > 0) 295 | { 296 | flagNext = false; 297 | 298 | var potential = new List(); 299 | foreach (var s in file.SegmentTable.Where(x => x.StringRecords != null)) 300 | { 301 | if (s.StringRecords.Any(x => 302 | x.Offset == disassemblyLine.Disassembly.Operands[0].LvalUWord)) 303 | { 304 | potential.Add(s.StringRecords.First(x => 305 | x.Offset == disassemblyLine.Disassembly.Operands[0].LvalUWord)); 306 | } 307 | } 308 | 309 | disassemblyLine.StringReference = potential.Where(x => x.IsPrintable).ToList(); 310 | continue; 311 | } 312 | 313 | //push ds followed by a push #### 314 | if (disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ipush && 315 | disassemblyLine.Disassembly.Operands.Any(x => x.Base == ud_type.UD_R_DS)) 316 | { 317 | flagNext = true; 318 | continue; 319 | } 320 | } 321 | 322 | flagNext = false; 323 | } 324 | } 325 | } 326 | 327 | /// 328 | /// Scans through the disassembled code and adds comments on any Conditional or Unconditional Jump 329 | /// Labels the destination where the source came from 330 | /// 331 | /// 332 | private void ResolveJumpTargets(NEFile file) 333 | { 334 | 335 | //Setup variables to make if/where clauses much easier to read 336 | var jumpShortOps = new[] 337 | { 338 | 0xEB, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 339 | 0xE3 340 | }; 341 | var jumpNearOps1stByte = new[] {0xE9, 0x0F}; 342 | var jumpNearOps2ndByte = new[] 343 | {0x80, 0x81, 0x82, 0x83, 0x84, 0x5, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F}; 344 | 345 | foreach (var segment in file.SegmentTable.Where(x => 346 | x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) 347 | { 348 | //Only op+operand <= 3 bytes, skip jmp word ptr because we won't be able to label those 349 | foreach (var disassemblyLine in segment.DisassemblyLines.Where(x => 350 | MnemonicGroupings.JumpGroup.Contains(x.Disassembly.Mnemonic) && x.Disassembly.Bytes.Length <= 3)) 351 | { 352 | ulong target = 0; 353 | 354 | //Jump Short, Relative to next Instruction (8 bit) 355 | if (jumpShortOps.Contains(disassemblyLine.Disassembly.Bytes[0])) 356 | { 357 | target = ToRelativeOffset8(disassemblyLine.Disassembly.Bytes[1], 358 | disassemblyLine.Disassembly.Offset, disassemblyLine.Disassembly.Bytes.Length); 359 | } 360 | 361 | //Jump Near, Relative to next Instruction (16 bit) 362 | //Check to see if it's a 1 byte unconditinoal or a 2 byte conditional 363 | if (jumpNearOps1stByte.Contains(disassemblyLine.Disassembly.Bytes[0]) && 364 | (disassemblyLine.Disassembly.Bytes[0] == 0xE9 || 365 | jumpNearOps2ndByte.Contains(disassemblyLine.Disassembly.Bytes[1]))) 366 | { 367 | target = ToRelativeOffset16(BitConverter.ToUInt16(disassemblyLine.Disassembly.Bytes, 368 | disassemblyLine.Disassembly.Bytes[0] == 0xE9 ? 1 : 2), 369 | disassemblyLine.Disassembly.Offset, 370 | disassemblyLine.Disassembly.Bytes.Length); 371 | } 372 | 373 | //Set Target 374 | segment.DisassemblyLines.FirstOrDefault(x => x.Disassembly.Offset == target)?.BranchFromRecords 375 | .Add(new BranchRecord 376 | { 377 | Segment = segment.Ordinal, 378 | Offset = disassemblyLine.Disassembly.Offset, 379 | BranchType = 380 | disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ijmp 381 | ? EnumBranchType.Unconditional 382 | : EnumBranchType.Conditional, 383 | IsRelocation = false 384 | }); 385 | 386 | //Set Origin 387 | disassemblyLine.BranchToRecords.Add(new BranchRecord 388 | { 389 | Segment = segment.Ordinal, 390 | Offset = target, 391 | BranchType = 392 | disassemblyLine.Disassembly.Mnemonic == ud_mnemonic_code.UD_Ijmp 393 | ? EnumBranchType.Unconditional 394 | : EnumBranchType.Conditional, 395 | IsRelocation = false 396 | }); 397 | } 398 | } 399 | } 400 | 401 | /// 402 | /// Scans through the code and adds comments to any Call 403 | /// Labels the destination where the source came from 404 | /// 405 | /// 406 | private void ResolveCallTargets(NEFile file) 407 | { 408 | foreach (var segment in file.SegmentTable.Where(x => 409 | x.Flags.Contains(EnumSegmentFlags.Code) && x.DisassemblyLines.Count > 0)) 410 | { 411 | //Only processing 3 byte calls 412 | foreach (var j in segment.DisassemblyLines.Where(x => 413 | x.Disassembly.Bytes[0] == 0xE8 && x.Disassembly.Bytes.Length <= 3)) 414 | { 415 | 416 | ulong target = (ushort) (BitConverter.ToUInt16(j.Disassembly.Bytes, 1) + j.Disassembly.Offset + 3); 417 | 418 | //Set Target 419 | segment.DisassemblyLines.FirstOrDefault(x => 420 | x.Disassembly.Offset == target)?.BranchFromRecords.Add(new BranchRecord() 421 | { 422 | Segment = segment.Ordinal, 423 | Offset = j.Disassembly.Offset, 424 | BranchType = EnumBranchType.Call, 425 | IsRelocation = false 426 | }); 427 | 428 | //Set Origin 429 | j.BranchToRecords.Add(new BranchRecord() 430 | { 431 | Segment = segment.Ordinal, 432 | Offset = target, 433 | BranchType = EnumBranchType.Call, 434 | IsRelocation = false 435 | }); 436 | } 437 | } 438 | } 439 | 440 | 441 | /// 442 | /// Scans through DATA segments within the specified file extracting NULL terminated strings 443 | /// 444 | /// 445 | private void ProcessStrings(NEFile file) 446 | { 447 | //Filter down potential segments 448 | foreach (var seg in file.SegmentTable.Where(x => x.Flags.Contains(EnumSegmentFlags.Data))) 449 | { 450 | seg.StringRecords = new List(); 451 | var sbBuffer = new StringBuilder(); 452 | for (var i = 0; i < seg.Length; i++) 453 | { 454 | if (seg.Data[i] == 0x0) 455 | { 456 | if (sbBuffer.Length > 0) 457 | { 458 | seg.StringRecords.Add(new StringRecord 459 | { 460 | Segment = seg.Ordinal, 461 | Offset = i - sbBuffer.Length, 462 | Length = sbBuffer.Length, 463 | Value = sbBuffer.ToString() 464 | }); 465 | sbBuffer.Clear(); 466 | } 467 | 468 | continue; 469 | } 470 | 471 | sbBuffer.Append((char) seg.Data[i]); 472 | } 473 | } 474 | } 475 | 476 | 477 | /// 478 | /// Calculates Relative Offset for 16bit Operand 479 | /// 480 | /// 481 | /// 482 | /// 483 | /// 484 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 485 | private static ulong ToRelativeOffset16(ushort operand, ulong currentOffset, int instructionLength) 486 | { 487 | if (operand < 0x7FFF) 488 | { 489 | //Near Forward Jump 490 | return operand + currentOffset + (ulong) instructionLength; 491 | } 492 | 493 | //Near Backwards Jump 494 | return currentOffset - (ushort) ~operand + (ulong) instructionLength; 495 | } 496 | 497 | /// 498 | /// Calculates Relative Offset for 8bit Operand 499 | /// 500 | /// 501 | /// 502 | /// 503 | /// 504 | [MethodImpl(MethodImplOptions.AggressiveInlining)] 505 | private static ulong ToRelativeOffset8(byte operand, ulong currentOffset, int instructionLength) 506 | { 507 | if (operand <= 0x7F) 508 | { 509 | //Short Forward Jump 510 | return operand + currentOffset + (ulong) instructionLength; 511 | } 512 | 513 | //Short Backwards Jump 514 | return (ulong) ((int) currentOffset + instructionLength - ((byte) ~operand + 1)); 515 | } 516 | 517 | 518 | /// 519 | public void Dispose() 520 | { 521 | _inputFile = null; 522 | } 523 | } 524 | } -------------------------------------------------------------------------------- /MBBSDASM/Dasm/DisassemblyLine.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Collections.Generic; 3 | using MBBSDASM.Artifacts; 4 | using Instruction = SharpDisasm.Instruction; 5 | 6 | namespace MBBSDASM.Dasm 7 | { 8 | /// 9 | /// Class that contains the actual disassembly obejct from SharpDisasm but also 10 | /// additional Metadata 11 | /// 12 | public class DisassemblyLine 13 | { 14 | public int Ordinal { get; set; } 15 | public Instruction Disassembly { get; set; } 16 | public List Comments { get; set; } 17 | public ExportedFunctionRecord ExportedFunction { get; set; } 18 | public ConcurrentBag BranchToRecords { get; set; } 19 | public ConcurrentBag BranchFromRecords { get; set; } 20 | public List StringReference { get; set; } 21 | public ushort SubroutineID { get; set; } 22 | } 23 | } -------------------------------------------------------------------------------- /MBBSDASM/Dasm/ExportedFunctionRecord.cs: -------------------------------------------------------------------------------- 1 | namespace MBBSDASM.Dasm 2 | { 3 | public class ExportedFunctionRecord 4 | { 5 | public string Name { get; set; } 6 | } 7 | } -------------------------------------------------------------------------------- /MBBSDASM/Dasm/MnemonicGroupings.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using SharpDisasm.Udis86; 4 | 5 | namespace MBBSDASM.Dasm 6 | { 7 | public static class MnemonicGroupings 8 | { 9 | //All Conditional/Unconditional JUMP instructions 10 | public static readonly List JumpGroup = new List() 11 | { 12 | ud_mnemonic_code.UD_Ijmp, 13 | ud_mnemonic_code.UD_Ija, 14 | ud_mnemonic_code.UD_Ijae, 15 | ud_mnemonic_code.UD_Ijb, 16 | ud_mnemonic_code.UD_Ijbe, 17 | ud_mnemonic_code.UD_Ijcxz, 18 | ud_mnemonic_code.UD_Ijecxz, 19 | ud_mnemonic_code.UD_Ijg, 20 | ud_mnemonic_code.UD_Ijge, 21 | ud_mnemonic_code.UD_Ijl, 22 | ud_mnemonic_code.UD_Ijle, 23 | ud_mnemonic_code.UD_Ijno, 24 | ud_mnemonic_code.UD_Ijnp, 25 | ud_mnemonic_code.UD_Ijns, 26 | ud_mnemonic_code.UD_Ijnz, 27 | ud_mnemonic_code.UD_Ijo, 28 | ud_mnemonic_code.UD_Ijp, 29 | ud_mnemonic_code.UD_Ijs, 30 | ud_mnemonic_code.UD_Ijz 31 | }; 32 | 33 | //Instructions used to increment/decrement a value 34 | public static List IncrementDecrementGroup => IncrementGroup.Concat(DecrementGroup).ToList(); 35 | 36 | //Instructions used to increment a value 37 | public static readonly List IncrementGroup = new List() 38 | { 39 | ud_mnemonic_code.UD_Iinc, 40 | ud_mnemonic_code.UD_Iadd 41 | }; 42 | 43 | //Instructions used to decrement a value 44 | public static readonly List DecrementGroup = new List() 45 | { 46 | ud_mnemonic_code.UD_Idec, 47 | ud_mnemonic_code.UD_Isub 48 | }; 49 | 50 | } 51 | } -------------------------------------------------------------------------------- /MBBSDASM/Enums/EnumBranchType.cs: -------------------------------------------------------------------------------- 1 | namespace MBBSDASM.Enums 2 | { 3 | public enum EnumBranchType 4 | { 5 | Call, 6 | CallImport, 7 | Conditional, 8 | Unconditional, 9 | SegAddr, 10 | SegAddrImport 11 | } 12 | } -------------------------------------------------------------------------------- /MBBSDASM/Enums/EnumEntryFlags.cs: -------------------------------------------------------------------------------- 1 | namespace MBBSDASM.Enums 2 | { 3 | public enum EnumEntryFlags 4 | { 5 | Exported = 0x01, 6 | GlobalDataSegments = 0x02 7 | } 8 | } -------------------------------------------------------------------------------- /MBBSDASM/Enums/EnumRecordsFlag.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MBBSDASM.Enums 4 | { 5 | [Flags] 6 | public enum EnumRecordsFlag 7 | { 8 | INTERNALREF = 0x00, 9 | IMPORTORDINAL = 0x01, 10 | IMPORTNAME = 0x02, 11 | TARGET_MASK = 0x03, 12 | ADDITIVE = 0x04 13 | } 14 | } -------------------------------------------------------------------------------- /MBBSDASM/Enums/EnumSegmentFlags.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MBBSDASM.Enums 4 | { 5 | public enum EnumSegmentFlags : ushort 6 | { 7 | Code = 0, 8 | Data = 1, 9 | Iterated = 8, 10 | Fixed = 15, 11 | Movable = 16, 12 | Impure = 31, 13 | Pure = 32, 14 | LoadOnCall = 63, 15 | Preload = 64, 16 | ReadOnly = 127, 17 | ExecuteOnly = 128, 18 | HasRelocationInfo = 256, 19 | HasDebuggingInfo = 512 20 | } 21 | } -------------------------------------------------------------------------------- /MBBSDASM/Logging/CustomLogger.cs: -------------------------------------------------------------------------------- 1 | using NLog; 2 | using NLog.Layouts; 3 | 4 | namespace MBBSDASM.Logging 5 | { 6 | public class CustomLogger : Logger 7 | { 8 | 9 | static CustomLogger() 10 | { 11 | var config = new NLog.Config.LoggingConfiguration(); 12 | 13 | //Setup Console Logging 14 | var logconsole = new NLog.Targets.ConsoleTarget("logconsole") 15 | { 16 | Layout = Layout.FromString("${shortdate}\t${time}\t${message}") 17 | }; 18 | config.AddTarget(logconsole); 19 | config.AddRuleForAllLevels(logconsole); 20 | 21 | LogManager.Configuration = config; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /MBBSDASM/MBBSDASM.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | netcoreapp2.0 5 | 7.2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /MBBSDASM/Program.cs: -------------------------------------------------------------------------------- 1 | using MBBSDASM.UI; 2 | using MBBSDASM.UI.impl; 3 | 4 | namespace MBBSDASM 5 | { 6 | /// 7 | /// Main ConsoleUI Entrypoint 8 | /// 9 | class Program 10 | { 11 | 12 | 13 | private static IUserInterface _userInterface; 14 | 15 | static void Main(string[] args) 16 | { 17 | //Set the interface based on the args passed in 18 | _userInterface = args.Length == 0 ? (IUserInterface) new InteractiveUI() : new ConsoleUI(args); 19 | 20 | _userInterface.Run(); 21 | } 22 | 23 | 24 | } 25 | } -------------------------------------------------------------------------------- /MBBSDASM/Renderer/impl/StringRenderer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using MBBSDASM.Artifacts; 5 | using MBBSDASM.Enums; 6 | 7 | namespace MBBSDASM.Renderer.impl 8 | { 9 | /// 10 | /// String Renderer 11 | /// 12 | /// Used to generate human readable string output of the disassembly. Mainly used to output to a text file 13 | /// 14 | public class StringRenderer 15 | { 16 | /// 17 | /// Input File 18 | /// 19 | private readonly NEFile _inputFile; 20 | 21 | /// 22 | /// Default Constructor 23 | /// 24 | /// 25 | public StringRenderer(NEFile inputFile) 26 | { 27 | _inputFile = inputFile; 28 | } 29 | 30 | /// 31 | /// Renders the Segment Information as readable text 32 | /// 33 | /// 34 | public string RenderSegmentInformation() 35 | { 36 | var output = new StringBuilder(); 37 | 38 | output.AppendLine(";-------------------------------------------"); 39 | output.AppendLine("; Segment Information"); 40 | output.AppendLine($"; Number of Code/Data Segments = {_inputFile.WindowsHeader.SegmentTableEntries}"); 41 | output.AppendLine(";-------------------------------------------"); 42 | foreach (var s in _inputFile.SegmentTable) 43 | { 44 | output.AppendLine( 45 | $"; 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")}"); 46 | } 47 | 48 | return output.ToString(); 49 | } 50 | 51 | /// 52 | /// Renders the Entry Table as readable text 53 | /// 54 | /// 55 | public string RenderEntryTable() 56 | { 57 | var output = new StringBuilder(); 58 | 59 | output.AppendLine(";-------------------------------------------"); 60 | output.AppendLine("; Entry Table Information"); 61 | output.AppendLine($"; Number of Entry Table Functions = {_inputFile.EntryTable.Count}"); 62 | output.AppendLine(";-------------------------------------------"); 63 | foreach (var t in _inputFile.NonResidentNameTable) 64 | { 65 | if (t.IndexIntoEntryTable == 0) 66 | continue; 67 | 68 | output.AppendLine( 69 | $"; 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}"); 70 | } 71 | 72 | foreach (var t in _inputFile.ResidentNameTable) 73 | { 74 | if (t.IndexIntoEntryTable == 0) 75 | continue; 76 | 77 | output.AppendLine( 78 | $"; 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}"); 79 | } 80 | 81 | return output.ToString(); 82 | } 83 | 84 | /// 85 | /// Renders the Disassembly output as readable text 86 | /// 87 | /// 88 | /// 89 | public string RenderDisassembly(bool analysis = false) 90 | { 91 | var output = new StringBuilder(); 92 | 93 | //Write Disassembly to output 94 | foreach (var s in _inputFile.SegmentTable.Where(x => x.Flags.Contains(EnumSegmentFlags.Code))) 95 | { 96 | output.AppendLine(";-------------------------------------------"); 97 | output.AppendLine($"; Start of Code for Segment {s.Ordinal}"); 98 | output.AppendLine("; FILE_OFFSET:SEG_NUM.SEG_OFFSET BYTES DISASSEMBLY"); 99 | output.AppendLine(";-------------------------------------------"); 100 | 101 | //Allows us to line up all the comments in a segment along the same column 102 | var maxDecodeLength = 103 | s.DisassemblyLines.Max(x => 104 | x.Disassembly.ToString().Length + Constants.MAX_INSTRUCTION_LENGTH + 1) + 21; 105 | 106 | //Write each line of the disassembly to the output stream 107 | foreach (var d in s.DisassemblyLines) 108 | { 109 | //Label Entrypoints/Exported Functions 110 | if (d.ExportedFunction != null) 111 | { 112 | d.Comments.Add($"Exported Function: {d.ExportedFunction.Name}"); 113 | } 114 | 115 | //Label Branch Targets 116 | foreach (var b in d.BranchFromRecords) 117 | { 118 | switch (b.BranchType) 119 | { 120 | case EnumBranchType.Call: 121 | d.Comments.Add( 122 | $"Referenced by CALL at address: {b.Segment:0000}.{b.Offset:X4}h {(b.IsRelocation ? "(Relocation)" : string.Empty)}"); 123 | break; 124 | case EnumBranchType.Conditional: 125 | case EnumBranchType.Unconditional: 126 | d.Comments.Add( 127 | $"{(b.BranchType == EnumBranchType.Conditional ? "Conditional" : "Unconditional")} jump from {b.Segment:0000}:{b.Offset:X4}h"); 128 | break; 129 | } 130 | } 131 | 132 | //Label Branch Origins (Relocation) 133 | foreach (var b in d.BranchToRecords.Where(x => 134 | x.IsRelocation && x.BranchType == EnumBranchType.Call)) 135 | d.Comments.Add($"CALL {b.Segment:0000}.{b.Offset:X4}h (Relocation)"); 136 | 137 | //Label Refereces by SEG ADDR (Internal) 138 | foreach (var b in d.BranchToRecords.Where(x => 139 | x.IsRelocation && x.BranchType == EnumBranchType.SegAddr)) 140 | d.Comments.Add($"SEG ADDR of Segment {b.Segment}"); 141 | 142 | //Label String References 143 | if (d.StringReference != null) 144 | foreach (var sr in d.StringReference) 145 | d.Comments.Add($"Possible String reference from SEG {sr.Segment} -> \"{sr.Value}\""); 146 | 147 | //Only label Imports if Analysis is off, because Analysis does much more in-depth labeling 148 | if (!analysis) 149 | { 150 | foreach (var b in d.BranchToRecords?.Where(x => 151 | x.IsRelocation && (x.BranchType == EnumBranchType.CallImport || 152 | x.BranchType == EnumBranchType.SegAddrImport))) 153 | d.Comments.Add( 154 | $"{(b.BranchType == EnumBranchType.CallImport ? "call" : "SEG ADDR of")} {_inputFile.ImportedNameTable.FirstOrDefault(x => x.Ordinal == b.Segment)?.Name}.Ord({b.Offset:X4}h)"); 155 | } 156 | 157 | var sOutputLine = 158 | $"{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}"; 159 | if (d.Comments != null && d.Comments.Count > 0) 160 | { 161 | var newLine = false; 162 | var firstCommentIndex = 0; 163 | foreach (var c in d.Comments) 164 | { 165 | if (!newLine) 166 | { 167 | var l = maxDecodeLength - sOutputLine.Length; 168 | if (l<0) l = 0; 169 | sOutputLine += $"{new string(' ', l)}; {c}"; 170 | 171 | //Set variables to help us keep the following comments lined up with the first one 172 | firstCommentIndex = sOutputLine.IndexOf(';'); 173 | newLine = true; 174 | continue; 175 | } 176 | 177 | sOutputLine += $"\r\n{new string(' ', firstCommentIndex)}; {c}"; 178 | } 179 | } 180 | 181 | output.AppendLine(sOutputLine); 182 | } 183 | 184 | output.AppendLine(); 185 | } 186 | 187 | return output.ToString(); 188 | } 189 | 190 | /// 191 | /// Renders strings in DATA segments as readable text 192 | /// 193 | /// 194 | public string RenderStrings() 195 | { 196 | var output = new StringBuilder(); 197 | 198 | foreach (var seg in _inputFile.SegmentTable.Where(x => 199 | x.Flags.Contains(EnumSegmentFlags.Data) && x.StringRecords?.Count > 0)) 200 | { 201 | output.AppendLine(";-------------------------------------------"); 202 | output.AppendLine($"; Start of Data for Segment {seg.Ordinal}"); 203 | output.AppendLine("; FILE_OFFSET:SEG_NUM.SEG_OFFSET"); 204 | output.AppendLine(";-------------------------------------------"); 205 | foreach (var str in seg.StringRecords) 206 | output.AppendLine( 207 | $"{str.Offset + str.Offset:X8}h:{seg.Ordinal:0000}.{str.Offset:X4}h '{str.Value}'"); 208 | } 209 | 210 | return output.ToString(); 211 | } 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /MBBSDASM/UI/IUserInterface.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace MBBSDASM.UI 7 | { 8 | internal interface IUserInterface 9 | { 10 | void Run(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /MBBSDASM/UI/impl/ConsoleUI.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Text; 5 | using MBBSDASM.Dasm; 6 | using MBBSDASM.Enums; 7 | using MBBSDASM.Logging; 8 | using MBBSDASM.Renderer.impl; 9 | using NLog; 10 | 11 | namespace MBBSDASM.UI.impl 12 | { 13 | /// 14 | /// ConsoleUI Class 15 | /// 16 | /// This class is used when command line switches are passed into MBBSDASM, allowing users 17 | /// to bypass the interactive UI functionality and work strictly with command line arguments 18 | /// 19 | public class ConsoleUI : IUserInterface 20 | { 21 | /// 22 | /// Logger Implementation 23 | /// 24 | protected static readonly Logger _logger = LogManager.GetCurrentClassLogger(typeof(CustomLogger)); 25 | 26 | /// 27 | /// Args passed in via command line 28 | /// 29 | private readonly string[] _args; 30 | 31 | /// 32 | /// Input File 33 | /// Specified by the -i argument 34 | /// 35 | private string _sInputFile = string.Empty; 36 | 37 | /// 38 | /// Output File 39 | /// Specified with the -o argument 40 | /// 41 | private string _sOutputFile = string.Empty; 42 | 43 | /// 44 | /// Minimal Disassembly 45 | /// Specified with the -minimal argument 46 | /// Will only do basic disassembly of opcodes 47 | /// 48 | private bool _bMinimal; 49 | 50 | /// 51 | /// MBBS Analysis Mode 52 | /// Specified with the -analysis argument 53 | /// Will perform in-depth analysis of imported MBBS/WG functions and include detailed information and labeling 54 | /// 55 | private bool _bAnalysis; 56 | 57 | /// 58 | /// Strings Analysis 59 | /// Specified with the -string argument 60 | /// Includes all strings discovered in DATA segments at the end of the disassembly output 61 | /// 62 | private bool _bStrings; 63 | 64 | /// 65 | /// Default Constructor 66 | /// 67 | /// string - Command Line Arguments 68 | public ConsoleUI(string[] args) 69 | { 70 | _args = args; 71 | } 72 | 73 | /// 74 | /// (IUserInterface) Runs the specified User Interface 75 | /// 76 | public void Run() 77 | { 78 | try 79 | { 80 | //Command Line Values 81 | 82 | for (var i = 0; i < _args.Length; i++) 83 | { 84 | switch (_args[i].ToUpper()) 85 | { 86 | case "-I": 87 | _sInputFile = _args[i + 1]; 88 | i++; 89 | break; 90 | case "-O": 91 | _sOutputFile = _args[i + 1]; 92 | i++; 93 | break; 94 | case "-MINIMAL": 95 | _bMinimal = true; 96 | break; 97 | case "-ANALYSIS": 98 | _bAnalysis = true; 99 | break; 100 | case "-STRINGS": 101 | _bStrings = true; 102 | break; 103 | case "-?": 104 | Console.WriteLine("-I -- Input File to DisassembleSegment"); 105 | Console.WriteLine("-O -- Output File for Disassembly (Default ConsoleUI)"); 106 | Console.WriteLine("-MINIMAL -- Minimal Disassembler Output"); 107 | Console.WriteLine( 108 | "-ANALYSIS -- Additional Analysis on Imported Functions (if available)"); 109 | Console.WriteLine( 110 | "-STRINGS -- Output all strings found in DATA segments at end of Disassembly"); 111 | return; 112 | } 113 | } 114 | 115 | //Verify Input File is Valid 116 | if (string.IsNullOrEmpty(_sInputFile) || !File.Exists(_sInputFile)) 117 | throw new Exception("Error: Please specify a valid input file"); 118 | 119 | //Warn of Analysis not being available with minimal output 120 | if (_bMinimal && _bAnalysis) 121 | _logger.Warn( 122 | $"Warning: Analysis Mode unavailable with minimal output option, ignoring"); 123 | 124 | _logger.Info($"Inspecting File: {_sInputFile}"); 125 | 126 | //Perform Disassmebly 127 | var dasm = new Disassembler(_sInputFile); 128 | var inputFile = dasm.Disassemble(_bMinimal); 129 | 130 | //Apply Selected Analysis 131 | if (_bAnalysis) 132 | { 133 | _logger.Info($"Performing Additional Analysis"); 134 | Analysis.MBBS.Analyze(inputFile); 135 | } 136 | 137 | _logger.Info($"Writing Disassembly Output"); 138 | 139 | //Build Final Output 140 | var renderer = new StringRenderer(inputFile); 141 | var output = new StringBuilder(); 142 | output.AppendLine($"; Disassembly of {inputFile.Path}{inputFile.FileName}"); 143 | output.AppendLine($"; Description: {inputFile.NonResidentNameTable[0].Name}"); 144 | output.AppendLine(";"); 145 | 146 | //Render Segment Information to output 147 | output.Append(renderer.RenderSegmentInformation()); 148 | output.Append(renderer.RenderEntryTable()); 149 | output.AppendLine(";"); 150 | output.Append(renderer.RenderDisassembly(_bAnalysis)); 151 | 152 | //Write Strings to Output 153 | if (_bStrings) 154 | { 155 | output.Append(renderer.RenderStrings()); 156 | } 157 | 158 | if (string.IsNullOrEmpty(_sOutputFile)) 159 | { 160 | _logger.Info(output.ToString()); 161 | } 162 | else 163 | { 164 | _logger.Info($"{DateTime.Now} Writing Disassembly to {_sOutputFile}"); 165 | File.WriteAllText(_sOutputFile, output.ToString()); 166 | } 167 | 168 | _logger.Info($"{DateTime.Now} Done!"); 169 | } 170 | catch (Exception e) 171 | { 172 | _logger.Error(e); 173 | _logger.Error($"{DateTime.Now} Fatal Exception -- Exiting"); 174 | } 175 | } 176 | } 177 | } -------------------------------------------------------------------------------- /MBBSDASM/UI/impl/InteractiveUI.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using MBBSDASM.Dasm; 6 | using MBBSDASM.Renderer.impl; 7 | using Terminal.Gui; 8 | 9 | namespace MBBSDASM.UI.impl 10 | { 11 | public class InteractiveUI : IUserInterface 12 | { 13 | private string _selectedFile; 14 | private bool _DisassemblyLoaded; 15 | 16 | private bool _optionMBBSAnalysis; 17 | private bool _optionStrings; 18 | private bool _optionMinimal; 19 | private string _outputFile; 20 | 21 | private MenuBar _topMenuBar; 22 | private Window _mainWindow; 23 | private readonly ProgressBar _progressBar; 24 | private readonly Label _statusLabel; 25 | internal InteractiveUI() 26 | { 27 | Application.Init(); 28 | 29 | //Define Main Window 30 | _mainWindow = new Window(new Rect(0, 1, Application.Top.Frame.Width, Application.Top.Frame.Height - 1), null); 31 | _mainWindow.Add(new Label(0, 0, $"--=[About {Constants.ProgramName}]=--")); 32 | _mainWindow.Add(new Label(0, 1, $"{Constants.ProgramName} is a x86 16-Bit NE Disassembler with advanced analysis for MajorBBS/Worldgroup modules")); 33 | _mainWindow.Add(new Label(0, 3, $"--=[Credits]=--")); 34 | _mainWindow.Add(new Label(0, 4, $"{Constants.ProgramName} is Copyright (c) 2019 Eric Nusbaum and is distributed under the 2-clause \"Simplified BSD License\". ")); 35 | _mainWindow.Add(new Label(0, 5, "SharpDisam is Copyright (c) 2015 Justin Stenning and is distributed under the 2-clause \"Simplified BSD License\". ")); 36 | _mainWindow.Add(new Label(0, 6, "Terminal.Gui is Copyright (c) 2017 Microsoft Corp and is distributed under the MIT License")); 37 | _mainWindow.Add(new Label(0, 8, $"--=[Code]=--")); 38 | _mainWindow.Add(new Label(0, 9, "http://www.github.com/enusbaum/mbbsdasm")); 39 | _progressBar = 40 | new ProgressBar(new Rect(1, Application.Top.Frame.Height - 5, Application.Top.Frame.Width - 4, 1)); 41 | _mainWindow.Add(_progressBar); 42 | _statusLabel = new Label(1, Application.Top.Frame.Height - 7, "Ready!"); 43 | _mainWindow.Add(_statusLabel); 44 | Application.Top.Add(_mainWindow); 45 | 46 | //Draw Menu Items 47 | // Creates a menubar, the item "New" has a help menu. 48 | var menuItems = new List(); 49 | 50 | menuItems.Add( 51 | new MenuBarItem("_File", new MenuItem[] 52 | { 53 | new MenuItem("_Disassemble", "", OpenFile), 54 | new MenuItem("_Exit", "", () => { Application.Top.Running = false; }) 55 | })); 56 | 57 | _topMenuBar = new MenuBar(menuItems.ToArray()); 58 | Application.Top.Add(_topMenuBar); 59 | 60 | } 61 | 62 | public void Run() 63 | { 64 | //Run it 65 | Application.Run(); 66 | } 67 | 68 | private void OpenFile() 69 | { 70 | //Show Open File Dialog 71 | var fOpenDialog = new OpenDialog("Open File for Disassembly", "DisassembleSegment File") 72 | { 73 | CanChooseFiles = true, 74 | AllowsMultipleSelection = false, 75 | CanChooseDirectories = false, 76 | AllowedFileTypes = new[] {"dll", "exe", "DLL", "EXE"} 77 | }; 78 | 79 | Application.Run(fOpenDialog); 80 | 81 | //Get Selected File 82 | _selectedFile = fOpenDialog.FilePaths.FirstOrDefault(); 83 | 84 | //If nothing is selected, bail 85 | if (string.IsNullOrEmpty(_selectedFile)) 86 | return; 87 | 88 | _outputFile = $"{_selectedFile.Substring(0, _selectedFile.Length -3)}asm"; 89 | 90 | //Show Disassembly Options 91 | var analysisCheckBox = new CheckBox(20, 0, "Enhanced MBBS/WG Analysis") {Checked = true}; 92 | var stringsCheckBox = new CheckBox(20, 1, "Process All Strings") { Checked = true }; 93 | var disassemblyRadioGroup = new RadioGroup(0, 0, new NStack.ustring[] {"_Minimal", "_Normal"}, 1); 94 | 95 | var okBtn = new Button("OK", true); 96 | okBtn.Clicked += () => 97 | { 98 | Application.RequestStop(); 99 | _optionMBBSAnalysis = analysisCheckBox.Checked; 100 | _optionStrings = stringsCheckBox.Checked; 101 | _optionMinimal = disassemblyRadioGroup.SelectedItem == 0; 102 | Task.Factory.StartNew(() => DoDisassembly()); 103 | }; 104 | 105 | var cancelBtn = new Button("Cancel", true); 106 | cancelBtn.Clicked += () => { Application.RequestStop (); }; 107 | 108 | var fv = new FrameView(new Rect(0, 5, 55, 6), "Disassembly Options"); 109 | fv.Add(disassemblyRadioGroup,analysisCheckBox,stringsCheckBox); 110 | 111 | var disOptionsDialog = new Dialog("Disassembly Options", 60, 16, new Button[]{okBtn,cancelBtn}); 112 | disOptionsDialog.Add( 113 | new Label(0, 0, "Input File:"), 114 | new TextField(0, 1, 55, _selectedFile), 115 | new Label(0, 2, "Output File:"), 116 | new TextField(0, 3, 55, _outputFile), 117 | fv 118 | ); 119 | 120 | Application.Run(disOptionsDialog); 121 | } 122 | 123 | private void DoDisassembly() 124 | { 125 | using (var dasm = new Disassembler(_selectedFile)) 126 | { 127 | if (File.Exists(_outputFile)) 128 | File.Delete(_outputFile); 129 | 130 | _statusLabel.Text = "Performing Disassembly..."; 131 | var inputFile = dasm.Disassemble(_optionMinimal); 132 | 133 | //Apply Selected Analysis 134 | if (_optionMBBSAnalysis) 135 | { 136 | _statusLabel.Text = "Performing Additional Analysis..."; 137 | Analysis.MBBS.Analyze(inputFile); 138 | } 139 | _progressBar.Fraction = .25f; 140 | 141 | 142 | var _stringRenderer = new StringRenderer(inputFile); 143 | 144 | _statusLabel.Text = "Processing Segment Information..."; 145 | File.AppendAllText(_outputFile, _stringRenderer.RenderSegmentInformation()); 146 | _progressBar.Fraction = .50f; 147 | 148 | 149 | _statusLabel.Text = "Processing Entry Table..."; 150 | File.AppendAllText(_outputFile, _stringRenderer.RenderEntryTable()); 151 | _progressBar.Fraction = .75f; 152 | 153 | 154 | 155 | _statusLabel.Text = "Processing Disassembly..."; 156 | File.AppendAllText(_outputFile, _stringRenderer.RenderDisassembly(_optionMBBSAnalysis)); 157 | _progressBar.Fraction = .85f; 158 | 159 | 160 | if (_optionStrings) 161 | { 162 | _statusLabel.Text = "Processing Strings..."; 163 | File.AppendAllText(_outputFile, _stringRenderer.RenderStrings()); 164 | } 165 | 166 | _statusLabel.Text = "Done!"; 167 | _progressBar.Fraction = 1f; 168 | } 169 | 170 | var d = new Dialog($"Disassembly Complete!", 50, 12); 171 | d.Add(new Label(0, 0, $"Output File: {_outputFile}"), 172 | new Label(0, 1, $"Bytes Written: {new FileInfo(_outputFile).Length}") 173 | ); 174 | var okBtn = new Button("OK", true); 175 | okBtn.Clicked += () => { Application.RequestStop (); }; 176 | d.AddButton(okBtn); 177 | Application.Run(d); 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | ## v1.5 3 | * Minor Bug Fixes 4 | * Added Cross-Plaform Text UI Mode 5 | * Using Terminal.Gui Library to provide interactive text UI 6 | * Accessible by specifying no command line arguments 7 | * Cleaned up Module Definition Auto-Documentation for **GALGSBL** and **MAJORBBS** 8 | * More Liberal String Guessing 9 | * Looks for any matching string in **any** DATA Segment at specified offset 10 | * While less accurate (multiple candidates), prevents misses from incorrect DATA Segment identification 11 | * Updated NuGet Packages 12 | * Added nLog for Console Logging 13 | * Additional Module Definiton Auto-Documentation 14 | * **DOSCALLS** : 10 functions documented of 145 defined 15 | 16 | ## v1.4 17 | * Added bytes to Disassembly output 18 | * Implemented TPL for processing Relocation Records (Thread-Safe) 19 | * ~400% speed up depending on target file and machine SMP capability 20 | * Additional Module Definiton Auto-Documentation 21 | * **MAJORBBS** : 1217 functions documented of 1233 defined 22 | * **GALGSBL** : 97 functions documented of 101 defined 23 | 24 | ## v1.3 25 | * Enhanced FOR loop recognition 26 | * Refactored Disassembled Branch Tracking/Labeling 27 | * Enhanced Strings Extraction/Tracking/Labeling 28 | * Increased performance and accuracy of string reference lookup 29 | * Added -STRINGS command line to output all strings extracted from DATA segments to Disassembly output 30 | * Additional Module Definiton Auto-Documentation 31 | * **MAJORBBS** : 594 functions documented of 1233 defined 32 | * **GALGSBL** : 97 functions documented of 101 defined 33 | * Minor code refactoring 34 | 35 | ## v1.2 36 | * Added Worldgroup 1.0/2.0 for DOS Support 37 | * Added initial FOR loop recognition 38 | * Enhanced String Reference resolution (fewer false positives) 39 | * Additional Module Definiton Auto-Documentation 40 | * **MAJORBBS** : 460 functions documented of 1233 defined 41 | * **GALGSBL** : 97 functions documented of 101 defined 42 | * Support of parsing MZ DOS Header 43 | * Minor code refactoring 44 | 45 | ## v1.1 46 | * Added initial support for variable tracking 47 | * Added Procedure Auto-Identification 48 | * Additional Module Definition Auto-Documentation 49 | * **MAJORBBS**: 391 functions documented of 1210 defined 50 | * **GALGSBL**: 13 functions documented of 101 defined 51 | * Support for identifying multiple possible string references (if n>1) 52 | * Enhanced CALL tracking of INTERNALREF entries in Segment Reolcation Table 53 | * Assembler comments now all ligned up on the same column per Segment 54 | * Fixed references to hex numbers that were missing 'h' identifier 55 | 56 | ## v1.0 57 | * Initial Release -------------------------------------------------------------------------------- /mbbsdasm_ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mbbsemu/MBBSDASM/476a30afbb1542abbef180dda73478e08a8a0900/mbbsdasm_ui.png -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # MBBSDASM 2 | ![](http://forthebadge.com/images/badges/made-with-c-sharp.svg) 3 | ![](http://forthebadge.com/images/badges/60-percent-of-the-time-works-every-time.svg) 4 | 5 | ![MajorBBS Disassembler (MBBSDASM) Preview](./mbbsdasm_ui.png) 6 | 7 | **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. 8 | 9 | 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++. 10 | 11 | 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)]. 12 | 13 | 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. 14 | 15 | # Text UI 16 | 17 | **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. 18 | 19 | # Example Command Line 20 | 21 | **MBBSDASM** supports disassembly of MajorBBS/Worldgroup modules via command line as well. 22 | 23 | An example command line to disassemble a DLL and perform enhanced MajorBBS/Worldgroup Analysis: 24 | ``` 25 | -i c:\bbsv6\example.dll -o c:\bbsv6\output.txt -strings -analysis 26 | ``` 27 | 28 | # Current Features 29 | **MBBSDASM** offers several disassembly/code analysis options that are configurable through the command line. 30 | 31 | #### Minimal Disassembly (-minimal) 32 | Minimal will output the disassembled x86 code segments labeled with SEGMENT:OFFSET with no additional analysis. 33 | 34 | ```asm 35 | 00000C68h:0002.0068h 83C408 add sp, 0x8 36 | 00000C6Bh:0002.006Bh 68FF7F push 0x7fff 37 | 00000C6Eh:0002.006Eh 680180 push 0x8001 38 | 00000C71h:0002.0071h 6A07 push 0x7 39 | 00000C73h:0002.0073h 9AFFFF0000 call word 0x0:0xffff 40 | 00000C78h:0002.0078h 83C406 add sp, 0x6 41 | 00000C7Bh:0002.007Bh A3EC02 mov [0x2ec], ax 42 | 00000C7Eh:0002.007Eh 6A08 push 0x8 43 | 00000C80h:0002.0080h 9AFFFF0000 call word 0x0:0xffff 44 | 00000C85h:0002.0085h 59 pop cx 45 | 00000C86h:0002.0086h 89160403 mov [0x304], dx 46 | 00000C8Ah:0002.008Ah A30203 mov [0x302], ax 47 | ``` 48 | #### Normal Disassembly (default) 49 | Normal will output the disassembled x86 code segments labeled with SEGMENT:OFFSET as well as processing: 50 | * Processing Segment Relocation Table Entries 51 | * Resolve External References 52 | * String Reference Resolution (best guess) 53 | * Identify and Label Conditional/Unconditional Jumps as well as Function Calls 54 | ```asm 55 | 00000C68h:0002.0068h 83C408 add sp, 0x8 56 | 00000C6Bh:0002.006Bh 68FF7F push 0x7fff 57 | 00000C6Eh:0002.006Eh 680180 push 0x8001 58 | 00000C71h:0002.0071h 6A07 push 0x7 59 | 00000C73h:0002.0073h 9AFFFF0000 call word 0x0:0xffff ; call MAJORBBS.Ord(01B9h) 60 | 00000C78h:0002.0078h 83C406 add sp, 0x6 61 | 00000C7Bh:0002.007Bh A3EC02 mov [0x2ec], ax 62 | 00000C7Eh:0002.007Eh 6A08 push 0x8 63 | 00000C80h:0002.0080h 9AFFFF0000 call word 0x0:0xffff ; call MAJORBBS.Ord(0236h) 64 | 00000C85h:0002.0085h 59 pop cx 65 | 00000C86h:0002.0086h 89160403 mov [0x304], dx 66 | 00000C8Ah:0002.008Ah A30203 mov [0x302], ax 67 | ``` 68 | 69 | #### Enhanced Analysis Mode (-analysis) 70 | Enhanced Analysis mode enables **MBBSDASM** to provide additional detailed analysis of Major BBS & Worldgroup Modules/DLL's with information provided from the Major BBS 6.25 Software Development Kit as well as GALACTICOMM's Developer's Guide for The Major BBS 6.2 [[link](http://software.bbsdocumentary.com/IBM/WINDOWS/MAJORBBS/devguide.pdf)] 71 | 72 | Additional disassembly analysis includes: 73 | * Automatic Documentation on a large portion of the most MAJORBBS & GALGSBL functions 74 | * Provide Method Signatures in place of the external module calls 75 | * Reverse Engineer and rebuild method signatures with the actual input values built from the x86 Assembly 76 | * Identify FOR loops generated by the Borland Turbo C++ compiler and label them 77 | * Basic variable tracking and labeling 78 | 79 | The Enhanced Analysis mode can be extended through pull requests by adding Module Definition JSON files for known libraries. 80 | ```asm 81 | 00000C68h:0002.0068h 83C408 add sp, 0x8 82 | 00000C6Bh:0002.006Bh 68FF7F push 0x7fff 83 | 00000C6Eh:0002.006Eh 680180 push 0x8001 84 | 00000C71h:0002.0071h 6A07 push 0x7 85 | 00000C73h:0002.0073h 9AFFFF0000 call word 0x0:0xffff ; int numopt(int msgnum,int floor,int ceiling); 86 | ; Resolved Signature: numopt(7, 32769, 32767) 87 | ; Retrieves a numeric option from MCV file 88 | 00000C78h:0002.0078h 83C406 add sp, 0x6 89 | 00000C7Bh:0002.007Bh A3EC02 mov [0x2ec], ax ; Return value saved to 0x2ECh 90 | 00000C7Eh:0002.007Eh 6A08 push 0x8 91 | 00000C80h:0002.0080h 9AFFFF0000 call word 0x0:0xffff ; char *string=stgopt(int msgnum); 92 | ; Resolved Signature: char *string=stgopt(8); 93 | ; Gets a string from an MCV file 94 | 00000C85h:0002.0085h 59 pop cx 95 | 00000C86h:0002.0086h 89160403 mov [0x304], dx 96 | 00000C8Ah:0002.008Ah A30203 mov [0x302], ax ; Return value saved to 0x302h 97 | ; AX holds pointer, DX holds size in return from function 98 | ``` 99 | # What's Next 100 | * Enhance MBBS Analysis 101 | * Enhanced Variable Labeling and Tracking 102 | * Enhanced Auto-Documentation of GALGSBL and MAJORBBS imported function 103 | * Add support for DOS MZ EXE files 104 | * This would allow disassembly of the MajorBBS/WG EXE files 105 | * Add support for Worldgroup 3.0+ 106 | * Requires additional support for disassembly of 32-bit PE format EXE/DLL files 107 | * The best tool for this is probably IDA Freeware, which disassembles PE files with ease 108 | 109 | # Using Hex-Rays IDA for Disassembly? 110 | Check out [MBBSDASM.IDA](https://github.com/enusbaum/MBBSDASM.IDA), which is a collection of IDS/IDT files that allow Hex-Rays IDA to properly identify/comment imports for both MAJORBBS and GALGSBL. 111 | 112 | While **MBBSDASM.IDA** lacks some of the advanced analysis features that **MBBSDASM** provides, I know some folks prefer to use Hex-Rays IDA for their disassembly/reverse engineering. 113 | 114 | # Contribute 115 | 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: 116 | 117 | * The MajorBBS/Worldgroup Development Documentation (beyond already available SDK docs) 118 | * Unreleased/publically unavailable source code for commercial modules 119 | 120 | Any information sent my way will be kept **strictly confidential** and will only be used as a point of reference for enhancing this research project. My goal here is to not let the past just rot away in ZIP files but give people a chance to learn how systems like The MajorBBS and Worldgroup worked. 121 | 122 | Additionally, please feel free to submit pull requests with enhancements and bug reports with any issues you might be experiencing! 123 | 124 | # Thanks 125 | 126 | The project makes use of [SharpDiasm](https://github.com/spazzarama/SharpDisasm) to do the actual Disassmebly of the Code Segments into 16-bit x86 Assembly Language. 127 | 128 | 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/)] 129 | 130 | # License / Copyright 131 | 132 | MBBSDASM is Copyright (c) 2017 Eric Nusbaum and is distributed under the 2-clause "Simplified BSD License". 133 | 134 | SharpDisam is Copyright (c) 2015 Justin Stenning and is distributed under the 2-clause "Simplified BSD License". 135 | 136 | Terminal.Gui is Copyright (c) 2017 Microsoft Corp and is distributed under the MIT License 137 | 138 | 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". 139 | --------------------------------------------------------------------------------