Skip to content

Latest commit

 

History

165 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Seven Cities of Gold — a macOS remaster in progress

Reverse engineering Seven Cities of Gold (Ozark Softscape / Electronic Arts, 1984) from the original Commodore 64 disks, and rebuilding it for macOS in Swift.

The goal is fidelity underneath and a modern surface on top: the simulation's rules, stats and randomness recovered from the original binaries and verified against them, presented at native resolution with modern conveniences. Not smooth scrolling, as it turns out — the original moves the map by whole tiles and never between them, and tweening it looks wrong.

Status: milestone one — exploration. You can start an expedition, sail west out of Spain, make landfall, walk the continent and get back aboard, on the classic map or on a world the original's own World Maker builds. The game's rules — trade, natives, the court, scoring — are not started. See TODO.md.

The viewer showing the Gulf of Mexico and the Caribbean, drawn with the original C64 terrain art

The classic map and the terrain art above both came out of the original disks — the art is not a screenshot of the game, but the game's own tile data, read out of its main program and redrawn. See What the app does.

Milestone one — exploration

The exploration screen: a coastline and open sea inside the original's dithered frame, with the compass and its bearing needle at the center, MEN, FOOD, GOODS and GOLD panels either side, and SPEED and DEPTH beneath

Everything on that screen came out of the original disks and none of it ships here: the map, the terrain tiles, the frame around the viewport, and the charset every word is set in — MEN and DEPTH included. As with the shot above, it is not a photograph of the game but the game's own data, read off disks you supply and redrawn.

The whole of it is navigation: pick a world, arrive at sea off the eastern edge as the original does, sail west, run the ship aground to send the party ashore, explore, and walk back to the ship to leave. There is no crew, no cargo, no supplies and no clock — the four panels and the pace line hold dashes because nothing stands behind them yet, and they are drawn rather than omitted because that is where those numbers go when they arrive.

The screen is the original's, rebuilt rather than emulated. The charset, the viewport frame and the palette all come off your own disks; the layout, the colors, the compass and the wave stipple were each measured from the game running under an emulator rather than eyeballed. Two things it does that the C64 could not: the window can be as wide as your screen, with fog of war supplying the constraint the original's six-tile porthole used to — and if you would rather have the porthole, Game ▸ Aperture ▸ Classic gives you exactly six tiles, drawn much larger than a C64 ever could.

You must supply your own disks

No game data ships here, and none ever will. The code, graphics, text and maps are copyright Electronic Arts / Ozark Softscape. This repository contains only original engine code plus tools that read data from disk images you already own.

This project is heavily AI-assisted

Almost all of the code, the reverse engineering and the notes here were written by Claude, working from my direction. I am saying so plainly because it changes how you should read the claims in NOTES.md.

What that means in practice:

  • Findings are verified where they can be. Ported routines are asserted against output captured from the original 6502 running under an emulator; the map decode was cross-checked against an independent community dump; the terrain art was checked against the game's own rendering. Where something is a guess or a simplification, it says so.
  • NOTES.md records the wrong turns too, deliberately — a cipher that was declared absent and then found, a variant formula rejected twice on bad evidence, several confident conclusions drawn from measurements that could not support them. That record is more useful than a clean narrative, and it is a fair sample of how the work went.
  • Commit messages are long on purpose. They carry the reasoning and the corrections, so the history is readable as an account of the investigation rather than a list of changes.

Treat the reverse-engineering conclusions as well-evidenced but not peer-reviewed. If you know this game's internals and something here is wrong, I would like to know.

Quick start

You do not need the game to try this. Clone it and run:

make run            # opens the app on its New Game screen

Without disk images the classic map is unavailable, so pick Random World and the World Maker builds you one.

To get the classic map and the original terrain art, add images of disks you own:

mkdir -p d64
#   d64/7CITIES1.D64    program disk, side 1 — the terrain art
#   d64/7CITIES2.D64    program disk, side 2 — the historical map

./extract.sh        # decodes both into assets/
make run

Or skip all of that and use the app: File ▸ Import Disk Images… does the same thing from a file picker.

make on its own lists the other targets. Side 2 carries the historical map; side 1 carries the terrain art, the charset and the World Maker. Either can be supplied without the other.

Dependencies

To build and run the port — one dependency, and it is the toolchain:

Requirement Version Notes
macOS 14+ declared in Package.swift
Swift 6.0+ Xcode 16, or the standalone toolchain

There are no Swift package dependenciesPackage.swift has an empty dependency list, so swift build fetches nothing. No emulator, no Python, no network access. AppKit and SpriteKit are system frameworks.

To run the research tools in tools/ — optional, and needed by nobody who just wants to build or play:

Requirement Needed by
Python 3.9+ all of them
Pillow the 9 that render or compare images (pip install pillow)
vice-mcp the 14 that drive the emulator

vice-mcp is a fork of VICE with an MCP server built in — Homebrew's VICE will not work.

What the emulator is still for

Nothing on the build, extract or run path. Everything a user does is static. It is needed for three things:

  • Differential verification. rng_reference.py and arith_reference.py execute the original 6502 and capture its output as test fixtures, which the Swift tests then assert against. Those fixtures are committed, so you only need the emulator to regenerate or extend them — which is how the World Maker phases and the game rules will be verified as they are ported.
  • Re-deriving the cipher. The substitution table in GameCipher was recovered from a known-plaintext pair captured live by catch_decrypt.py. Its closed form was never found and the generating routine is not in RAM, so the table cannot currently be re-derived statically. If you would rather not take it on trust, that is the tool that reproduces it.
  • Measuring what is not on the disks. The exploration screen's colors, its compass sprites and its wave stipple exist only at runtime — built into RAM by the game as it draws. They were read off a live session rather than extracted, which is why those parts of the screen are redrawn to measurements instead of loaded. TODO.md lists what is still guessed.

What the app does

  • New Game (Cmd-N) — the classic map of the Americas, or a world the original's World Maker builds fresh, generated off the main thread behind a progress bar
  • Game ▸ ApertureClassic, the original's six-tile window with no fog, or Explorer, a window as wide as the screen with fog of war and a three-tile sight radius
  • Palette — three models of the C64's colors; Tiles — original C64 art, or custom tiles drawn for this port
  • Steer with the arrow keys, the numpad, the QWE/ASD/ZXC cluster, or vi's YUI/HJKL/BNM — all four are live at once, so vi is there without being the default
  • Sail into land to go ashore; the ship moors where it was and waits. Walk back onto it to re-embark
  • In Explorer only: drag or scroll to pan, =/- to zoom, 0 to fit the whole world, f to re-center. Classic pins the aperture, so it offers none of them

The world is 256 x 400 tiles at roughly three miles each. The sight radius is three, which is the original's six-tile window expressed as a radius — so nothing is ever revealed that the C64 would not have revealed, and the extra room on a modern screen buys memory rather than vision.

Everything the screen is made of comes from your own disks, with no emulator: the classic map, the terrain tiles, the charset the text is set in and the four glyphs of the viewport frame. None of it is a screenshot. The game draws terrain as redefined characters and sets text in a charset of its own, and both turned out to be static data — the tiles inside the main program, the font in disk 1's raw sectors — so extract.sh reads them directly. If no art has been extracted the app falls back to custom tiles and says so in its title bar.

Two things on that screen are not from the disks, because they are nowhere on them: the compass and the wave stipple. Both are hardware sprites or runtime-composed characters, so they are redrawn here to measurements taken from the game running under an emulator — shape, thickness, color and density. NOTES.md and TODO.md say which parts are measured and which are still a guess.

Opening it in Xcode

open app/SevenCities.xcodeproj

Build and run. There is nothing to configure: no scheme arguments, no paths. The app keeps its assets in ~/Library/Application Support/SevenCities/assets, generates a world if none are there, and has File ▸ Import Disk Images… for pulling the classic map and the original art off disks you own. You never need a terminal.

The project is a deliberately thin wrapper: it owns the bundle, the menu bar and the launch, and depends on SevenCitiesCore as a local Swift package. All the behavior lives in the package's ViewerKit library, which the MapViewer command-line front end uses too, so the two cannot drift apart. Because the app target references the package rather than duplicating it, adding a file to the package needs no project edit.

Prefer the package on its own? open SevenCitiesCore/Package.swift works as well, and swift build / swift test behave as usual.

Layout

extract.sh          decode your disks into assets/
Makefile            extract, run, test, build, app, clean
app/                thin Xcode app wrapper — bundle and launch only
  SevenCities.xcodeproj     depends on the package below, duplicates nothing
SevenCitiesCore/    the Swift package, where everything actually lives
  Sources/SevenCitiesCore   simulation, decoding and extraction, no UI
  Sources/ViewerKit         the SpriteKit viewer, shared by the app and the CLI
  Sources/Extract           the command-line extractor
  Sources/MapViewer         command-line front end for the viewer
images/             screenshots used by this README
tools/              Python research tools (not needed to build or run)
NOTES.md            the reverse engineering record
TODO.md             what is still missing
d64/                your disk images — gitignored
assets/             extracted data — gitignored

tools/ holds the archaeology: a D64 reader, a recursive-descent 6502 disassembler, harnesses that drive VICE to verify ported code against the original, and the map and tile extractors that came first. None of it is needed to build or play; it is how the format was worked out, kept so the findings can be reproduced and checked.

Fidelity

Ported code is not trusted until it matches the original. tools/rng_reference.py loads the original 6502 routine into an emulator, runs it, and captures the output as a test fixture; the Swift tests then assert the port reproduces it exactly.

Component Status
Fastloader / disk sector order Solved from the loader's own command string
Map format Solved — blocked sectors, nibble tiles, 256x400
Terrain vocabulary Solved — from the game's own name table
game cipher Solved — fixed byte substitution, verified exactly
Terrain tiles Solved — read from the program, no emulator
Charset and viewport frame Solved — static on disk 1, inverted at load
World Maker RNG Ported and verified against the original 6502
Multiply / divide helpers Ported and verified against the original 6502
World generation Ported and verified — both bands byte for byte, and the draw order
Exploration screen Measured from a live game — layout, colors, compass, wave stipple
Expedition position in memory Not found — see TODO.md
The game's rules Not started

The map decode was cross-checked against an independent community dump of the historical map and agrees on every continent, island chain, river course and mountain range.

The World Maker is the largest thing verified this way. It is a plate-tectonics model and a cultural-diffusion model in 18 KB of 6502, and the port reproduces the original's output byte for byte over both bands of the map — every landmass, river course, lake and village — and finishes with its random generator in the state the original leaves it in, which is a claim about every draw of every phase in order, not just about the result.

NOTES.md records how each of these was established, including the approaches that failed.

License

Engine code here is original work, released under the MIT license (see LICENSE). It is not affiliated with or endorsed by Electronic Arts. Seven Cities of Gold and all original game assets remain the property of their respective rights holders.

About

Reverse engineering Seven Cities of Gold (C64, 1984) and rebuilding it for macOS in Swift. No game data included — bring your own disk images.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages