Skip to content

Commit

Permalink
Add CCP - and more!
Browse files Browse the repository at this point in the history
This pull-request got out of hand, but it closes #30 by adding a
CCP to the CP/M emulator:

* When launched with no arguments the CCP is launched.
* Otherwise we run as we did in the past.
* There is support for using subdirectories as drives now too.
* I've added some more documentation to samples/
* I've added dist/ with games
* Some TODOS are added, and tests.
  • Loading branch information
skx committed Apr 20, 2024
1 parent 52d9210 commit 289a4e6
Show file tree
Hide file tree
Showing 16 changed files with 2,044 additions and 189 deletions.
164 changes: 113 additions & 51 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,13 @@
# cpmulator - A CP/M emulator written in golang

A couple of years ago I wrote [a text-based adventure game](https://github.com/skx/lighthouse-of-doom/) to amuse my child, in Z80 assembly for CP/M. Later my game was ported to the ZX Spectrum. As only a handful of CP/M BIOS functions were used I reasoned it should be possible to emulate those BIOS calls and combine them with a Z80 emulator to allow playing my game on a modern computer.
This repository contains a CP/M emulator, with integrated CCP, which is primarily designed to launch simple CP/M binaries:

This repository is the result: A minimal cross-platform CP/M emulator that supports _just enough_ functionality to run my game, and the Z-machine games from Infocom (i.e. ZORK 1, 2, 3, and the Hitchhiker's guide to the galaxy).
* The project was created to run [a text-based adventure game](https://github.com/skx/lighthouse-of-doom/) I wrote a few years ago, to amuse my child.
* This was written in Z80 assembly language and initially targeted at CP/M, although it was later ported to the ZX Spectrum.
* Over time it has become more functional:
* It can now run ZORK 1, 2, & 3, as well as The Hitchhiker's guide to the galaxy and other similar games.




# Credits

90% of the functionality of this repository comes from the [excellent Z80 emulator library](https://github.com/koron-go/z80) written by @koron-go.




# Limitations

This CP/M emulator is pretty basic, I initially implemented just those primitives required to play my game, and later I added onlye enough of the missing BIOS functions that were required to run the Z-machine from Infocom:

* This means you can play Zork 1!
* This means you can play Zork 2!
* This means you can play Zork 3!
* This means you can play The Hitchhikers guide to the galaxy!

**NOTE**: Now I've reached the point where I can successfully run Zork updates will slow down in this repository, but pull-requests to add functionality will always be welcome!



## Notes On Filesystem Functions

Traditionally CP/M would upper-case the command-line arguments it received, which means that you will ALWAYS need to work with filenames in upper-case.

I don't handle different drive-letters, or user-areas. If you were to run the file-creation code each of these functions would create "./FOO.TXT":

* `cpmulater samples/create.com foo.txt`
* `cpmulater samples/create.com B:foo.txt`
* `cpmulater samples/create.com C:foo.txt`
* `cpmulater samples/create.com D:foo.txt`
I've implemented enough of the BIOS functions to run simple well-behaved utilities, but I've not implemented any notion of disk access - only file-based I/O.



Expand All @@ -49,7 +20,7 @@ This emulator is written using golang, so if you have a working golang toolchain
go install github.com/skx/cpmulator@latest
```

If you were to clone this repository you could build and install by running:
If you were to clone this repository to your local system you could then build and install by running:

```
go install .
Expand All @@ -62,21 +33,51 @@ If neither of these options sufficed you may download the latest binary from [ou

# Usage

To run the emulator you only need to specify the path to a CP/M binary (Z80) which you wish to execute, along with any optional arguments (arguments to the binary you're launching, not the emulator itself):
If you launch `cpmulator` with no arguments then the integrated CCP ("console command processor") will be launched, dropping you into a familiar shell - note here the filenames are presented in the old-school way, as per the later note on filesystems and filenames:

```sh
$ cpmulator
A>
A>DIR *
A: .GIT | .GIT | .GIT | LICENSE
A: README .MD | CCP . | CPM . | CPMULATO.
A: FCB . | GO .MOD | GO .SUM | MAIN .GO
A: MEMORY . | SAMPLES .

A>TYPE LICENSE
The MIT License (MIT)
..
A>
```
$ cpmulator /path/to/binary [optional-args]
```

Note that the Infocom games are distributed as two files; an executable and a data-file. For example if you wish to play ZORK1 you'll need to have these two files, in the same directory:
You can terminate the CCP by pressing Ctrl-C, otherwise interact with it as usual via the built-in commands.

<details>
<summary>Show the CCP built-in commands:</summary>
* `DIR`
* List files, by default this uses "`*.*`", so files without suffixes will be hidden.
* Prefer "`DIR *`" if you want to see _everything_.
* `CLS`
* Clear the screen.
* `ERA`
* Erase the named files.
* `TYPE`
* View the contents of the named file - wildcards are not permitted.
* `REN`
* Rename files, so "`REN NEW = OLD`" - again wildcards are not permitted, nor is cross-drive renaming. This will fail until I've implemented F_RENAME.
</details>


* ZORK1.COM
* The filename of this doesn't matter.
* ZORK1.DAT
* This **must** be named ZORK1.DAT, in upper-case, and be in the current directory.
* Because ZORK1.COM will try to load it by upper-cased name.
You can also launch a binary directly, by specifying it's path upon the command-line, followed by any optional arguments:

```
$ cpmulator /path/to/binary [optional-args]
```

I've placed some games within the `dist/` directory, so you can launch them easily like so:

```sh
$ cd dist/
$ cpmulator ZORK1.COM
ZORK I: The Great Underground Empire
Copyright (c) 1981, 1982, 1983 Infocom, Inc. All rights
Expand All @@ -88,26 +89,66 @@ West of House
You are standing in an open field west of a white house, with
a boarded front door.
There is a small mailbox here.
..

>
```

Note that the Infocom games are distributed as two files; an executable and a data-file. When you launch the game you'll need to be in the same directory as both the game and the data-file.



## Drives vs. Directories

By default when you launch `cpmulator` with no arguments you'll be presented with the CCP interface, with A: as the current drive and A:, B:, C:, and all other drives, will refer to the current-working directory where you launched the emulator from. This is perhaps the most practical way to get started, but it means that files are unique across drives:

* i.e. "`A:FOO`" is the same as "`B:FOO`"
* In short "`FOO`" will exist on drives `A:` all the way through to `P:`.

If you prefer you can configure drives to have their contents from sub-directories upon the host system (i.e. the machine you're running on). This means you need to create subdirectories, and the contents of those directories will only be visible on the appropriate drives:

```sh
$ mkdir A/ ; touch A/LS.COM ; touch A/FOO.COM
$ mkdir B/ ; touch B/DU.COM ; touch B/BAR.COM
$ mkdir G/ ; touch G/ME.COM ; touch G/BAZ.COM
```

Now if you launch the emulator you'll see only the files which _should_ be visible on the appropriate drive:


```sh
$ cpmulator -drives
A>DIR A:
A: FOO .COM | LS .COM

A>DIR B:
B: BAR .COM | DU .COM

A>DIR G:
G: BAZ .COM | ME .COM

A>DIR E:
No file
```

It isn't currently possibly to point different drives to arbitrary paths on your computer, but that might be considered if you have a use-case for it.



## Debugging Failures

When the emulator is asked to execute an unimplemented BIOS call it will abort with a fatal error, for example:
When an unimplemented BIOS call is attempted the program it will abort with a fatal error, for example:

```
$ ./cpmulator FOO.COM
{"time":"2024-04-14T15:39:34.560609302+03:00",
"level":"ERROR",
"msg":"Unimplemented syscall",
"syscall":33,
"syscallHex":"0x21"}
"syscall":255,
"syscallHex":"0xFF"}
Error running FOO.COM: UNIMPLEMENTED
```

You can see a lot of the functions it did successfully emulate and handle by setting the environmental variable DEBUG to a non-empty value, this will generate a log to STDERR where you can save it:
You can see a log of the functions it did successfully emulate and handle by setting the environmental variable DEBUG to a non-empty value, this will generate a log to STDERR where you can save it:

```
$ DEBUG=1 ./cpmulator ZORK1.COM 2>log.log
Expand Down Expand Up @@ -136,13 +177,34 @@ $ cat log.log

You'll see some Z80 assembly programs beneath [samples](samples/) which are used to check my understanding. If you have the `pasmo` compiler enabled you can build them all by running "make".

In case you don't I've added the compiled versions.
In case you don't I've added ensured I also commit the generated binaries to the git repository.




# Credits

* Much of the functionality of this repository comes from the [excellent Z80 emulator library](https://github.com/koron-go/z80) it is using, written by @koron-go.
* The CCP comes from [my fork](https://github.com/skx/z80-playground-cpm-fat/) of the origianl [cpm-fat](https://github.com/z80playground/cpm-fat/)
* However this is largely unchanged from the [original CCP](http://www.cpm.z80.de/source.html) from Digital Research.
* I've not looked at the differences in-depth, but I don't think there are any significant changes.



## Bugs?

Let me know by filing an issue. If your program is "real" then it is highly likely it will try to invoke an unimplemented BIOS function.

Outstanding issues I'm aware of:

* No real support for drives-as-subdirectories.
* Inconsistent handling of Drives in FCB entries.
* There seems to be no suffering here, but ..
* I don't implement some of the basic BIOS calls that might be useful
* Get free RAM, etc, etc.
* These will be added over time as their absence causes program-failures.
* PIP.COM doesn't work for file copying; `PIP A:NEW.COM=B:ORIG.COM`.
* This should copy "ORIG.COM" to "NEW.COM", but aborts due to lack of `F_RANDREC` (and probably more syscalls).
* You can [read about PIP](https://www.shaels.net/index.php/cpm80-22-documents/using-cpm/6-pip-utility) if you're interested. It is a useful tool!

Steve
2 changes: 2 additions & 0 deletions ccp/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ccp.bin: ccp.asm
pasmo ccp.asm ccp.bin
10 changes: 10 additions & 0 deletions ccp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# CCP

This directory contains the CCP we bundle within our emulator:

* [ccp.asm](ccp.asm)
* The source-code, to be compiled by [pasmo]() with the included `Makefile`.
* [ccp.bin](ccp.bin)
* The compiled binary, which is embedded in `cpmulator`, via [ccp.go](ccp.go).

CCP stands for "console command processor" and is basically the "shell".
Loading

0 comments on commit 289a4e6

Please sign in to comment.