A directory-level patch utility for MS-DOS, written in 8086 assembly for MASM 1.25.
PATCH compares two directories and writes a single .DIF file that turns the first
into the second, byte for byte. Applying that file restores the second directory
exactly, including files that were added or deleted. Files that are identical in both
directories never enter the patch and are never touched on apply.
The .DIF file is readable by eye but processed as raw bytes, so it does not depend on
the encoding of the files, on their line terminators, or on whether they are text or
binary at all.
- Target: DOS 2.0 or later. Only DOS 2.0 calls are used (
3Ch,3Dh,3Eh,3Fh,40h,41h,42h,43h,48h,4Ah,56h). - Memory: about 40 KB. Tested on a 128 KB machine.
- No file is ever read into memory in full, not even the
.DIFitself, so patch and file size are limited only by the file system. A 1.8 MB patch has been built and applied on a 640 KB machine, and a 600 KB file processed there. - Checksums are CRC-32 with polynomial
EDB88320h, the same one used by PKZIP andzlib.crc32, so a patch can be verified from outside DOS.
See docs/FORMAT.md for the .DIF grammar and the internals.
A compiled binary is in bin/PATCH.COM. Copy it anywhere on your DOS
path and run it without arguments to see the usage banner.
PATCH MAKE <olddir> <newdir> <file.DIF> build a patch
PATCH APPLY [dir] <file.DIF> apply it in place
PATCH TEST [dir] <file.DIF> rehearse, changing nothing
Typical use, distributing a change to a source tree:
C:\> PATCH MAKE SRC.OLD SRC CHANGES.DIF
21 files, 4 changed
C:\> PATCH TEST SRC.OLD CHANGES.DIF
checking source checksums
trial run, nothing is changed
test passed
C:\> PATCH APPLY SRC.OLD CHANGES.DIF
checking source checksums
applying patch
applied
For APPLY and TEST the directory is optional; without it the current directory is
used. There is no output directory: APPLY edits files where they lie.
| Exit code | Meaning |
|---|---|
| 0 | Everything matched |
| 1 | Bad command line |
| 2 | File operation failed |
| 3 | Checksum or parse mismatch |
- First pass (
checking source checksums) reads the whole patch and verifies the length and CRC-32 of every source file it mentions. Nothing is modified. A file that already matches its target state is reportedalready patchedand skipped on the second pass. A file matching neither the source nor the target state stops the run with exit code 3. - If nothing is left to do, the second pass does not start at all:
nothing to do, exit code 0. - Second pass (
applying patch) rebuilds each file into a temporaryPATCH$$$.TMP, verifies its length and checksum, and only then puts it in place of the original. - On any error the temporary file is removed. The utility leaves no debris behind.
TEST is the same second pass, except the rebuilt file is deleted after its checksum is
verified and the originals are never touched.
MAKE validates the hunk layout of every file before writing it, but does not rehearse
the finished patch. Run TEST separately if you want that.
PATCH 1.0
;
FILE PATCH BOOT.ASM
OLD 37469 F5ABDE5F
NEW 37527 7533887B
@ 1195
- 7
TILE_01
+ 6
SYSINI
END BOOT.ASM
;
END PATCH 1
Service lines are ASCII and end with CRLF; hunk bodies are raw bytes counted by a byte counter, not by lines. The full grammar, the hunk-building algorithm, and the memory design are in docs/FORMAT.md.
bin/PATCH.COM the compiled utility, ready to run
src/ the seven modules, PATCH.INC and MAKEIT.BAT
tools/masm/ MASM 1.25: MASM.EXE, LINK.EXE, EXE2BIN.EXE
build/ scratch directory the build happens in, ignored by git
BUILD.BAT copies the sources and the assembler into build/ and builds
docs/FORMAT.md the .DIF format and the internals
The sources are written for MASM 1.25 and the build is verified with it. Later assemblers have not been tested. The assembler is in the repository, so a clone has everything the build needs.
git clone https://github.com/rustequal/patch.git
Keep the path short and free of spaces: DOSBox exposes 8.3 filenames only. Everything inside the repository is already 8.3.
DOSBox or DOSBox-X is enough; real
hardware, 86Box and PCem work equally well. Open the configuration file (Options →
Configuration in the shell, or dosbox.conf next to the executable) and put the clone
on drive C:
[cpu]
core=auto
cycles=auto
[autoexec]
mount c D:\patch
c:On Linux and macOS the mount line reads mount c /home/you/patch.
C:\> BUILD.BAT
BUILD.BAT changes into BUILD\, copies the sources out of SRC\ and the assembler out
of TOOLS\MASM\, and runs MAKEIT.BAT there. Nothing is written outside BUILD\, which
git ignores, so the tree stays clean and a rebuild needs no cleanup.
MAKEIT.BAT assembles the seven modules, links them in a fixed order, converts the
result with EXE2BIN and removes the intermediates. The link order matters:
PATEND.OBJ must be last, because it carries the CRC table, the stack and the
end-of-image marker that the memory setup measures against.
masm PATCH,,NUL;
...
link PATCH+PATTXT+PATIO+PATWR+PATMAK+PATAPL+PATEND,PATCH,NUL;
exe2bin PATCH.EXE PATCH.COM
C:\BUILD> PATCH
PATCH 1.0 - source tree patcher, (c) 2026 rustequal, MIT License
PATCH MAKE <olddir> <newdir> <file.DIF>
PATCH APPLY [dir] <file.DIF>
PATCH TEST [dir] <file.DIF>
A fuller check takes about a minute: copy SRC\ twice, change one file in the second
copy, then PATCH MAKE a patch between the two, PATCH TEST it against the first copy
and PATCH APPLY it.
| File | Contents |
|---|---|
PATCH.ASM |
Entry point, command line, directory walk, MAKE driver |
PATTXT.ASM |
Screen output, numbers, strings |
PATIO.ASM |
Files, streams, memory, CRC-32 |
PATWR.ASM |
Writing text into the .DIF |
PATMAK.ASM |
Sliding window over a file, comparison, hunk building |
PATAPL.ASM |
.DIF parsing and application |
PATEND.ASM |
CRC table, stack, end of image |
PATCH.INC |
Shared constants and structure offsets |
Two environment traps worth knowing before you edit anything:
- MASM 1.25 does not tolerate data placed below code. Forward references to
DBandDWthat it has not seen yet are sized its own way in pass one and it falls apart in a cascade ofPhase error. Each module keeps its data at the top. - DOS sets
SPfor a.COMprogram to the top of the whole allocated block. If the program hands memory back with function4Ah, the stack ends up outside the shrunken block and the first large read overwrites it. The utility movesSPto its own stack inside the image as its first act.
- One directory level. Subdirectories are not descended into and do not enter the patch.
- All files are taken, hidden and system included; directories are filtered out.
- No limit on the number of files or hunks. The directory listing is not stored at all, and the hunk list lives in a memory block obtained from DOS.
- An edit longer than
MAXD= 600 bytes is not found by the resynchronisation search and the record degrades toREPLACE, meaning the whole file is written out. This does not happen on assembly listings; it does happen on binary data. - File attributes (hidden, system, read-only) are cleared before editing and restored after.
- A
.DIFservice line is limited to 200 bytes. Hunk bodies are not, since they are read by a byte counter. - Creation timestamps are not preserved.
- Volumes that DOS does not expose through
FindFirstare not supported.
bin/PATCH.COM
size 25859 bytes
CRC-32 C31574BF
SHA-256 6fd729608f1f8882d038133b362e2008f462e73c6407b47b5afbb1fe2e11e254
MIT, see LICENSE. It covers the PATCH sources and binary. The three files in
tools/masm/ are Microsoft's and carry their own terms; see
tools/masm/README.md.
The copyright line is compiled into the binary at label MUSAGE in PATCH.ASM. If you
fork this and ship your own build, change it there.