Encoding Binary Data into Batch Code

I wrote this up at the beginning of 2002 but did not post it for a few reasons.. the main reason being the BASM dos compiler was replaced with a Windows-based version. Tokiwa disappeared as well, or at least was hiding the last time I looked. ASIC is still around in archives and the Moonrock page works, at least there are a couple tiny-com compilers the modern simple problem-solver can use. Recently I've noticed a renewed interest in how ascii assembler works, and even if my application of it is a bit extreme this document provides a decent overview and links to tools. One usually does not write ascii-assembler by hand if it can possibly be helped, rather encoding/decoding techniques are used to translate existing machine code into batch-legal code. NT-like OS's redefine what is legal, be very careful using some of the raw translaters since they output code NT/XP/etc will not interpret correctly, when machine code is involved the results can be unpredictable, why I adopted a more complicated but compatible approach. If I was smart enough to write a compatible one-step encoder I would but I'm mostly batch and high-level, machine code isn't my thing unless it's for a PIC.

Standard disclaimers, not responsible if you fry your computer using these techniques and programs. Machine code can do just about anything, including horribly crash the computer the errant code is running on. Who knows my code might have a bug, all I can say is it works on my Windows 95 machine, and the output runs under XP. The batch code will likely need adapting to your environment if used, especially CONV2BAT since it is so compiler-specific. If you need to do stuff like this you probably have other specific needs, consider crafting your own solution that best suits your needs, taking code here and elsewhere as examples.

Contents:

Many times when programming in batch it is necessary to use binary data of one kind or another, be it a helper utility or simply a file without a return at the end. For the purpose of this article, binary means a sequence of bytes where the bytes can take on any value without restriction and can represent a machine code program, a gif file or any other arbitrary file. The important thing is the sequence has to remain exact, there can be no system-created changes or malfunction will likely occur. MsDos Batch is an ascii-based language which can only write files that end in CrLf (Ascii 13, 10), and cannot directly write certain characters that mean other things to the system unless specific steps are taken to ensure syntactic legality. Most of the time batch files are written to use already-existing binary files, however there are times when a single file solution would be better, particularly when distributing a solution for others to use but also to avoid accumulating extra files for trivial purposes.

Just like any programming tool there are advantages and drawbacks. Advantages: Binary-to-batch (Bin-batch) encoding techniques enable batch programmers to recreate any small file as needed. Bin-batch tools let the batch programmer write complex functions in a true HLL (high-level language) then convert it to batch code, rather than relying on unreliable version-specific batch hacks (if the function is even expressible in batch). Carefully constructed bin-batch code can run on all known dos platforms from the earliest MsDos with echo and redirection to Windows XP so long as the underlying app is compatible. Bin-batch text cannot be modified by com-infecting viruses and is safer to redistribute than raw com files, provided of course the files are clean to begin with. Any change is visible and changes the way the encoded ascii "reads" (phrases that randomly appear in a particular routine's encoded text).

Disadvantages: Encoding obscures the original content, and people are fearful of code that cannot be discerned. Depending on the context, bin-batch is no more and arguably less dangerous than simply distributing the binaries with the batch, however in an area where binaries generally are not allowed such as a discussion newsgroup. Posting bin-batch code can get you majorly flamed, in alt.msdos.batch only very small binaries written by known authors are tolerated. Bin-batch code is always a bit slower than merely accessing already-existing binaries, and it adds another layer of possible incompatibility. Creating executable binaries on-the-fly might be against company policy or require specific approval. Some lockdown-style anti-virus (AV) programs prevent the creation of executable code altogether when enabled, other AV programs slow down bin-batches because they have to scan each line of the binary file as it is written to disk. Using a separate decoder greatly reduces scanning overhead by creating the binary in one scanned step (only the decoder has to be scanned line-by-line), or write the com to a file with a non-executable extension then rename before running.

Binary to Batch Encoding Techniques

The old MsDos debug command is probably the most common method of encoding binary data for batch, the command is available on most systems but it is possible the user removed or renamed it, and if booting from floppy you'll have to make sure the debug command is available by putting it on the disk. Debug more than triples the unencoded file size so is best used for very small routines. Numerous debug-encoding utilities are available.

It is possible to write small machine code programs that consist entirely of batch-legal characters and echo it directly to a com file for execution, this method is handy for echoing characters without a CrLf, inputting data and other simple functions that otherwise cannot easily be done in batch. Writing such "ascii assembler" code though is a tedious affair even when one is familiar with assembly, often the results contain more code for patching incompatible bytes than for the task itself but still the utilities rarely exceed a few lines of ascii. Herbert Kleebauer and Laura Fairhead are established experts in this area and have published many useful ascii machine code batch routines on the alt.msdos.batch newsgroup over the years.

Among the most interesting of bin-batch utilities are com-file encoders. After being echoed to disk the com file consists of a decoder consisting entirely of batch-echoable ascii characters followed by an ascii-encoded representation of the original machine code program. When the com runs, the decoder decodes the original code and executes it, if all goes well the original code is not aware of the process. Herbert Kleebauer's Convert.bat program requires that the machine code be assembled at an offset to make room for the decoder. Laura Fairhead's Cm3.com program goes a step further and appends a copy routine, permitting it to encode standard com files.

The method I settled on uses a CM3-encoded decoder that decodes data from one redirected file to another, allowing any small file to be encoded and recreated exactly regardless of its contents. There is more overhead running machine code programs this way but the same decoder can be used multiple times, and the decoding process doesn't produce as much AV scanner activity since only the decoder itself is written line-by-line to a com file. A major reason for going with a separate decoder was to be able to control the encoding process myself, CM3 sometimes produced NT-incompatible output on the 2nd line.

Binary Batch Echo Rules

Tricky stuff! Cross-compatibility information can be hard to come by so there may be errors or omissions in the above. Best to keep the encoding method simple as possible to avoid errors with some platforms.

A Universal Batch-Compatible Encoder and Decoder

For my encoding scheme I chose to reduce the range of characters from 256 to 64 starting at ascii 48 ("0") to pack 1.5 bytes per pair of encoded characters. To assure batch compatibility ascii 58 thru 64 (":" to "@") and ascii 94 thru 96 ("^" to "`") are skipped. This encoding scheme doesn't reach the problem ascii 124 pipe character. To create my solution I started with another binary-ascii encoder/decoder pair named zencode/zdecode by Tenie Remmel and replaced the 13-bit (91 position) binary fetch code with by own 12-bit code, along with changes to offset and skip as needed. I further modified the decoder so that it outputs a single RET instruction should the decoded size not match the size recorded at the beginning of the encoded data. Not as good as a checksum but at least provides some protection. The decoder is encoded using CM3, for batch use and distribution the encoder can be encoded by itself. Note that my encoder converts to raw ascii, echo redirection to a file must be added to include within a batch.

Here is a batch containing the decoder which I call "DecBin" and used to decode the encoder I call "EncBin"...

@echo off
set dec=decbin.com
set enc=encbin.com
set tf=encbin.tmp
::---------------------
:: CM3-encoded DecBin decoder...
ECHO:`h}aXP5y`P]4nP_XW(F4(F6(F=(FF)FH(FL(Fe(FR0FTs*}`A?+,>%dec%
ECHO:fkOU):G*@Crv,*t$HU[rlf~#IubfRfXf(V#fj}fX4{PY$@fPfZsZ$:NvN$>>%dec%
ECHO:9AyroNB-)dOKwK0rRkfTbi)ws_~[[q9wE'sqlu1sY*Bsfe=@ziNS1a)88e>>%dec%
ECHO:f9RTL)9Z{3INBD?o6@MDLO{Zz4Q23E-'09NX9@Vz(42A7c8zMS:u$w6k5Q>>%dec%
ECHO:N,h:le)~gF?tutTyxoe5UiIdtn';0rJ1q:{7;lAl']y:yTjZBbOo?QRIdN>>%dec%
ECHO:$Bp@P/nAp_r0*4f'XcF4q3o?$_t5lx$Q-OxSfUNQ__Gd~$Q-Oxgkx=LGHU>>%dec%
ECHO:S)$C6P8#>>%dec%
::---------------------
:: the EncBin encoder, encodes stdin to stdout, encoded by itself...
ECHO:AALIxnCmeRf0\Uf0pWjXYBlxr0MyG02u022nc1Z5Z0r4G2ldMAj[8F34dd>%tf%
ECHO:Z1Z0r4G2ld6Aj[8F34Ed3EmbG02lJpNl0jjjCt9v0407ZvjtS3I0j7rvLv>>%tf%
ECHO:G203l0wUDv20F42eD3ZujTS6fmprbD2e4uwp39gwYdfDfAdng0f1f0ZF2t>>%tf%
ECHO:04bemDCXj0C0LHtA2701ZsG0SFdfW]6630Jf36S6W1f0rJ2fMfQYW1YAoO>>%tf%
ECHO:EAt0y[36S6W1[8LRi3}>>%tf%
::---------------------
:: decode encoder...
%dec% < %tf% > %enc%
if errorlevel 1 echo error decoding binary
if not errorlevel 1 echo Created encoder in %enc%
echo Created decoder in %dec%
del %tf%

To encode a binary manually using encbin.com run: encbin < binary > file then edit file and add "echo " or "echo:" at the beginning and redirection to the desired file at the end of each line. To do this automatically as part of a binary-to-batch batch presents a bit of difficulty, either the symmetry and simplicity of the encoder/decoder pair has to be broken by including echo and file redirection in the encoder output, or the problem can be simply solved using encoded HLL.

Com-file Compilers and Conv2Bat.bat

Several BASIC-like compilers produce com files small enough to encode for batch usage, just about all of them can be found on the Basm page (see links). Each of these has its own particular syntax which has to be memorized, at least where in the doc file the info can be found. For the most part it's like regular basic but expressions must be highly simplified and some of the commands have slightly different syntax. Basm works the best for me but each have strengths and weaknesses... Basm and Moonrock produce assembly source which can be used for hand-tweeking the compiler output. Basm provides easy access to stdin and stdout and produces simple, stable code that should run on any platform that supports dos (I have no info about NT/2000/XP compatibility, I'd guess probably ok). Moonrock worked with everything I wrote with it but the bounce.moo demo crashes and takes down Windows on my machine. Asic is the most complete but the com file often is inflated in size and must be compressed using PkLite or equivalent. Tokiwa does floating point math but I don't know much about it.

The compiler binaries and batch files need to be in path directories so they can be operated by issuing simple dos commands in the directory the source is in. Usually linking batches (@c:\path\prog.exe %1 %2 etc) in a path dir are ok for compilers but some command line parms cannot be passed (specifically asm's ";" no-prompt option) and anything that uses redirection to specify input or output must be available in binary form. On my system I have link batches for asicc.exe, mrc.exe and tbc.com, with basm.exe, asm.exe, freelink.exe and moonrock's mrlink.com in a path directory. In its default configuration Moonrock uses A86 to assemble the output, to use asm unzip mrcasm.cfg from config.zip and rename to mrc.cfg, and make sure everything listed is on the path. Of course it's not necessary to install and learn all of these, to use Basm all you need is Basm, Asm and Freelink.

Conv2bat.bat combines compiling and converting to batch into one step usable from Windows' right-click menu system. Asicc is called to compile asi files, basm/asm/freelink for bas files, Moonrock's mrc for moo files and Tokiwa's tbc for tok files. Notepad or any other text editor can be used to create the source, keep shortcuts to the docs handy. After compiling the source, Conv2bat encodes it into batch form, leaving a .com file and a .bat file with the same base name as the original source file. A batch-commented version of the original source is included in the output batch to help remember what the binary does. If the specified file's extension isn't a supported source file then Conv2Bat encodes it directly to batch, edit the output batch code as needed to use the binary.

All that sounds easy enough... for a real language! Problems... If batch is used as a drag and drop target the current directory is not set to where the file is, rather it must be determined from the passed filename. Essentially the extension must be separated from the rest of the filename but doing anything besides adding strings together is outside the scope of batch. After encoding with EncBin, "ECHO:" and ">>file" (one > on the first line, file is variable) needs to be added to each line to create runnable batch code. To include source code, "::" needs to be inserted before each line. A batch that does all that using only standard commands (if you count debug) is possible, a QBasic script would work, but the former would take quite a bit of hack-style programming, and QBasic isn't installed on every system (and flashes). Binary batch blocks written in Basm easily solved all these problems.

NT compatibility should be reasonably good but NT's find command doesn't return an errorlevel, the part that documents whether or not the com file was compressed with PkLite won't work properly but code output is not affected. Getting away from incompatibilities like this is a major reason for using binary batch (I just hope it doesn't create new incompatibilities), but since this one is minor so I don't think I'll fix unless I get an NT-like system. There may be other incompatibilities I don't know about, all I have is 95 and the Win95Cmd Cmd emulator to test with.

Here is Conv2Bat... (skip to source or more docs)

:: CONV2BAT.BAT - this batch converts HLL source or an
:: existing file (<40K) into equivalent batch code.
:: Required components... (assumed to be on the path)
::  for ASIC (asi): ASICC
::  for BASM (bas): BASM ASM FREELINK
::  for Moonrock (moo): MRC (plus asm/link in mrc.cfg)
::  for Tokiwa (tok): TBC
:: Compresses com with PKLITE, uses Dos6+ FC and FIND to
:: determine if compressed for comments. Disable below.
:: The temp variable must point to a writable directory.
:: This batch is designed to operate by drag-drop, from
:: Windows' right-click menus, or from a dos prompt.
:: Usage: CONV2BAT [path\]filename.ext
:: If extension is asi bas moo or tok then produces
:: filename.bat containing batch-encoded binary with
:: attached source, in addition to compiler output.
:: If ext not supported then encodes binary to batch.
:: Tested in Win95, should be dos and NT-tolerent except
:: for compression test (see comments) but maybe more.
:: This is free software and comes with no guarantee,
:: test thoroughly before distributing the output code.
:: Last major mod 12/08/01, last mod 1/03/02
:: (C) Copyright 2002 Terry Newton
:: EncBin encoder, DecBin decoder and Basm helpers
:: are public domain, use as you wish.
@echo off
if .%1==.Shell goto %1
if not .%1==. if exist %1 goto fileexists
echo  Compiles HLL source and converts to batch
echo  Usage: CONV2BAT [path\]filename.ext
echo  Compiles .asi .bas .moo and .tok files
echo  encodes other types directly to batch
goto end
:fileexists
:: shell with lots of environment space
%comspec% /e:30000 /c %0 Shell %1
goto end
:Shell
::**** options, edit as needed...
:: usepklite=yes to compress with pklite
set usepklite=yes
:: usetempdir=yes to use temp dir in output code
set usetempdir=no
:: decname=filename used for decoder
set decname=_d.com
:: tempname=filename used for temp file
set tempname=_t.tmp
:: extension to output batch code to
set outputext=bat
:: callconv2cfg=yes to put variables in conv2cfg.bat
set callconv2cfg=no
:: temp filenames...
set dec=%temp%.\_decbin.com
set enc=%temp%.\_encbin.com
set file2bat=%temp%.\_2bat.com
set cmntsrc=%temp%.\_cmntsrc.com
set sepname=%temp%.\_sepname.com
set replace=%temp%.\_replace.com
set tf=%temp%.\_tf.tmp
set tf1=%temp%.\_tf1.tmp
set tf2=%temp%.\_tf2.tmp
:: override variables if configured...
if .%callconv2cfg%==.yes call conv2cfg.bat
:: double check...
set xyz=0123456789012345678901234567890123456789
if not .%xyz%==.0123456789012345678901234567890123456789 goto end
set xyz=
echo.
echo  **** CONV2BAT **** 12/8/01 WTN
echo  Checking file support...
::-----------------------
:: CM3-encoded DecBin decoder...
ECHO:`h}aXP5y`P]4nP_XW(F4(F6(F=(FF)FH(FL(Fe(FR0FTs*}`A?+,>%dec%
ECHO:fkOU):G*@Crv,*t$HU[rlf~#IubfRfXf(V#fj}fX4{PY$@fPfZsZ$:NvN$>>%dec%
ECHO:9AyroNB-)dOKwK0rRkfTbi)ws_~[[q9wE'sqlu1sY*Bsfe=@ziNS1a)88e>>%dec%
ECHO:f9RTL)9Z{3INBD?o6@MDLO{Zz4Q23E-'09NX9@Vz(42A7c8zMS:u$w6k5Q>>%dec%
ECHO:N,h:le)~gF?tutTyxoe5UiIdtn';0rJ1q:{7;lAl']y:yTjZBbOo?QRIdN>>%dec%
ECHO:$Bp@P/nAp_r0*4f'XcF4q3o?$_t5lx$Q-OxSfUNQ__Gd~$Q-Oxgkx=LGHU>>%dec%
ECHO:S)$C6P8#>>%dec%
::---- EncBin-encoded binary ----
:: sepname.com made with BASM ASM FREELINK
ECHO:AEPPr64U2y00tD61O0AAj7CDXn28A5S5D7JeRkl5Ll00xG47uvgmv5gl[5>%tf%
ECHO:gGC0v]U[2uY7213301EBu1Je13dnJDrX5OIZO5QZO5ibr1Vbj3AxS5Ayv6>>%tf%
ECHO:Zb02ibf3Axv6Rbf2RbC2Ayv6Jbf3abb3Axv62yK6ebr12rK6ZbC2ib42Ay>>%tf%
ECHO:v6ab[3Axv6Eyj5Rb42Dn3e0J]xN53b8l3b6l3bB\2jG0gC7DB7n6j50037>>%tf%
ECHO:v6j5000mF0cdr5ilW6tfC106Ld2ZNH37C6n5003yC6n5EXn5Ou3Hr5S3Fe>>%tf%
ECHO:IGebC3Axv6EXn56u00qbC3ybb1EyG7qb83ExG7Myn5ebb1Dn3e07]XNJ2Z>>%tf%
ECHO:ND2xT32yNN3b6M1nf3r76GeJC5eFf5thpXj5Ou3Fv5S3Ve2GR73F0500Rb>>%tf%
ECHO:r2]xn5Iy[7Vb81ebn2Axv6Myr7ib41ubj2hxn5QyC8yb01t3FF451yf3s6>>%tf%
ECHO:EGF0O33FK53yFyeTW5tfv2vpNT1bRl3b933b4Z2yV\3bAX0mF006NH0ev6>>%tf%
ECHO:j5pZn5abW2RyFTW5RlW6rrW2r0v1fpW8sxC2WxND01Dy3e1I]XND2ZNT3b>>%tf%
ECHO:9Z3ynpn59bv6ebv1ebn0Iy[7ZbK20mlZLd2xV\3b2N03b6O5pZn50mj3n6>>%tf%
ECHO:n5Oe3DC5eTW5rhv2vpNT1bV\3b7T3b2\2yV\3b8Z1bNV3b8s1bUK3b8m1b>>%tf%
ECHO:LR3b8g1bNd3b8a1bV\3b8X1bLR3b8R1bNo3b8L1bWp3b8F1bLR3b892n9C>>%tf%
ECHO:F07DAn83Fy3D4me0Z23xlZLI2Au6y0IZK5i3WMLI3x7AcKb5Zjuxlnn2jG>>%tf%
ECHO:m3xfdd30coy3PHLNtBvla0iymyylUhyHg9Z1sVLP73LNPMLNXUuXiyZOXU>>%tf%
ECHO:uRiyZ8s1LVf3yvRmA[DPlxPG2yNy3byRy3TGLMmvdhHo0AN0VrwULOi3Zr>>%tf%
ECHO:6sK0ivZqlfD1Pl0vwQS20ftdBYy3ebaygGZ8]3v0gxEBOUK5IXK5tBDIr5>>%tf%
ECHO:46w8I33y1yC2Fy2tK0Bqr37rqa2r2j2ElnnFjGSv1nW5f0trZ0Jvb]p857>>%tf%
ECHO:ZYv7WVK186K5y3hXW6s3uyhXW6ryr0gGv6hXW6rnuyhXW6r1C0lyQX3bva>>%tf%
ECHO:xxQX2yPG3bvUxxPG3bvWxyPG2BF83rpA603u9yCBF0M819DFZsn0Vhy3hX>>%tf%
ECHO:W6uruxhXC6F9l0CWTo16D1ZsLHVn6sK02yK6Fvv]E[m786K5013yG6K5t3>>%tf%
ECHO:DKb55ov4vELK2tLO2nn9jXtB5Ib5ZJexlBGUK5EAyxFEFRAn3DCGkLlBtB>>%tf%
ECHO:Hp2yPG2BPE3vRmA[0ml8n5N2041LlBt3Bf0ma0X6ixXpW4t8ix9606I9s6>>%tf%
ECHO:vw001wW6f1[0X6yxgGO6j5f0Xpv4gGC61puxIs1H0esEyvRmA[0ml8f5Z\>>%tf%
ECHO:9Tj4v0iyC5F0M933xxv5mbFxF3LGmbuyyvgGW6K3ujHPRmA[N600N3MBif>>%tf%
ECHO:XpW4r\ixZ86u00QBK42nJDnXN202014000070DGA8D0A07000000000000>>%tf%
ECHO:0000000000000000000000000000000000000000000000000000000000>>%tf%
ECHO:0000000000000000000000000000000000000000000000000000000000>>%tf%
ECHO:00000000whOW5jPl1g0000000000j0K00h000000001mH\SW1]TXXnOwC0>>%tf%
ECHO:T\1n9]5YPmo\C0LmPnOWP\HrSwn0}>>%tf%
:: BASM source...
:: rem separate specified name into fpath,fbase,fext
:: rem outputs sets to stdout
:: $com
:: a$=command$:a$=trim$(a$):a$=lcase$(a$)
:: if a$="" then
::  print "no parm":end
:: endif
:: rem loop through string, mark last \ and .
:: slash=0:dot=0:ilen=len(a$)
:: for i=1 to ilen
::  c$=mid$(a$,i,1):if c$="\" then slash=i
::  if c$="." then dot=i
:: next i
:: rem not an extension if \ after .
:: if slash>dot then dot=0
:: fpath$="":fbase$=a$:fext$=""
:: if dot>1 then
::  d=dot-1:fbase$=left$(a$,d)
::  d=ilen-dot:fext$=right$(a$,d)
:: endif
:: if slash>1 then
::  rem includes trailing \
::  d=slash:fpath$=left$(a$,d)
::  d=len(fbase$):d=d-slash
::  fbase$=right$(fbase$,d)
:: endif
:: output "set fpath=";fpath$
:: output "set fbase=";fbase$
:: output "set fext=";fext$
::-------------------------------
:: decode and use _sepname.com to parse input (in %2)
%dec% < %tf% > %sepname%
if errorlevel 1 goto binerror
%sepname% %2 > %temp%.\_sep_tmp.bat
call %temp%.\_sep_tmp.bat
del %temp%.\_sep_tmp.bat
if .%fpath%==. goto incurrent
:: change to source directory
%fpath%
cd %fpath%
:incurrent
if exist %fbase%.%fext% goto curdirok
echo  Could not change to directory
goto cleanup
:curdirok
:: *******
:: ******* compiler support, edit as needed
:: *******
if .%fext%==.asi goto asifile
if .%fext%==.bas goto basfile
if .%fext%==.moo goto moofile
if .%fext%==.tok goto tokfile
echo  Unknown filetype, encoding as-is
set compname=
set madewith=????
set binfile=%fbase%.%fext%
goto conv2batch
:asifile
set compname=ASIC
set madewith=ASICC
if exist %fbase%.com del %fbase%.com
call asicc %fbase%.asi C
goto checkcom
:basfile
set compname=BASM
set madewith=BASM ASM FREELINK
if exist %fbase%.com del %fbase%.com
call basm %fbase%.bas
if exist %fbase%.asm call asm %fbase%.asm ;
if exist %fbase%.obj call freelink /c %fbase%.obj
goto checkcom
:moofile
set compname=Moonrock
set madewith=MRC ASM MRLINK
if exist %fbase%.com del %fbase%.com
call mrc %fbase%.moo
goto checkcom
:tokfile
set compname=Tokiwa
set madewith=TBC
if exist %fbase%.com del %fbase%.com
call tbc /c %fbase%.tok
goto checkcom
:: ******* end compiler commands
:checkcom
set binfile=%fbase%.com
if exist %binfile% goto comexists
echo.
echo  =======================================
echo    Compile failed, debug and try again
echo  =======================================
echo.
goto cleanup
:comexists
if exist %fbase%.obj del %fbase%.obj
:: try to pklite it (if option enabled)
if not .%usepklite%==.yes goto conv2batch
copy %binfile% %tf1% >nul
call pklite %binfile%
fc /b %binfile% %tf1% > %tf2%
find "00" < %tf2% > nul
::reset var for docs if not changed by pklite...
::note--not NT compatible here, PkLite status won't
::be properly indicated in the output batch comments
if errorlevel 1 set usepklite=no
del %tf1%
del %tf2%
:conv2batch
echo.
echo  Converting to batch code...
::---------------------
:: the EncBin encoder, encodes stdin to stdout, encoded by itself...
ECHO:AALIxnCmeRf0\Uf0pWjXYBlxr0MyG02u022nc1Z5Z0r4G2ldMAj[8F34dd>%tf%
ECHO:Z1Z0r4G2ld6Aj[8F34Ed3EmbG02lJpNl0jjjCt9v0407ZvjtS3I0j7rvLv>>%tf%
ECHO:G203l0wUDv20F42eD3ZujTS6fmprbD2e4uwp39gwYdfDfAdng0f1f0ZF2t>>%tf%
ECHO:04bemDCXj0C0LHtA2701ZsG0SFdfW]6630Jf36S6W1f0rJ2fMfQYW1YAoO>>%tf%
ECHO:EAt0y[36S6W1[8LRi3}>>%tf%
::---------------------
:: decode encoder...
%dec% < %tf% > %enc%
if errorlevel 1 goto binerror
::---- EncBin-encoded binary ----
:: 2bat.com made with BASM ASM FREELINK
ECHO:AFDJr64U2y00tD61O0AAj7CDXn28p5j5D7JeRkl5ND00xG47uviEv5iD[5>%tf%
ECHO:gGC0v]U[2ues213301EBu1Je13dnJDrX5OhZf5pZf5JbC2qb04pxj56yb8>>%tf%
ECHO:ybC27b046xb8ubv2qbO26yb8ebv3ybr36xb8qbf2ibK26yb8Zbr36xb8ty>>%tf%
ECHO:46RbK2Do3c7j0mF0e5v5hXr8fYW6sQC206N52ZO[23Gx86v0S37cn13bf3>>%tf%
ECHO:RyC[W6QXW8s7W3rOv2fPW9t\W3tQr3fPW9sbW2r6v2fPW9tJC3F0MZj56x>>%tf%
ECHO:b8Fbj1O335C5b]C6F0O30]f686O[2ZO]3bDc3yOp865bb8Zb437bn16y[9>>%tf%
ECHO:ubK30mlZN52y]H2xOY3b7U03K6j5IZ86t3C[061y[3sLW1rnv3vpO[1b]H>>%tf%
ECHO:3bBB3b6S2yb93bCc3bCU2xb93b7f3b6A2yb93bCN0mF0e5r5hHW9s7C106>>%tf%
ECHO:N52ZO]0mF006O]0eG686QZ86qbr2RyC]W6QHW9tSW2sLv1hHW9uYb2fPf9>>%tf%
ECHO:fbW6ubC2WxMs00Dn3c3\2tb92rOd3bB]23[xf5G0S3Bcn0R70f0600ibb2>>%tf%
ECHO:tx866yWAqbn0t3Cf061n[3tYO0j6YX006GbbW6tkr1fsv6hXW8tx40e3C5>>%tf%
ECHO:bfC6WxOf00Dn3e1u]xOj3b2j2B78bdf6ZO2tC62nJDWXuer0hXW8tPf0Z8>>%tf%
ECHO:dX86YBptYX2no0jXJbn06xWABbW0YBlXOd2BeOh1GAg07DBb2w2x]H3b1e>>%tf%
ECHO:2B78bdf6ZO6t[92nJDWXt]40bdf6ZOAs00xtb52nJDWXtKW0r2r2fpv6h1>>%tf%
ECHO:WArnf0r4hGr4]GsEiyWUOd3b7r2BWU867bv1lnGlq0jXEn0lqyjG0mlAu2>>%tf%
ECHO:y0]Zb5QAmxF0deC5mvdfbAN0Vt73LMgNZx0llsyyyvuleqaHIBy1LUDPkH>>%tf%
ECHO:PNLMsNNbyXxBuONbyRxB78HUDV3uFvv]][N36xf7]yC7ibqy33LNkMxfMh>>%tf%
ECHO:S40AkourLVDOkmC3dsW5y3XLtciyZOgMZq7bey2BoUtBsBVvMBmodBBAxh>>%tf%
ECHO:ko6BiHZBRmcnCB3sfevuVJVaee2GfexBi7Keq3N30mkRDTlyWc3bwXxxWc>>%tf%
ECHO:2yUk3bwOxxUk3bwQxyUk2BF83rpA603u9yCBF0M819DFZsn0Vhy3fcW8rl>>%tf%
ECHO:uyfcC8F9l0CWTo16D1ZsLrVnYsS06yf7Fvv]E[kWYBps3y2tOs2nnyjXol>>%tf%
ECHO:KBJ0TPDGLI8ULV2BpxMi2u022BihJvMBxfdBmlMhSQ6BpBFBx]gn03isBu>>%tf%
ECHO:Cuoqiar8hGBuXFye02asxBq6070VgQLOXl63B70eVtKeV7yOhPZ867pr81>>%tf%
ECHO:JDWXNe0Fabuuy7C6j5012BRQj740R3gLZfQBS46yf7tBG6Fvv]E[F0M81T>>%tf%
ECHO:J200gLZfl3r20ml9s6xBHp3bssveP610Q9Jx0w00Pw2r0129s6xyUk3605>>%tf%
ECHO:2BHp2yUk03spwEYsS0reJxFvv]E[F0M82Bq\N2042ySs0ma0W5u3fsW7rg>>%tf%
ECHO:FxlnnxjXabVxF3KmF0e3G5fw7DBbsewRU9331IKma9WEN32nnvjXZbNx\R>>%tf%
ECHO:W7YBpnnxjXmbJx1Qf1pnFDWXs2Fxj14000070DGA8D0A07000000000000>>%tf%
ECHO:0000000000000000000000000000000000000000000000000000000000>>%tf%
ECHO:0000000000000000000000000000000000000000000000000000000000>>%tf%
ECHO:00000000000000000W000000000000000x15X3HF0t0x0x0g}>>%tf%
:: BASM source...
:: '2bat infile outfile tmpfile
:: 'reads infile, surrounds each line with
:: '"ECHO:" and ">>tmpfile" (one > 1st line)
:: 'and outputs results to outfile
:: $com
:: $string 120
:: a$=command$:a$=ltrim$(a$):a$=rtrim$(a$)
:: if a$<>"" then
::  b=instr(a$," ")
::  if b>0 then
::   infile$=left$(a$,b)
::   infile$=rtrim$(infile$)
::   c=len(a$):c=c-b
::   tmpfile$=right$(a$,c)
::   b=instr(tmpfile$," ")
::   if b>0 then
::    outfile$=left$(tmpfile$,b)
::    outfile$=rtrim$(outfile$)
::    c=len(tmpfile$):c=c-b
::    tmpfile$=right$(tmpfile$,c)
::    open infile$ for input as 1
::    if err=0 then
::     open outfile$ for output as 2
::     if err=0 then
::      done=0:b$=">"
::      while done=0
::       line input #1,a$:done=eof
::       if done=0 then
::        print #2,"ECHO:",a$,b$,tmpfile$
::        b$=">>"
::       end if
::      wend
::      close 2
::      close 1
::     end if
::    end if
::   end if
::  end if
:: end if
::-------------------------------
:: decode 2bat utility...
%dec% < %tf% > %file2bat%
if errorlevel 1 goto binerror
::---- EncBin-encoded binary ----
:: cmntsrc.com made with BASM ASM FREELINK
ECHO:ADLNr64U2y00tD61O0AAj7CDXn28h5G4D7JeRkl5HB00xG47uvgCv4gB[4>%tf%
ECHO:gGC0v]U[2uS1213301EBu1Je13dnJDrX5O]ZC4hZC4ybb0Zb[2hxG42yf6>>%tf%
ECHO:mbj0mbW22xf6ebO1ebv02yf6NbW22xf6ly[4Nbv0Dn3e0J]xIU3b413b2H>>%tf%
ECHO:3b8j2jG0gC7DAtQj2uI]3b9e2XGq2ZIb23Wxb4v0S3Fe6Gdxb4Rbn0RbO0>>%tf%
ECHO:Nb022j2noCjXR62j062GQXb4VbS1pxf42yf6ebK06XG4YZb4t3Eb041nf3>>%tf%
ECHO:rLYGQsW4riW2QjW6rcW2OjW4rZf2u7tB6]W4r1G2gC0lmDGXf3vlyDCGF0>>%tf%
ECHO:AAmxF0bab4Z63xlZGc33QB4ar4yA]X842AyxJEf23D73mvdd0AkoFtkHTM>>%tf%
ECHO:MBBxF0xsyyBvxh7qo96BkV\UN3THLMTMLU7buyYBoUibqyYBk1wUK3muym>>%tf%
ECHO:G]cPu3fEv6hwW5uRFykGPNNvtfcob430XowUXVN3Dm2ZGq3rZ3td3yMBF8>>%tf%
ECHO:Zsr0SutB4e44bcf4WxGa0UW6HFx3v09y0meygGS0yYW3yHZaf0tjZ0xn3D>>%tf%
ECHO:jGC7Ln2rW0hEm0x[W]WT97urv75OO10aC4lyPV3bwJxxPV3b0y2xOE2yPV>>%tf%
ECHO:3bw4xxPV3b0133xyK6VbFyxxK6ty06ubBytx063bFyty06YBj3erWTh0vW>>%tf%
ECHO:gy0mZ0W5w9I33s1wFhlyPV3bv8xxPV0m39WvKWS756I37sLoangHv0fEj6>>%tf%
ECHO:ymG]e3gWZOxsv0ptf4xnFD8XTT0Bkn1PLJsI5IgVZGpx84Au00tBsBivZo>>%tf%
ECHO:hfZAulcofQZHhBmmI]SB[3veiuFJTque08[eyuJrBe2GisZVV600bVLR9O>>%tf%
ECHO:SOU3j2bewNSemqaO]BV8ZH6r127D9Oye2G7bqxR7114400dBq6071XV3j6>>%tf%
ECHO:Ga01v0v6Gc23[x84KQS4uy0cb4fgG4f97DABSU84EAqxiAWUGc2AumyEfm>>%tf%
ECHO:pnn2jGy3hwC5F0M933pxf5ybBxJ3fx7DBbsuy3DJF06ZG4pnFDWXsgixM9>>%tf%
ECHO:C7kLlBtBHp3brKxBi8f1f0XMG4g07D9TB2000101S000bD0[bD00070000>>%tf%
ECHO:0000000000000000000000000000000000000000000000000000000000>>%tf%
ECHO:0000000000000000000000000000000000000000000000000000000000>>%tf%
ECHO:000000000000000000001h1i9j9XTg0000001]kcP\rWPi1n9]LiTh1[0t>>%tf%
ECHO:0t80jt}>>%tf%
:: BASM source...
:: rem open file specified on command line and write
:: rem to stdout with ":: " prefixed to each line
:: $com
:: a$=command$:a$=trim$(a$)
:: if a$="" then
::  print "no parm":end
:: endif
:: open a$ for input as 1:e=err
:: if e>0 then
::  print "file not found":end
:: endif
:: fileloop:
:: line input #1,a$:e=eof
:: if e=0 then
::  output ":: ";a$:goto fileloop:
:: endif
:: close 1
::-------------------------------
:: decode cmntsrc utility...
%dec% < %tf% > %cmntsrc%
if errorlevel 1 goto binerror
::---- EncBin-encoded binary ----
:: replace.com made with BASM ASM FREELINK
ECHO:AGINr64U2y00tD61O0AAj7CDXn28Q587D7JeRkl5S]00xG47uvfav7f][7>%tf%
ECHO:gGC0v]U[2uMDA13301EBu1Je13dnJDrX5OYZ47dZ47Fbv2ibO5pyW9dr00>>%tf%
ECHO:M90maRXTW1s3v3hDWJs[W5sIv5hDW9fD[0W5fmp95T3bCd2yMD7bLB3bKs>>%tf%
ECHO:2y]D2r0Y29C5FRp9K17b43pyWNBbC53b85Qx87pyWPNbj2Jb45pxWPabK4>>%tf%
ECHO:Fbv2pyWPJb45Bb05pxWPZb447br2pyWPBb053bv45d1b]D7bI93bBD2yhD>>%tf%
ECHO:7bJh2xhD6yUD7bB81of3r4]Gr7C2F0tZ47pxWPabS2O30UC7cqv7vETq3b>>%tf%
ECHO:Io3ySpS7obWPJbO43b[2pyWP7bf40mlZSU2y]D6xUD7bC]03r647]ZS7t3>>%tf%
ECHO:Ds871vf3r1xGvETs3bHv3y[pS7obWPFb44VbK2pyWTZbS4Q31s870mF0bU>>%tf%
ECHO:r7hDWPrFC206SU2ZTq0mF006Tq0e[6S7hZS7BbG4RyDuW7QDWPukW3rTv2>>%tf%
ECHO:hDWVrxW4rfr4hDWVskW3rBv2hDWVrfW4rQb4O1obWVFbf3Vbv1pyWRZb44>>%tf%
ECHO:pxWRpyWNBbv1Do3e04ac4k3bFo1dW1QDWVt]W3uIv1hDWRumr3hDvRhDWN>>%tf%
ECHO:uDK1S3Je2Gmc010mlZSU2xxD7b5x03r647UZS7t3Dq871vf3r4]GugC0Wx>>%tf%
ECHO:Tq02Dy3e0raySES7Fbb3RyDqW7QDWVsIW3sxv1hDWVtVv3vETq3bE93ySp>>%tf%
ECHO:S7obWV3b83JbO1pyWVNbW3qe2G3bS3pxS7pyWVNb41JbO3xxS7pyWXab01>>%tf%
ECHO:R7210700BbK3ubS3JbC1pyWZNbK3pxWZEyW7yb81Dn3e04ae1S]xEDAyMD>>%tf%
ECHO:7b4U1nf3r3hGuGpxWZpyWJub01Dn3e0Oab1V3bCH2xU52y6DBb2p3746W7>>%tf%
ECHO:003e6eWxU100j2S3ee6GBbv2pxWXJbv2pxWZubr2pyWXybr2Ry21f7t5t3>>%tf%
ECHO:E1071yf3r4YGrO00f0lnJDrXh7W7tHW1sQW0rc02f0lnJDrXhDvThDWBsQ>>%tf%
ECHO:r0hDvVhDWDsHr0hDvXhDWFs8W0tPr0hDv7hDWXrvW0QDWXtUW2OFW7tOC2>>%tf%
ECHO:ln9CF07DAn83Fy3D4me0Z23xlZS62Au6y0YZ07i3WMS63x7Ab8b7Zjuxln>>%tf%
ECHO:n2jGm3xfdd30coy3PHLNtBvla0iymyylUhyHg9Z1sVLP73LNPMLNXUuXiy>>%tf%
ECHO:ZOXUuRiyZ8s1LVf3yvRmA[DPlx]D2yUD3byRy3TGLMmvdhHo0AN0VrwULO>>%tf%
ECHO:N3MBFfZfv6hDOHj5v0hDO7j5r0hDWFtSayX6uxhDWBtJayX6FvF0Q9JtRy>>%tf%
ECHO:JtQBJtOuJxXyMxxD2y]D03cpwEtBJvFvv]E[F0M82x]D2y6D7bwQxx6D6y>>%tf%
ECHO:hD3bxLworLhDvDhDW7s[iyX67v16ytwEitrPtGhDvFhDC91putIs013vRm>>%tf%
ECHO:A[0ml8W5sSiytTMBsTN3NbvWxBROMBZqrOiyZGgUZhkBwNMBxfdBmlMhSS>>%tf%
ECHO:6BpBFBx]gn03isBuCuoqiarAhGBuUBkRCekTF3F0oRN3FbuxgGZ8]3v0gx>>%tf%
ECHO:EBbU07YX07tBC6r746w8I33y1yC2Fy2tK0Bqr37rqa2r2j2ElnnFjGSv1n>>%tf%
ECHO:W5f0trZ0Jvb]p857ZYv7WVK1O607y3hDWBtCuxhDvBhDW9t3uxhDW9t5yx>>%tf%
ECHO:hDf9Z8W3xA0TZuvWSB0ml8[5HF]3v0swu3pyWBqbNxpxWB[ml00v9oO8H1>>%tf%
ECHO:]1v0K2Vm2s02pyW9Fvv]E[m7O607013yW607t3C8b75ov4vES82tSC2nn9>>%tf%
ECHO:jXtB46b7ZJexlBWU07EAyxFEFRAn3DCGkLlBtBHp2y]D2BPE3vRmA[0ml8>>%tf%
ECHO:n5N2041LlBt3Bf0ma0X6ixXpW4ufiw9606I9s6vw001wW6f1[0X6yxhDO9>>%tf%
ECHO:j5f0Xpv4hDC91puxIs800esEyvRmA[0ml8f5Z\9Tj4v0hDC7F0M933pyW7>>%tf%
ECHO:ubiwu3hDW7tPFwkLlBtBHp3bpNxBi8f1f0XMG4g07D9TB200gLZfR62D09>>%tf%
ECHO:2GQn2lqyjX\n2Y]D36r6W900]Bq\N30101S000bD0[bD00070000000000>>%tf%
ECHO:0000000000000000000000000000000000000000000000000000000000>>%tf%
ECHO:0000000000000000000000000000000000000000000000000000000000>>%tf%
ECHO:00000000000000000000000000009\Tl9iS08j}>>%tf%
:: BASM source...
:: 'typical usage: type file | _replace "abc" "def" > newfile
:: 'replace all occurences of string "abc" with string "def"
:: 'read from standard input, write to standard output
:: $com
:: $string 512 'max line len
:: chr10$=chr$(10)
:: chr13$=chr$(13)
:: quote$=chr$(34)
:: 'parse command line...
:: c$=command$
:: c$=ltrim$(c$)      'get rid of surrounding spaces
:: c$=rtrim$(c$)
:: d$=left$(c$,1)
:: if d$<>quote$ then goto clerror
:: clen=len(c$)
:: clen=clen-1
:: c$=right$(c$,clen) 'remove 1st quote
:: qpos=instr(c$,quote$)  'get position of 2nd quote
:: if qpos<2 goto clerror 'must be at least 1 char
:: qpos=qpos-1
:: str1$=left$(c$,qpos) 'slice off match string
:: qpos=qpos+2
:: clen=len(c$)
:: rlen=clen-qpos
:: str2$=right$(c$,rlen) 'raw second half
:: str2$=ltrim$(str2$)  'no extra spaces
:: d$=left$(str2$,1)      'check for quotes
:: if d$<>quote$ then goto clerror
:: d$=right$(str2$,1)
:: if d$<>quote$ then goto clerror
:: clen=len(str2$)      'make sure valid string
:: if clen<2 then goto clerror 'too short
:: if clen>2 then            'at least 1 char
::  clen=clen-1
::  str2$=right$(str2$,clen)  'remove quotes
::  clen=clen-1
::  str2$=left$(str2$,clen)
:: else
::  str2$=""          'otherwise empty replace
:: end if
:: oneline$=""  'initialise main processing loop
:: linelen=0    'count chars to avoid overrun
:: mainloop:          'loop until no more...
:: a$=stdin$                   'get one character
:: if a$="" then goto nomore   'done with input
:: if a$=chr13$ then goto mainloop 'skip cr
:: if a$=chr10$ then           'if lf process line
::  gosub process               'replace and output
::  oneline$=""                 'reset string
::  linelen=0
::  goto mainloop               'and loop for more
:: end if                  'if not lf
:: if linelen<512 then      'if not overflow
::  oneline$=oneline$+a$     'add to line
::  linelen=linelen+1        'bump length count
:: end if
:: goto mainloop
:: nomore:
:: if linelen>0 then       'if extra chars w/ no crlf
::  gosub process           'process as one line
:: endif
:: end               'exit to dos
:: clerror:              'command line error
:: print "error"
:: end
:: process:                     'subroutine...
:: replace str1$ with str2$ in oneline$  'nice
:: output oneline$              'write to stdout+crlf
:: return
::-------------------------------
:: decode replace helper...
%dec% < %tf% > %replace%
if errorlevel 1 goto binerror
:: everything is in place to convert com into batch code...
::  %dec% - the decoder program
::  %enc% - encodes binary to text, stdin to stdout
::  %file2bat% infile outfile tmpfile - turns into echo code
::  %cmntsrc% file - outputs stdout with commented file
::  %replace% "search" "replace" - stdin to stdout
::  %compname% = the compiler name
::  %madewith% = utilities used to compile
::  %usetempdir% = yes if tempdir to be used
::  %decname% = decoder filename to use
::  %tempname% = temp filename to use
::  %fbase%.%outputext% = output filename
:: determine temp names...
set t=
if .%usetempdir%==.yes set t=%%temp%%.\
:: create output file...
set outfile=%fbase%.%outputext%
echo @echo off>%outfile%
echo>>%outfile% ::---- Generated by CONV2BAT ----
:: attach decoder...
echo>>%outfile% :: CM3-encoded DecBin decoder...
%file2bat% %dec% %tf1% %t%%decname%
type>>%outfile% %tf1%
:: attach encoded com file...
echo>>%outfile% ::---- EncBin-encoded binary ----
echo>>%outfile% :: %binfile% made with %madewith%
%enc% < %binfile% > %tf%
%file2bat% %tf% %tf1% %t%%tempname%
type>>%outfile% %tf1%
:: attach source code...
if not .%compname%==. echo>>%outfile% :: %compname% source...
if not .%compname%==. %cmntsrc% %fbase%.%fext% >>%outfile%
echo>>%outfile% ::-------------------------------
:: attach decode/run/delete lines...
echo>>%outfile% :: decode/run/delete, modify as needed...
echo>%tf1% %t%%decname% {{.lt{{ %t%%tempname% }}.gt}} %t%%binfile%
%replace% "{{.lt{{" "<" < %tf1% > %tf2%
%replace% "}}.gt}}" ">" < %tf2% >>%outfile%
if not .%compname%==. echo>>%outfile% %t%%binfile% %%1 %%2 %%3
if not .%compname%==. echo>>%outfile% del %t%%binfile%
if .%compname%==. echo>>%outfile% :: use %t%%binfile% as needed
echo>>%outfile% del %t%%tempname%
echo>>%outfile% del %t%%decname%
echo.
echo  ===========================================================
echo    Done! %binfile% is the raw binary
if exist %fbase%.asm echo    intermediate assembly is in %fbase%.asm
echo    %outfile% batch code recreates %binfile% when run
echo  ===========================================================
echo.
goto cleanup
:binerror
echo  Error in encoded binary
:cleanup
if exist %tf% del %tf%
if exist %tf1% del %tf1%
if exist %tf2% del %tf2%
if exist %enc% del %enc%
if exist %dec% del %dec%
if exist %replace% del %replace%
if exist %sepname% del %sepname%
if exist %cmntsrc% del %cmntsrc%
if exist %file2bat% del %file2bat%
:end

Tasm assembly source for the DecBin decoder... (before CM3 encoding)

;decbin.asm
;original code by Tenie Remmel - zdecode.asm from ASnip
;modified by Terry Newton to use 12 bit/64 char encoding
;verifies length, if not correct outputs ret and sets el=1

Ideal

Model Tiny
P186
CodeSeg
Org 100h

Proc        Program

            mov ah,3Fh         ;Read file/device
            xor bx,bx          ;handle for STDIN
            mov dx,1024        ;Start at DS:1K
            mov cx,63488       ;62K bytes (max)
            int 21h            ;DOS services

            mov si,1024        ;SI, DI = 1K
            mov di,si

            mov ah,'A'         ;AH = 'A'
            call GetDigit      ;First digit
            shl al,4           ;Shift over
            mov ch,al          ;Put in CH
            call GetDigit      ;Second digit
            or ch,al           ;OR into CH
            call GetDigit      ;Third digit
            shl al,4           ;Shift over
            mov cl,al          ;Put in CL
            call GetDigit      ;Last digit
            or cl,al           ;OR into CL
            push cx            ;Save numbytes

InpLoop:    lodsb              ;Load byte
            cmp al,21h         ;< 21h?
            jb InpLoop         ;Get new byte
            cmp al,'}'         ;Check for right bracket
            je Done            ;It's a right bracket, done
            ja InpLoop         ;Get new byte
            dec si             ;Re-load that byte
            lodsw              ;Load word
                ;first char in al, 2nd in ah
            xor dh,dh          ;Clear DH
            cmp al,5Eh         ;restore skip ^_` (mod...)
            jl AL_ok0
            sub al,3
AL_ok0:     cmp ah,5Eh
            jl AH_ok0
            sub ah,3
AH_ok0:     cmp al,3Ah         ;restore skip :;<=>?@ (mod...)
            jl AL_ok
            sub al,7
AL_ok:      cmp ah,3Ah
            jl AH_ok
            sub ah,7
AH_ok: 
            sub ax,3030h       ;Adjust to binary (mod)
            mov dl,ah          ;Save remainder
            mov ah,64          ;Multiply by 64 (mod)
            mul ah
            add ax,dx          ;Add in remainder
            call Out12         ;Output 12 bits (mod)
            jmp InpLoop

Done:
            pop cx             ;Number of bytes (moved)
;verify size... (mod)
            mov ax,di          ;get output pointer
            sub ax,1024        ;subtract starting offset
            cmp ax,cx          ;compare actual vs recorded bytes
            je VerOK           ;if equal then cool
            dec ax
            cmp ax,cx          ;if actual is 1 more then cool
            jne VerNotOK       ;otherwise verify fails
            xor ax,ax          ;zero ax
            push ax            ;save for return value
VerOK: ;(stock minus pop plus label)
            mov ah,40h         ;Write file/device
            mov bx,1           ;handle for STDOUT
            mov dx,1024        ;Start at DS:1K
            int 21h            ;DOS services
       ;return errorlevel 0 or 1 depending on verify status
            pop ax             ;get saved errorlevel
            mov ah, 4Ch        ;exit function
            int 21h            ;do it

VerNotOK: ;here if size doesn't match (mod)
            mov cx,1           ;just one byte
            mov [byte ds:1024],0C3h ;make it a return
            push cx            ;save for return value
            jmp VerOK          ;write it

EndP        Program

Proc        GetDigit

DLoop:      lodsb
            cmp al,'A'
            jb DLoop
            cmp al,'P'
            ja DLoop
            sub al,'A'
            ret

EndP        GetDigit

;my code follows...

SplitFlg    db 0     ;if not 0 then split byte
SplitByt    db 0     ;buffer

Proc        Out12    ;output 12 bits (to out buffer)
;on calling al/ah=Byte1/Byte2low, Byte3/Byte2high, Byte4/Byte5low
;                 Byte6/Byte5high, Byte7/Byte8low, etc (starting at 1)
            push bx cx
            mov bl,[SplitFlg]  ;get even/split flag
            cmp bl,0
            je OutEven         ;if 0 then goto OutEven
            mov bl,[SplitByt]  ;get last split byte
            and bl,0Fh         ;low nibble
            shl ah,4           ;ah=high nibble
            or ah,bl           ;ah=reconstructed byte
            xchg ah,al         ;swap bytes
            stosb              ;store split byte
            mov al,ah          ;put split byte in al
            stosb              ;followed by whole byte
            mov [SplitFlg],0   ;clear flag
            jmp Out12x         ;exit
OutEven:    mov [SplitByt],ah  ;save split byte low nibble
            stosb              ;store even byte
            mov [SplitFlg],1   ;set split flag
Out12x:     pop cx bx
            ret
EndP        Out12

End Program

Tasm source for the EncBin encoder...

;encbin.asm
;original code by Tenie Remmel - zencode.asm from ASnip
;modified by Terry Newton for 12 bits per character pair
;batch-compatible encoding (64 possible values)

Ideal

Model Tiny
P186
CodeSeg
Org 100h

Proc        Program

            mov ah,3Fh         ;Read file/device
            xor bx,bx          ;handle for STDIN
            mov dx,24064       ;Start at DS:23.5K (mod)
            mov cx,40960       ;40K bytes (max) (mod)
            int 21h            ;DOS services
            mov cx,ax          ;Number of bytes in CX

            mov si,24064       ;SI = 23.5K (mod)
            mov di,1024        ;DI = 1K
            mov bx,2           ;BX = 2 (words)

            mov ah,'A'         ;AH = 'A'
            mov al,ch          ;First digit
            shr al,4           ;Shift over
            add al,ah          ;Adjust to text
            stosb              ;Store byte
            mov al,ch          ;Second digit
            and al,0Fh         ;AND mask
            add al,ah          ;Adjust to text
            stosb              ;Store byte
            mov al,cl          ;Third digit
            shr al,4           ;Shift over
            add al,ah          ;Adjust to text
            stosb              ;Store byte
            mov al,cl          ;Last digit
            and al,0Fh         ;AND mask
            add al,ah          ;Adjust to text
            stosb              ;Store byte

            add cx,si          ;CX = limit for SI

InpLoop:    call In12          ;Input 12 bits     (mod)
            mov dl,64          ;Divide AX by 64   (mod)
            div dl             ;now AL, AH <= 63
            add ax,3030h       ;Adjust to text 1st char="0" (mod)
            cmp al,3Ah         ;skip :;<=>?@ (mod...)
            jl AL_ok
            add al,7
AL_ok:      cmp ah,3Ah
            jl AH_ok
            add ah,7
AH_ok:      cmp al,5Eh         ;skip ^_` (mod...)
            jl AL_ok0
            add al,3
AL_ok0:     cmp ah,5Eh
            jl AH_ok0
            add ah,3
AH_ok0:
            stosw              ;Store word
            inc bx             ;Inc line counter
            cmp bx,29          ;58 chars on this line? (mod)
            jl Next            ;Jump if not
            xor bx,bx          ;Clear line counter

            mov ax,0A0Dh       ;Put a CRLF in AX
            stosw              ;Store it
Next:       cmp si,cx          ;SI > CX?
            jbe InpLoop        ;Jump if not

            mov al,'}'         ;Store bracket (ending char)
            stosb
            mov ax,0A0Dh       ;Put a CRLF in AX (added)
            stosw              ;Store it (added)

            mov ah,40h         ;Write file/device
            mov bx,1           ;handle for STDOUT
            mov cx,di          ;Number of bytes
            mov dx,1024        ;Start at DS:1K
            sub cx,dx          ;Adjust CX
            int 21h            ;DOS services
            ret                ;Exit

EndP        Program

;the following is my code...
SplitByt    db 0               ;buffer byte
SplitFlg    db 0               ;even/split flag

Proc        In12
   ;return next 12 bits in ax, al=whole ah=fractions
   ;al/ah.. Byte1/Byte2low, Byte3/Byte2high, Byte4/Byte5low etc
            push bx cx        ;save regs
            mov cl,[SplitFlg] ;get flag
            cmp cl,0
            je get2           ;if 0 then goto get2
            lodsb             ;get one more byte in al
            mov ah,[SplitByt] ;ah=left over bits from 2nd byte
            shr ah,4          ;top nibble
            mov [SplitFlg],0  ;clear even/split flag
            jmp exin12        ;exit
get2:       lodsb             ;load byte into al
            push ax           ;save it
            lodsb             ;get next byte
            mov [SplitByt],al ;into buffer
            mov bl,al         ;save it
            pop ax            ;al=1st byte
            mov ah,bl         ;ah=2nd byte
            and ah,0Fh        ;ah=bottom nibble of 2nd byte
            mov [SplitFlg],8  ;anything but 0
exin12:     pop cx bx         ;restore regs
            ret               ;back to caller

EndP        In12

End Program

Using Conv2Bat and Reusing Binary Batch Code

Download and install the compilers you wish to use, copy binaries that don't access libraries (such as basm, asm and the linkers) to a path directory, create linking batch files in a path directory for the larger compilers (asicc, mrc, tbc). For Windows, make shortcuts to the doc files of the compilers you're using or make printed copies, you'll need to refer to them often as you write code. If you want to be able to right-click and select "Convert to Batch" (or however named) use Windows' View|Options File Types dialog and add entries for asi, bas, moo and tok files as needed. You can also drag and drop the source file on top of the Conv2Bat.bat file or a shortcut to it. Conv2Bat should work fine in Dos 6, it contains no Windows-specific code but does need the Dos 6 or later FIND command to properly document whether or not PkLite was used. Under NT the comments in the output batch will likely be incorrect but the code output should be the same (if there are no other incompatibilities, have only tested with Win95Cmd).

Conv2Bat outputs batch code that writes the decoder to "_d.com", the temp file to "_t.tmp" and the actual decoded program to its original filename. Edit the output code as needed for your requirements. I often change the file targets to variables (like >>%tf%) to allow writing to the temp directory without requiring a long filename on each line. Once useful utilities have been created you can copy/paste them as needed into your batch files.

I just installed a new compact html browser called "Off By One" and wanted to be able to drag an html file onto the icon to load, but the browser wants to see a file:///c:/whatever type url for local files. Basically I needed a batch that converts a quoted long filename into a url that the browser can load. First I need to convert the long name into a short name (really to get rid of the quotes but doesn't hurt to be short so a simple for loop will do), prepend "file:///" to the quoteless name, and change "\" to "/". Here's what I come up in very little time using code copied from Conv2Bat...

:: OffByOne doesn't understand disk filenames like "c:\web\file.htm"
:: Convert parm to "file:///" + name without quotes and change \ to /
@echo off
set url=
if .%1==. goto noparm
if not exist %1 goto noparm
set dec=%temp%.\_dec.com
set tf=%temp%.\_tmp.tmp
set replace=%temp%.\_rep.com
::-----------------------
:: CM3-encoded DecBin decoder...
ECHO:`h}aXP5y`P]4nP_XW(F4(F6(F=(FF)FH(FL(Fe(FR0FTs*}`A?+,>%dec%
ECHO:fkOU):G*@Crv,*t$HU[rlf~#IubfRfXf(V#fj}fX4{PY$@fPfZsZ$:NvN$>>%dec%
ECHO:9AyroNB-)dOKwK0rRkfTbi)ws_~[[q9wE'sqlu1sY*Bsfe=@ziNS1a)88e>>%dec%
ECHO:f9RTL)9Z{3INBD?o6@MDLO{Zz4Q23E-'09NX9@Vz(42A7c8zMS:u$w6k5Q>>%dec%
ECHO:N,h:le)~gF?tutTyxoe5UiIdtn';0rJ1q:{7;lAl']y:yTjZBbOo?QRIdN>>%dec%
ECHO:$Bp@P/nAp_r0*4f'XcF4q3o?$_t5lx$Q-OxSfUNQ__Gd~$Q-Oxgkx=LGHU>>%dec%
ECHO:S)$C6P8#>>%dec%
::---- EncBin-encoded binary ----
:: replace.com made with BASM ASM FREELINK
ECHO:AGINr64U2y00tD61O0AAj7CDXn28Q587D7JeRkl5S]00xG47uvfav7f][7>%tf%
ECHO:gGC0v]U[2uMDA13301EBu1Je13dnJDrX5OYZ47dZ47Fbv2ibO5pyW9dr00>>%tf%
ECHO:M90maRXTW1s3v3hDWJs[W5sIv5hDW9fD[0W5fmp95T3bCd2yMD7bLB3bKs>>%tf%
ECHO:2y]D2r0Y29C5FRp9K17b43pyWNBbC53b85Qx87pyWPNbj2Jb45pxWPabK4>>%tf%
ECHO:Fbv2pyWPJb45Bb05pxWPZb447br2pyWPBb053bv45d1b]D7bI93bBD2yhD>>%tf%
ECHO:7bJh2xhD6yUD7bB81of3r4]Gr7C2F0tZ47pxWPabS2O30UC7cqv7vETq3b>>%tf%
ECHO:Io3ySpS7obWPJbO43b[2pyWP7bf40mlZSU2y]D6xUD7bC]03r647]ZS7t3>>%tf%
ECHO:Ds871vf3r1xGvETs3bHv3y[pS7obWPFb44VbK2pyWTZbS4Q31s870mF0bU>>%tf%
ECHO:r7hDWPrFC206SU2ZTq0mF006Tq0e[6S7hZS7BbG4RyDuW7QDWPukW3rTv2>>%tf%
ECHO:hDWVrxW4rfr4hDWVskW3rBv2hDWVrfW4rQb4O1obWVFbf3Vbv1pyWRZb44>>%tf%
ECHO:pxWRpyWNBbv1Do3e04ac4k3bFo1dW1QDWVt]W3uIv1hDWRumr3hDvRhDWN>>%tf%
ECHO:uDK1S3Je2Gmc010mlZSU2xxD7b5x03r647UZS7t3Dq871vf3r4]GugC0Wx>>%tf%
ECHO:Tq02Dy3e0raySES7Fbb3RyDqW7QDWVsIW3sxv1hDWVtVv3vETq3bE93ySp>>%tf%
ECHO:S7obWV3b83JbO1pyWVNbW3qe2G3bS3pxS7pyWVNb41JbO3xxS7pyWXab01>>%tf%
ECHO:R7210700BbK3ubS3JbC1pyWZNbK3pxWZEyW7yb81Dn3e04ae1S]xEDAyMD>>%tf%
ECHO:7b4U1nf3r3hGuGpxWZpyWJub01Dn3e0Oab1V3bCH2xU52y6DBb2p3746W7>>%tf%
ECHO:003e6eWxU100j2S3ee6GBbv2pxWXJbv2pxWZubr2pyWXybr2Ry21f7t5t3>>%tf%
ECHO:E1071yf3r4YGrO00f0lnJDrXh7W7tHW1sQW0rc02f0lnJDrXhDvThDWBsQ>>%tf%
ECHO:r0hDvVhDWDsHr0hDvXhDWFs8W0tPr0hDv7hDWXrvW0QDWXtUW2OFW7tOC2>>%tf%
ECHO:ln9CF07DAn83Fy3D4me0Z23xlZS62Au6y0YZ07i3WMS63x7Ab8b7Zjuxln>>%tf%
ECHO:n2jGm3xfdd30coy3PHLNtBvla0iymyylUhyHg9Z1sVLP73LNPMLNXUuXiy>>%tf%
ECHO:ZOXUuRiyZ8s1LVf3yvRmA[DPlx]D2yUD3byRy3TGLMmvdhHo0AN0VrwULO>>%tf%
ECHO:N3MBFfZfv6hDOHj5v0hDO7j5r0hDWFtSayX6uxhDWBtJayX6FvF0Q9JtRy>>%tf%
ECHO:JtQBJtOuJxXyMxxD2y]D03cpwEtBJvFvv]E[F0M82x]D2y6D7bwQxx6D6y>>%tf%
ECHO:hD3bxLworLhDvDhDW7s[iyX67v16ytwEitrPtGhDvFhDC91putIs013vRm>>%tf%
ECHO:A[0ml8W5sSiytTMBsTN3NbvWxBROMBZqrOiyZGgUZhkBwNMBxfdBmlMhSS>>%tf%
ECHO:6BpBFBx]gn03isBuCuoqiarAhGBuUBkRCekTF3F0oRN3FbuxgGZ8]3v0gx>>%tf%
ECHO:EBbU07YX07tBC6r746w8I33y1yC2Fy2tK0Bqr37rqa2r2j2ElnnFjGSv1n>>%tf%
ECHO:W5f0trZ0Jvb]p857ZYv7WVK1O607y3hDWBtCuxhDvBhDW9t3uxhDW9t5yx>>%tf%
ECHO:hDf9Z8W3xA0TZuvWSB0ml8[5HF]3v0swu3pyWBqbNxpxWB[ml00v9oO8H1>>%tf%
ECHO:]1v0K2Vm2s02pyW9Fvv]E[m7O607013yW607t3C8b75ov4vES82tSC2nn9>>%tf%
ECHO:jXtB46b7ZJexlBWU07EAyxFEFRAn3DCGkLlBtBHp2y]D2BPE3vRmA[0ml8>>%tf%
ECHO:n5N2041LlBt3Bf0ma0X6ixXpW4ufiw9606I9s6vw001wW6f1[0X6yxhDO9>>%tf%
ECHO:j5f0Xpv4hDC91puxIs800esEyvRmA[0ml8f5Z\9Tj4v0hDC7F0M933pyW7>>%tf%
ECHO:ubiwu3hDW7tPFwkLlBtBHp3bpNxBi8f1f0XMG4g07D9TB200gLZfR62D09>>%tf%
ECHO:2GQn2lqyjX\n2Y]D36r6W900]Bq\N30101S000bD0[bD00070000000000>>%tf%
ECHO:0000000000000000000000000000000000000000000000000000000000>>%tf%
ECHO:0000000000000000000000000000000000000000000000000000000000>>%tf%
ECHO:00000000000000000000000000009\Tl9iS08j}>>%tf%
:: BASM source...
:: 'typical usage: type file | _replace "abc" "def" > newfile
:: 'replace all occurences of string "abc" with string "def"
:: 'read from standard input, write to standard output
:: $com
:: $string 512 'max line len
:: chr10$=chr$(10)
:: chr13$=chr$(13)
:: quote$=chr$(34)
:: 'parse command line...
:: c$=command$
:: c$=ltrim$(c$)      'get rid of surrounding spaces
:: c$=rtrim$(c$)
:: d$=left$(c$,1)
:: if d$<>quote$ then goto clerror
:: clen=len(c$)
:: clen=clen-1
:: c$=right$(c$,clen) 'remove 1st quote
:: qpos=instr(c$,quote$)  'get position of 2nd quote
:: if qpos<2 goto clerror 'must be at least 1 char
:: qpos=qpos-1
:: str1$=left$(c$,qpos) 'slice off match string
:: qpos=qpos+2
:: clen=len(c$)
:: rlen=clen-qpos
:: str2$=right$(c$,rlen) 'raw second half
:: str2$=ltrim$(str2$)  'no extra spaces
:: d$=left$(str2$,1)      'check for quotes
:: if d$<>quote$ then goto clerror
:: d$=right$(str2$,1)
:: if d$<>quote$ then goto clerror
:: clen=len(str2$)      'make sure valid string
:: if clen<2 then goto clerror 'too short
:: if clen>2 then            'at least 1 char
::  clen=clen-1
::  str2$=right$(str2$,clen)  'remove quotes
::  clen=clen-1
::  str2$=left$(str2$,clen)
:: else
::  str2$=""          'otherwise empty replace
:: end if
:: oneline$=""  'initialise main processing loop
:: linelen=0    'count chars to avoid overrun
:: mainloop:          'loop until no more...
:: a$=stdin$                   'get one character
:: if a$="" then goto nomore   'done with input
:: if a$=chr13$ then goto mainloop 'skip cr
:: if a$=chr10$ then           'if lf process line
::  gosub process               'replace and output
::  oneline$=""                 'reset string
::  linelen=0
::  goto mainloop               'and loop for more
:: end if                  'if not lf
:: if linelen<512 then      'if not overflow
::  oneline$=oneline$+a$     'add to line
::  linelen=linelen+1        'bump length count
:: end if
:: goto mainloop
:: nomore:
:: if linelen>0 then       'if extra chars w/ no crlf
::  gosub process           'process as one line
:: endif
:: end               'exit to dos
:: clerror:              'command line error
:: print "error"
:: end
:: process:                     'subroutine...
:: replace str1$ with str2$ in oneline$  'nice
:: output oneline$              'write to stdout+crlf
:: return
::-------------------------------
:: decode replace helper...
%dec% < %tf% > %replace%
del %dec%
del %tf%
:: convert parameter into url...
:: first get rid of quotes...
for %%a in (%1) do set file=%%a
:: create set for new url...
echo set url=file:///%file% > %temp%.\tf1.tmp
:: use helper utility to change \ to /
%replace% "\" "/" < %temp%.\tf1.tmp > %temp%.\tf2.bat
:: run results to set url var...
call %temp%.\tf2.bat
:: clean up...
del %temp%.\tf2.bat
del %temp%.\tf1.tmp
del %replace%
:: run browser...
:noparm
"c:\Program Files\hpsw\OffByOne\ob1.exe" %url%

Batch file size is increased, perhaps too much for some tastes, but I don't think that's a huge disadvantage. It does require about 3 seconds to launch rather than 1 second just running the program but a single batch line starting the browser takes almost as long (bin-batch adds only a fraction of a second) and it doesn't matter anyway considering 3 seconds is about 4 times faster than opening Netscape or IE. It doesn't bother me that two com files are created and deleted in my temp directory, it looks scary and takes more bytes, I'm happy I can drag a file to OffByOne and it only took a few minutes to accomplish using a search and replace helper ripped as-is from another bin-batch. I could make it smaller by writing custom code that directly changes "\" to "/" but that would have take longer.. it's faster to copy/paste existing code than write new code if the existing code does the job, the number of lines doesn't figure unless so large it impedes things in other ways.. like when notepad will no longer load the batch. In today's world of multi-megabyte programs a bloated batch is insignificant. I could save space and make the replace utility a persistent file and not encode at all, but then I'd have another utility on my path to keep up with, not worth saving a few kilobytes. Echoed QBasic scripts can do just about anything bin-batch can do, but there's a screen flash, QBasic.exe must be available, and not being able to use "<" or ">" in the code complicates comparisons. Whatever works.

Links to binary batch code and compiler resources


(C) Copyright 2003 by Terry Newton
http://www.infionline.net/~wtnewton