Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

29 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Breadbin

A Commodore 64 emulator for Android, written in Kotlin and Jetpack Compose.

No ads, no tracking, no account, no in-app purchases. The only thing in the app that ever opens a connection is the ROM import, and only when you type an address into it yourself.

It plays tapes, disks and cartridges.

What it is

The emulator is written from scratch rather than wrapped around an existing one. That is a choice with consequences, so here they are: it means one Kotlin codebase with no NDK, no native build and no C fork to maintain, and it means the compatibility ceiling is lower than VICE's. What it does run, it runs properly — the processor is exact, the video chip is drawn a cycle at a time, and the tape is emulated as a signal rather than as a shortcut.

engine/   Pure Kotlin, no Android: the 6510, the VIC-II, the SID, the CIAs, the PLA,
          and the disk, tape and cartridge formats.
app/      The Android app: Compose UI, touch controls, audio, storage.

Keeping the emulator in a plain JVM module means the whole machine is unit-testable without a device, which is how most of it was checked (see Tests).

The ROMs

Breadbin works the moment it is installed. It ships the MEGA65 project's Open ROMs — a free, independently written replacement for BASIC, the KERNAL and the character set, under the LGPL, containing none of Commodore's code. The exact images in app/src/main/assets/openroms/ are covered by the boot test: they reach a READY. prompt, run BASIC typed at the prompt, and load a program off a tape.

Disks, tapes and cartridges all work under them. The drive answers the serial bus one edge at a time rather than by intercepting KERNAL calls, so it does not care whose KERNAL is asking.

Commodore's own ROMs are still worth having. They are what every program of the era was written against, so anything that misbehaves on the free ones is worth trying again on those. Breadbin will take them three ways — the file picker, a link that opens VICE's download page in your browser, or an address you paste in for it to fetch — and works out which of the three each file is from its contents. If you own a C64 you can read them off it instead.

What it will not do is come with an address already in it. They are still someone's copyright, and an app that ships a pointer to a copy of them is the thing doing the distributing; one that fetches from an address you typed is a tool you pointed somewhere. That fetch is also the only thing in the app that ever opens a connection — no analytics, no update check, no ads, nothing phoning home.

What works

Video. All the graphics modes — standard and multicolour text, standard and multicolour bitmap, extended background colour — plus sprites with expansion, priority and both kinds of collision. The display state machine is the one from Christian Bauer's VIC-II article: VC, VCBASE, RC, badlines, and both border flip-flops, which means raster interrupts land on the right line, badlines steal the right forty cycles, and a program that opens the border gets an open border.

Pixels are produced eight at a time, once per cycle, reading the registers as they stand. A raster interrupt that changes the background colour halfway across a line changes it halfway across the line.

Sound. Three voices with all four waveforms, ring modulation, hard sync, the 6581's own envelope rate tables and its exponential decay, and a resonant filter. The emulation is paced by the audio device rather than by the display, so it runs at the machine's speed — 50.12Hz on PAL — rather than the phone's, and the sound has no seams in it.

Tapes. .tap files are played back as what they are: a recording of the gaps between edges on the tape head, delivered to CIA 1's FLAG line at the right moments. The KERNAL's own loader decodes them, which is why turbo loaders work too — this emulates the tape, not the loading.

.t64 archives and .prg files are also read, and those are injected straight into memory.

Disks. .d64 images, through a drive that answers the serial bus properly: three wires, wired-AND, a bit at a time, with the end of a file signalled by a silence rather than a byte. It is written from the protocol rather than from any one KERNAL, so it works under Commodore's ROMs and the free ones alike. Directories, wildcards, sub-directory syntax, saving and scratching all work, and a game that saves its high scores writes them back to the image file.

And a real 1541, if you have its ROM. Supply the 16K 1541 DOS the same way as the other three and the drive becomes a whole second computer: its own 6502, its two 6522s, and the disk modelled as GCR bits going past a head at three hundred rpm. That is the only thing a fast loader can talk to, because a fast loader is a program that runs on the drive's processor. Nothing needs it — disks load without it — but nothing else will run the code a game uploads into the drive.

Cartridges. .crt files, including the banked boards most games shipped on: Ocean, System 3, Dinamic, Magic Desk, Zaxxon, Fun Play, Super Games, Comal-80, Simons' BASIC, Final Cartridge III and EasyFlash, plus plain 8K and 16K and raw binaries.

The rest. PAL and NTSC, an on-screen joystick for either port, the whole C64 keyboard, fast forward, and opening a file from a file manager straight into the emulator.

What does not work

These are real limits, not oversights:

  • Fast loaders, without the 1541's DOS ROM. A game that bit-bangs the serial lines itself — most disk releases from about 1986, and every cracked intro — is uploading code into the drive's processor and then talking to it. The drive Breadbin ships has no processor to upload into, so those games sit waiting. Supplying the 1541 DOS ROM turns on the drive that does, and they work. Tape and cartridge releases are unaffected either way.
  • Save states. Not implemented.
  • Mid-line sprite movement. Sprites are evaluated once per line. Sprite multiplexers — what games actually use — work; the handful of demo effects that move a sprite within a line do not.
  • Copy protection that reads the disk surface needs the real drive, and even then a .d64 does not carry what such a scheme is looking for: it stores decoded sectors, not the flux those schemes measure. A .g64 would; Breadbin does not read one yet.
  • The SID filter is a state variable filter, not a model of the analogue original. Filter sweeps sound right. They do not sound like your particular 6581.

Building

Needs a JDK 21+ and an Android SDK with platform 37.

./gradlew :app:assembleDebug      # app/build/outputs/apk/debug/app-debug.apk
./gradlew :app:assembleRelease    # minified with R8
./gradlew test                    # the engine's unit tests
./gradlew :app:lintDebug

Minimum Android version: 7.0 (API 24).

Tests

./gradlew test runs 48 of them. The ones that matter:

  • The processor passes Klaus Dormann's 6502 functional test — every documented opcode, addressing mode, flag interaction and decimal-mode case, run to the success trap. The test binary is in engine/src/test/resources; it is Klaus2m5's, GPL-3.0, used unmodified as test data.
  • The PLA's truth table is checked at the configurations programs actually use, including $34 for 64K of RAM and $35 for RAM with I/O, and that a write under a ROM lands in the RAM beneath.
  • The video chip is checked by its pixels: a character cell in its colour on its background, the border around the display window, a blanked screen, a sprite over the background, and a sprite-to-background collision.
  • Badlines take the right amount of time — between 3.5% and 7.5% of a frame, against the 5.1% that twenty-five badlines of forty cycles come to.
  • The serial bus is tested against a computer written from the protocol, not from the drive: IecWireTest drives the three lines by hand, one edge at a time, and checks a file comes back byte for byte, that the directory arrives as a program, that a file written over the bus is on the disk afterwards, that a missing name gives back nothing and says 62,FILE NOT FOUND on the error channel, that a device that is not there stays silent, and that attention part way through a transfer abandons it cleanly.
  • The keyboard matrix is checked against the Programmer's Reference Guide rather than against the way this emulator stores it — drive a line, read a port, compare. Testing it against itself was perfectly happy with the rows and columns swapped over, which is how the on-screen keyboard came to do nothing at all.
  • Disk images survive a round trip: sector chains, the BAM, replacing a file, scratching one, and more files than one directory sector holds.

The boot test

BootTest switches a whole machine on and reads the screen, which needs a ROM set. Point BREADBIN_ROMS at a directory holding one and it runs; without it those tests are skipped.

BREADBIN_ROMS=~/c64-roms ./gradlew test

With the Open ROMs set, this repository's boot test has been seen to:

  • boot to a READY. prompt;

  • run PRINT"HELLO WORLD" typed at that prompt and print it;

  • load a program off a tape, pulse by pulse, through the KERNAL's own tape loader: SEARCHINGFOUND HELLOLOADING FROM $0801 TO $0817READY.RUN → the program's output. The tape is generated by TapWriter, which writes the three pulse lengths and the two copies of each block exactly as a C64 wrote them.

  • load a program off a disk, over the emulated serial bus: SEARCHING FOR HELLOLOADING FROM $0801 TO $0814READY.RUN → the program's output — under Open ROMs, with no Commodore ROM involved anywhere;

  • type on the keyboard, through the matrix rather than the KERNAL's buffer.

DriveBootTest needs a 16K 1541 DOS in the same directory. With one, two whole computers talk to each other down three wires: the drive boots its own DOS, passes its power-on self-test, seeks, reads GCR off the surface, and hands over a file and a directory.

What has actually been run

The engine is covered by the tests above, and the whole machine has been booted, typed into and loaded from tape as described. The Android app builds — debug and minified release — and passes lint clean, but it has not been run on a device or an emulator: the touch controls, the audio path and the screens have not been used by hand. That is the honest state of it.

About

A Commodore 64 emulator for Android. No ads, no tracking. Tapes, disks and cartridges.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages