Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement random reading and C_RAWIO #18

Merged
merged 3 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 47 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
# 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/) in Z80 assembly for CP/M, to amuse my child. As the game is written in Z80 assembly and only uses a couple of the CP/M BIOS functions I reasoned it should be possible to emulate just a few functions to get the game playable via golang - which would make it portable to Windows, MacOS, and other systems.
A couple of years ago I wrote [a text-based adventure game](https://github.com/skx/lighthouse-of-doom/) in Z80 assembly for CP/M, to amuse my child. As only a handful of CP/M BIOS functions were used I reasoned it should be possible to emulate those BIOS calls alongside a basic Z80 emulator, and play my game on a modern computer.

* **NOTE**: My game was later ported to the ZX Spectrum.

This repository is the result, a minimal/portable emulator for CP/M that supports just enough of the CP/M BIOS functions to run my game.
This repository is the result, a minimal/portable emulator for CP/M that supports _just enough_ CP/M-functionality to run my game, as well as the ZORK games from Infocom!

My intention is to implement sufficient functionality to run ZORK and HITCHHIKERS, and then stop active work upon it.
* **NOTE**: My game was later ported to the ZX Spectrum.




# Credits

95% of the functionality of this repository comes from the Z80 emulator library I'm using:
90% of the functionality of this repository comes from the Z80 emulator library I'm using:

* https://github.com/koron-go/z80

Expand All @@ -22,18 +20,29 @@ My intention is to implement sufficient functionality to run ZORK and HITCHHIKER

# Limitations

This CP/M emulator is extremely basic:
This CP/M emulator is extremely basic, I initially implemented just those primitives required to play _my_ game.

Later I added more BIOS functions such that it is now possible 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 galacy

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


* It loads a binary at 0x0100, which is the starting address for CP/M binaries.
* Initially I implements only the four syscalls (i.e. BIOS functions) I needed:
* Read a single character from the console.
* Read a line of input from the console.
* Output a character to the console.
* Output a $-terminated string to the console.
* I've since started to add more, and there are open issues you can use to track progress of the obvious missing calls (primarily file I/O related functinality):
* [Open issues](https://github.com/skx/cpmulator/issues)

**NOTE**: At the point I can successfully run Zork I will slow down the updates to this repository, but pull-requests to add functionality will be welcome regardless.
## 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`



Expand All @@ -42,13 +51,13 @@ This CP/M emulator is extremely basic:
When the emulator is asked to execute an unimplemented BIOS call it will abort with a fatal error, for example:

```
$ ./cpmulator ZORK1.COM
$ ./cpmulator FOO.COM
{"time":"2024-04-14T15:39:34.560609302+03:00",
"level":"ERROR",
"msg":"Unimplemented syscall",
"syscall":15,
"syscallHex":"0x0F"}
Error running ZORK1.COM: UNIMPLEMENTED
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:
Expand Down Expand Up @@ -91,10 +100,26 @@ Or:
go install github.com/skx/cpmulator@latest
```

After that you may launch the binary you wish to run under the emulator - in my case that would be `lihouse.com` from [the release page](https://github.com/skx/lighthouse-of-doom/releases):
If you wish to play ZORK, or a similar game you'll want to have the two files:

* 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.

```
$ cpmulator lihouse.com
$ cpmulator ZORK1.COM
ZORK I: The Great Underground Empire
Copyright (c) 1981, 1982, 1983 Infocom, Inc. All rights
reserved.
ZORK is a registered trademark of Infocom, Inc.
Revision 88 / Serial number 840726

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.
..
```


Expand All @@ -103,6 +128,8 @@ $ cpmulator lihouse.com

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.



## Bugs?
Expand Down
24 changes: 23 additions & 1 deletion cpm/cpm.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Package CPM is the main package for our emulator, it uses memory to
// Package cpm is the main package for our emulator, it uses memory to
// emulate execution of things at the bios level
package cpm

Expand Down Expand Up @@ -64,6 +64,12 @@ type CPM struct {
// Valid values are 00-15
userNumber uint8

// fileIsOpen records whether we have an open file.
fileIsOpen bool

// file has the handle to the open file, if fileIsOpen is true.
file *os.File

// findFirstResults is a sneaky cache of files that match a glob.
//
// For finding files CP/M uses "find first" to find the first result
Expand Down Expand Up @@ -103,6 +109,10 @@ func New(filename string, logger *slog.Logger) *CPM {
Desc: "C_WRITE",
Handler: SysCallWriteChar,
}
sys[6] = CPMHandler{
Desc: "C_RAWIO",
Handler: SysCallRawIO,
}
sys[9] = CPMHandler{
Desc: "C_WRITESTRING",
Handler: SysCallWriteString,
Expand All @@ -115,6 +125,14 @@ func New(filename string, logger *slog.Logger) *CPM {
Desc: "DRV_SET",
Handler: SysCallDriveSet,
}
sys[15] = CPMHandler{
Desc: "F_OPEN",
Handler: SysCallFileOpen,
}
sys[16] = CPMHandler{
Desc: "F_CLOSE",
Handler: SysCallFileClose,
}
sys[17] = CPMHandler{
Desc: "F_SFIRST",
Handler: SysCallFindFirst,
Expand Down Expand Up @@ -143,6 +161,10 @@ func New(filename string, logger *slog.Logger) *CPM {
Desc: "F_USERNUM",
Handler: SysCallUserNumber,
}
sys[33] = CPMHandler{
Desc: "F_READRAND",
Handler: SysCallReadRand,
}

// Create the object
tmp := &CPM{
Expand Down
Loading
Loading