Initial commit

This commit is contained in:
Mark Moxon 2022-09-01 10:14:30 +01:00
commit 3049dc938c
38 changed files with 1191 additions and 0 deletions

17
.gitattributes vendored Normal file
View file

@ -0,0 +1,17 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

23
.gitignore vendored Normal file
View file

@ -0,0 +1,23 @@
# Windows thumbnail cache files
Thumbs.db
ehthumbs.db
ehthumbs_vista.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# Project files
.vscode/tasks.json
work/

129
README.md Normal file
View file

@ -0,0 +1,129 @@
# Flicker-free Elite on the Commodore 64
This repository contains a patch for Commodore 64 Elite that drastically improves the quality of the graphics. It does this by removing flicker from the ship-drawing routines.
To play the flicker-free version, see the section on [playing flicker-free Commodore 64 Elite](playing-flicker-free-commodore-64-elite]).
To read about how the patch weaves its magic, see the section on [how the patch works](#how-the-patch-works).
If you are interested in building and applying the patch yourself, see the section on [building the patch](#building-the-patch).
## Contents
* [Acknowledgements](#acknowledgements)
* [A note on licences, copyright etc.](#user-content-a-note-on-licences-copyright-etc)
* [Playing flicker-free Commodore 64 Elite](playing-flicker-free-commodore-64-elite])
* [How the patch works](#how-the-patch-works)
* [A better algorithm](a-better-algorithm)
* [The patching process](the-patching-process)
* [Building the patch](#building-the-patch)
* [Requirements](#requirements)
* [Applying the patch](#applying-the-patch)
## Acknowledgements
Commodore 64 Elite was written by Ian Bell and David Braben and published by Firebird, and is copyright © D. Braben and I. Bell 1985.
The game disks in this repository are similar to those released on [Ian Bell's personal website](http://www.elitehomepage.org/), but to ensure accuracy to the released versions, they're actually the versions from the [Commodore 64 Preservation Project on archive.org](https://archive.org/download/C64_Preservation_Project_10th_Anniversary_Collection) (as it turns out that the disk images on Ian Bell's site differ from the official versions).
The commentary is copyright © Mark Moxon. Any misunderstandings or mistakes in the documentation are entirely my fault.
Huge thanks are due to the original authors for not only creating such an important piece of my childhood, but also for releasing the source code for us to play with. You can find more information about my Elite project in the [accompanying website's project page](https://www.bbcelite.com/about_site/about_this_project.html).
### A note on licences, copyright etc.
This repository is _not_ provided with a licence, and there is intentionally no `LICENSE` file provided.
According to [GitHub's licensing documentation](https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/licensing-a-repository), this means that "the default copyright laws apply, meaning that you retain all rights to your source code and no one may reproduce, distribute, or create derivative works from your work".
The reason for this is that my patch is intertwined with the original Elite game code, and the original game code is copyright. The whole repository is therefore covered by default copyright law, to ensure that this copyright is respected.
Under GitHub's rules, you have the right to read and fork this repository... but that's it. No other use is permitted, I'm afraid.
My hope is that the educational and non-profit intentions of this repository will enable it to stay hosted and available, but the original copyright holders do have the right to ask for it to be taken down, in which case I will comply without hesitation. I do hope, though, that along with the various other disassemblies, commentaries and disk images of Elite, it will remain viable.
## Playing flicker-free Commodore 64 Elite
The flicker-free version of Commodore 64 Elite is available in two versions: NTSC and PAL. These are both based on the GMA86 release of Elite from 1986.
To play the patched game in an emulator or on a real machine, you can download disk images for both versions from the [flicker-free-disks](flicker-free-disks) folder. These have been tested in the VICE emulator, but should also work in other emulators and on real machines. If you don't know which one to use, try the PAL version first, as that seems to be the default for most emulators.
Loading commander files should work in exactly the same way as in the unpatched GMA86 version; the only changes in the patch are graphical.
## How the patch works
### A better algorithm
The 1986 releases of the BBC Master and Apple II versions of Elite saw a marked improvement in the ship-drawing algorithm that seriously reduced flicker without slowing down the game.
In the original versions of Elite, such as those for the BBC Micro, Acorn Electron and Commodore 64, ships were animated on-screen by first erasing them entirely, and then redrawing them in their new positions. The improved algorithm in the BBC Master and Apple II versions is similar, but instead of erasing the entire ship and then redrawing a whole new ship, it erases one line of the old ship and immediately redraws one line of the new ship, repeating the process until the whole ship gets redrawn, one line at a time. This interleaving of the line-drawing process results in much smoother ship graphics, and without adding any extra steps, so it doesn't affect the game speed.
Unfortunately planets are unaffected by the patch, as they use a completely different routine, so they still flicker. However ships, stations, asteroids and missiles are much improved.
For more information on the flicker-free algorithm, see these deep dives on [flicker-free ship drawing](https://www.bbcelite.com/deep_dives/flicker-free_ship_drawing.html) and [backporting the flicker-free algorithm](https://www.bbcelite.com/deep_dives/backporting_the_flicker-free_algorithm.html) in my BBC Micro Elite project.
### The patching process
In order to patch Elite to use the new algorithm, we have to do the following:
* Use c1451 to extract the game binaries from the original disk image
* Use BeebAsm to assemble the additional code that's required for flicker-free ships
* Use Python to inject this new code into the game binaries and adjust the code in a number of places, and to disable any copy protection in the original binaries
* Use c1451 to create a new disk image containing the flicker-free binaries
To find out more about the exact steps in this process, check out the following files, which contain lots of comments about how the process works.
* The [build.sh](build.sh) script controls the build. Read this for an overview of the patching process, which is described above.
* The [elite-flicker-free.asm](src/elite-flicker-free.asm) file assembles and saves out a number of code binaries. These contain larger blocks of code that implements the flicker-free algorithm, which are saved as binary files that are ready to be injected into the game binary to implement the patch.
* The [elite-modify.py](src/elite-modify.py) modifies the game binary. It does this by loading the binary into memory, patching it by injecting the output from BeebAsm, and making a number of modifications to the code before saving out the modified version. It also disables any copy protection so the resulting
## Building the patch
### Requirements
If you want to patch Commodore 64 Elite to the flicker-free version yourself, or you want to explore the patching process in more detail, then you will need the following:
* A Mac or Linux box. The process may work on the Windows Subsystem for Linux, but I haven't tested it.
* BeebAsm, which can be downloaded from the [BeebAsm repository](https://github.com/stardot/beebasm). You will have to build your own executable with `make code`.
* Python. Both versions 2.7 and 3.x should work.
* c1652 from the VICE emulator, which can be downloaded from the [VICE site](https://vice-emu.sourceforge.io).
Let's look at how to patch Commodore 64 Elite to get those flicker-free ships.
### Applying the patch
The patching process is implemented by a bash script called `build.sh` in the root folder of the repository. If any of BeebAsm, Python or c1541 are not on your path, then you can either fix this, or you can edit the `$beebasm`, `$python` or `$c1541` variables in the first three lines of `build.sh` to point to their locations.
You also need to change directory to the repository folder (i.e. the same folder as `build.sh`), and make the script executable.
All being well, doing the following:
```
cd /path/to/c64-elite-flicker-free
chmod a+x build.sh
./build.sh
```
will produce two disk images in the `compiled-game-disks` folder that contain the patched game, which you can then load into an emulator or real machine.
The build process also verifies the build against binaries that are known to be correct, and BeebAsm log files and interim binaries are saved in the `work` folder.
---
Right on, Commanders!
_Mark Moxon_

71
build.sh Executable file
View file

@ -0,0 +1,71 @@
#!/usr/bin/env bash
# Set up program locations (change these if they are not aleady in the path)
beebasm="beebasm"
c1541="c1541"
python="python"
# Create an empty work folder
rm -fr work
mkdir work
# First, we build the NTSC version
cd work
# Extract the files from the original disk image
$c1541 -attach "../original-disks/elite[firebird_1986](ntsc)(v060186)(!).g64" \
-extract
# Assemble the additional code required for flicker-free ships
$beebasm -i ../src/elite-flicker-free.asm -v > compile.txt
# Modify the main game code
$python ../src/elite-modify.py ntsc
# Rebuild the game disk
$c1541 \
-format "no-flicker elite,1" \
d64 \
../flicker-free-disks/c64-elite-flicker-free-ntsc.d64 \
-attach ../flicker-free-disks/c64-elite-flicker-free-ntsc.d64 \
-write firebird \
-write gma1.modified gma1 \
-write gma3 \
-write gma4 \
-write gma5 \
-write gma6.encrypted gma6
# Report checksums
cd ..
$python src/crc32.py reference-binaries/ntsc work
# And now, we build the PAL version
cd work
# Extract the files from the original disk image
$c1541 -attach "../original-disks/elite[firebird_1986](pal)(v040486).g64" \
-extract
# Assemble the additional code required for flicker-free ships
$beebasm -i ../src/elite-flicker-free.asm -v >> compile.txt
# Modify the main game code
$python ../src/elite-modify.py pal
# Rebuild the game disk
$c1541 \
-format "no-flicker elite,1" \
d64 \
../flicker-free-disks/c64-elite-flicker-free-pal.d64 \
-attach ../flicker-free-disks/c64-elite-flicker-free-pal.d64 \
-write firebird \
-write byebyejulie \
-write gma1.modified gma1 \
-write gma3 \
-write gma4 \
-write gma5 \
-write gma6.encrypted gma6
# Report checksums
cd ..
$python src/crc32.py reference-binaries/pal work

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1 @@
}¥CÉŽ° \™¥Ci \™©(…(Lx¡©÷%(…(Lx¡…l…n¥5…ki<03>©ÿ…mLàÌ

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
reference-binaries/pal/gma1 Normal file

Binary file not shown.

Binary file not shown.

BIN
reference-binaries/pal/gma3 Normal file

Binary file not shown.

BIN
reference-binaries/pal/gma4 Normal file

Binary file not shown.

BIN
reference-binaries/pal/gma5 Normal file

Binary file not shown.

BIN
reference-binaries/pal/gma6 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1 @@
}¥CÉŽ° \™¥Ci \™©(…(Lx¡©÷%(…(Lx¡…l…n¥5…ki<03>©ÿ…mLàÌ

108
src/crc32.py Normal file
View file

@ -0,0 +1,108 @@
#!/usr/bin/env python
#
# ******************************************************************************
#
# ELITE VERIFICATION SCRIPT
#
# Written by Kieran Connell, extended by Mark Moxon
#
# This script performs checksums on the compiled files from the build process,
# and checks them against the extracted files from the original source disc
#
# ******************************************************************************
from __future__ import print_function
import sys
import os
import os.path
import zlib
def main():
if len(sys.argv) <= 2:
# Do CRC on single folder
folder = sys.argv[1] if len(sys.argv) == 2 else "."
names = sorted(os.listdir(folder))
print()
print('Checksum Size Filename')
print('------------------------------------------')
for name in names:
if not name.startswith("."):
full_name = os.path.join(folder, name)
if not os.path.isfile(full_name):
continue
with open(full_name, 'rb') as f:
data = f.read()
print('%08x %5d %s' % (
zlib.crc32(data) & 0xffffffff,
len(data),
full_name)
)
print()
else:
# Do CRC on two folders
folder1 = sys.argv[1]
names1 = sorted(os.listdir(folder1))
folder2 = sys.argv[2]
names2 = sorted(os.listdir(folder2))
names = list(names1)
names.extend(x for x in names2 if x not in names)
if 'reference-binaries' in folder1:
src = '[--originals--]'
elif 'output' in folder1:
src = '[---output----]'
else:
src = '[{0: ^13}]'.format(folder1[0:13]).replace(' ', '-')
if 'reference-binaries' in folder2:
dest = '[--originals--]'
elif 'output' in folder2:
dest = '[---output----]'
else:
dest = '[{0: ^13}]'.format(folder2[0:13]).replace(' ', '-')
print()
print(src + ' ' + dest)
print('Checksum Size Checksum Size Match Filename')
print('-----------------------------------------------------------')
for name in names:
if not name.startswith("."):
full_name1 = os.path.join(folder1, name)
full_name2 = os.path.join(folder2, name)
if name in names1 and name in names2 and os.path.isfile(full_name1) and os.path.isfile(full_name2):
with open(full_name1, 'rb') as f:
data1 = f.read()
with open(full_name2, 'rb') as f:
data2 = f.read()
crc1 = zlib.crc32(data1) & 0xffffffff
crc2 = zlib.crc32(data2) & 0xffffffff
match = ' Yes ' if crc1 == crc2 and len(data1) == len(data2) else ' No '
print('%08x %5d %08x %5d %s %s' % (
crc1,
len(data1),
crc2,
len(data2),
match,
name)
)
elif name in names1 and os.path.isfile(full_name1):
with open(full_name1, 'rb') as f:
data = f.read()
print('%08x %5d %s %s %s %s' % (
zlib.crc32(data) & 0xffffffff,
len(data),
'- ',
' -',
' - ',
name)
)
print()
if __name__ == '__main__':
main()

429
src/elite-flicker-free.asm Normal file
View file

@ -0,0 +1,429 @@
\ ******************************************************************************
\
\ BBC MASTER ELITE GAME SOURCE (FLICKER-FREE ROUTINES)
\
\ BBC Master Elite was written by Ian Bell and David Braben and is copyright
\ Acornsoft 1986
\
\ The code on this site has been reconstructed from a disassembly of the version
\ released on Ian Bell's personal website at http://www.elitehomepage.org/
\
\ The commentary is copyright Mark Moxon, and any misunderstandings or mistakes
\ in the documentation are entirely my fault
\
\ The terminology and notations used in this commentary are explained at
\ https://www.bbcelite.com/about_site/terminology_used_in_this_commentary.html
\
\ The deep dive articles referred to in this commentary can be found at
\ https://www.bbcelite.com/deep_dives
\
\ ******************************************************************************
GUARD &CE00 \ Guard against assembling over memory used by game
\ ******************************************************************************
\
\ Configuration variables
\
\ The addresses in the following are from when the game binary is loaded into
\ memory. They were calculated by analysing a memory dump of the running game,
\ searching for patterns in the bytes to match them with the corrsponding code
\ from the BBC Micro version (which is very similar, if you ignore any different
\ addresses).
\
\ XX14 is an unused variable in BBC Micro Elite, and the corresponding address
\ in Commodore 64 Elite is used by something else. Luckily locations $FB to $FE
\ are unused by Elite and the OS, so we can sneak XX14 in there instead.
\
\ Also, the Y variable contains the height of the space view in pixels, divided
\ by 2. This is 96 on the BBC Micro, but the space view is 144 pixels on the
\ Commodore 64, so Y needs to be set to 72 instead.
\
\ ******************************************************************************
XX1 = $0009
INWK = $0009
XX19 = $002A
CNT = $0030
K3 = $0035
K4 = $0043
XX0 = $0057
V = $005B
XX15 = $006B
X1 = $006B
Y1 = $006C
X2 = $006D
Y2 = $006E
XX12 = $0071
XX17 = $009F
XX4 = $00AD
XX20 = $00AE
XX14 = $00FB
PROJ = $7D1F
LL75 = $9FB8
LL80 = $A13F
LL30 = $AB91
Y = 72
\ ******************************************************************************
\
\ Name: SHPPT
\ Type: Subroutine
\ Category: Drawing ships
\ Summary: Draw a distant ship as a point rather than a full wireframe
\
\ ******************************************************************************
ORG $9932
.SHPPT
JSR PROJ \ Project the ship onto the screen, returning:
\
\ * K3(1 0) = the screen x-coordinate
\ * K4(1 0) = the screen y-coordinate
\ * A = K4+1
ORA K3+1 \ If either of the high bytes of the screen coordinates
BNE nono \ are non-zero, jump to nono as the ship is off-screen
LDA K4 \ Set A = the y-coordinate of the dot
CMP #Y*2-2 \ If the y-coordinate is bigger than the y-coordinate of
BCS nono \ the bottom of the screen, jump to nono as the ship's
\ dot is off the bottom of the space view
JSR Shpt \ Call Shpt to draw a horizontal 4-pixel dash for the
\ first row of the dot (i.e. a four-pixel dash)
LDA K4 \ Set A = y-coordinate of dot + 1 (so this is the second
CLC \ row of the two-pixel-high dot)
ADC #1
JSR Shpt \ Call Shpt to draw a horizontal 4-pixel dash for the
\ first row of the dot (i.e. a four-pixel dash)
LDA #%00001000 \ Set bit 3 of the ship's byte #31 to record that we
ORA XX1+31 \ have now drawn something on-screen for this ship
STA XX1+31
JMP LL155 \ Jump to LL155 to draw any remaining lines that are
\ still in the ship line heap and return from the
\ subroutine using a tail call
.nono
LDA #%11110111 \ Clear bit 3 of the ship's byte #31 to record that
AND XX1+31 \ nothing is being drawn on-screen for this ship
STA XX1+31
JMP LL155 \ Jump to LL155 to draw any remaining lines that are
\ still in the ship line heap and return from the
\ subroutine using a tail call
.Shpt
\ This routine draws a horizontal 4-pixel dash, for
\ either the top or the bottom of the ship's dot
STA Y1 \ Store A in both y-coordinates, as this is a horizontal
STA Y2 \ dash at y-coordinate A
LDA K3 \ Set A = screen x-coordinate of the ship dot
STA X1 \ Store the x-coordinate of the ship dot in X1, as this
\ is where the dash starts
CLC \ Set A = screen x-coordinate of the ship dot + 3
ADC #3
BCC P%+4 \ If the addition overflowed, set A = 255, the
LDA #255 \ x-coordinate of the right edge of the screen
STA X2 \ Store the x-coordinate of the ship dot in X1, as this
\ is where the dash starts
JMP LLX30 \ Draw this edge using flicker-free animation, by first
\ drawing the ship's new line and then erasing the
\ corresponding old line from the screen, and return
\ from the subroutine using a tail call
SAVE "shppt.bin", SHPPT, P%
\ ******************************************************************************
\
\ Name: LL9 (Part 11 of 12)
\ Type: Subroutine
\ Category: Drawing ships
\ Summary: Draw ship: Loop back for the next edge
\ Deep dive: Drawing ships
\
\ ******************************************************************************
ORG $A15B
.LL78
LDA XX14 \ If XX14 >= CNT, skip to LL81 so we don't loop back for
CMP CNT \ the next edge (CNT was set to the maximum heap size
BCS LL81 \ for this ship in part 10, so this checks whether we
\ have just run out of space in the ship line heap, and
\ stops drawing edges if we have)
LDA V \ Increment V by 4 so V(1 0) points to the data for the
CLC \ next edge
ADC #4
STA V
BCC ll81 \ If the above addition didn't overflow, jump to ll81
INC V+1 \ Otherwise increment the high byte of V(1 0), as we
\ just moved the V(1 0) pointer past a page boundary
.ll81
INC XX17 \ Increment the edge counter to point to the next edge
LDY XX17 \ If Y >= XX20, which contains the number of edges in
CPY XX20 \ the blueprint, skip the following
BCS P%+5
JMP LL75-2 \ Loop back to LL75-2 to process the next edge (we jump
\ to LL75-2 as we have modified the code around LL75,
\ which moves the jump point back by two bytes)
.LL81
NOP
SAVE "ll78.bin", LL78, P%
\ ******************************************************************************
\
\ Name: LL9 (Part 12 of 12)
\ Type: Subroutine
\ Category: Drawing ships
\ Summary: Draw ship: Draw all the visible edges from the ship line heap
\ Deep dive: Drawing ships
\
\ ******************************************************************************
ORG $A178
.LL155
LDY XX14 \ Set Y to the offset in the line heap XX14
.LL27
CPY XX14+1 \ If Y >= XX14+1, jump to LLEX to return from the ship
BCS LLEX \ drawing routine, because the index in Y is greater
\ than the size of the existing ship line heap, which
\ means we have alrady erased all the old ships lines
\ when drawing the new ship
\ If we get here then Y < XX14+1, which means Y is
\ pointing to an on-screen line from the old ship that
\ we need to erase
LDA (XX19),Y \ Fetch the X1 line coordinate from the heap and store
STA XX15 \ it in XX15
INY \ Increment the heap pointer
LDA (XX19),Y \ Fetch the Y1 line coordinate from the heap and store
STA XX15+1 \ it in XX15+1
INY \ Increment the heap pointer
LDA (XX19),Y \ Fetch the X2 line coordinate from the heap and store
STA XX15+2 \ it in XX15+2
INY \ Increment the heap pointer
LDA (XX19),Y \ Fetch the Y2 line coordinate from the heap and store
STA XX15+3 \ it in XX15+3
JSR LL30 \ Draw a line from (X1, Y1) to (X2, Y2) to erase it from
\ the screen
INY \ Increment the heap pointer
JMP LL27 \ Loop back to LL27 to draw (i.e. erase) the next line
\ from the heap
.LLEX
LDA XX14 \ Store XX14 in the first byte of the ship line heap
LDY #0
STA (XX19),Y
.LL82
RTS \ Return from the subroutine
SAVE "ll155.bin", LL155, P%
ORG $CCE0
\ ******************************************************************************
\
\ Name: LLX30
\ Type: Subroutine
\ Category: Drawing lines
\ Summary: Draw a ship line using flicker-free animation
\
\ ******************************************************************************
.LLX30
LDY XX14 \ Set Y = XX14, to get the offset within the ship line
\ heap where we want to insert our new line
CPY XX14+1 \ Compare XX14 and XX14+1 and store the flags on the
PHP \ stack so we can retrieve them later
LDX #3 \ We now want to copy the line coordinates (X1, Y1) and
\ (X2, Y2) to XX12...XX12+3, so set a counter to copy
\ 4 bytes
.LLXL
LDA X1,X \ Copy the X-th byte of X1/Y1/X2/Y2 to the X-th byte of
STA XX12,X \ XX12
DEX \ Decrement the loop counter
BPL LLXL \ Loop back until we have copied all four bytes
JSR LL30 \ Draw a line from (X1, Y1) to (X2, Y2)
LDA (XX19),Y \ Set X1 to the Y-th coordinate on the ship line heap,
STA X1 \ i.e. one we are replacing in the heap
LDA XX12 \ Replace it with the X1 coordinate in XX12
STA (XX19),Y
INY \ Increment the index to point to the Y1 coordinate
LDA (XX19),Y \ Set Y1 to the Y-th coordinate on the ship line heap,
STA Y1 \ i.e. one we are replacing in the heap
LDA XX12+1 \ Replace it with the Y1 coordinate in XX12+1
STA (XX19),Y
INY \ Increment the index to point to the X2 coordinate
LDA (XX19),Y \ Set X1 to the Y-th coordinate on the ship line heap,
STA X2
LDA XX12+2 \ Replace it with the X2 coordinate in XX12+2
STA (XX19),Y
INY \ Increment the index to point to the Y2 coordinate
LDA (XX19),Y \ Set Y2 to the Y-th coordinate on the ship line heap,
STA Y2
LDA XX12+3 \ Replace it with the Y2 coordinate in XX12+3
STA (XX19),Y
INY \ Increment the index to point to the next coordinate
STY XX14 \ and store the updated index in XX14
PLP \ Restore the result of the comparison above, so if the
BCS LL82a \ original value of XX14 >= XX14+1, then we have already
\ redrawn all the lines from the old ship's line heap,
\ so return from the subroutine (as LL82 contains an
\ RTS)
JMP LL30 \ Otherwise there are still more lines to erase from the
\ old ship on-screen, so the coordinates in (X1, Y1) and
\ (X2, Y2) that we just pulled from the ship line heap
\ point to a line that is still on-screen, so call LL30
\ to draw this line and erase it from the screen,
\ returning from the subroutine using a tail call
.LL82a
RTS \ Return from the subroutine
\ ******************************************************************************
\
\ Name: LL9 (Part 1 of 12)
\ Type: Subroutine
\ Category: Drawing ships
\ Summary: Draw ship: Check if ship is exploding, check if ship is in front
\ Deep dive: Drawing ships
\
\ ******************************************************************************
.PATCH1
\ We replace the following two instructions in part 1 of
\ LL9 with JSR PATCH1, so we start with those two
\ instructions to ensure that they still get done
LDA #31 \ Set XX4 = 31 to store the ship's distance for later
STA XX4 \ comparison with the visibility distance. We will
\ update this value below with the actual ship's
\ distance if it turns out to be visible on-screen
\ We now set things up for flicker-free ship plotting,
\ by setting the following:
\
\ XX14 = offset to the first coordinate in the ship's
\ line heap
\
\ XX14+1 = the number of bytes in the heap for the
\ ship that's currently on-screen (or 0 if
\ there is no ship currently on-screen)
LDY #1 \ Set XX14 = 1, the offset of the first set of line
STY XX14 \ coordinates in the ship line heap
DEY \ Decrement Y to 0
LDA #%00001000 \ If bit 3 of the ship's byte #31 is set, then the ship
BIT INWK+31 \ is currently being drawn on-screen, so skip the
BNE P%+5 \ following two instructions
LDA #0 \ The ship is not being drawn on screen, so set A = 0
\ so that XX14+1 gets set to 0 below (as there are no
\ existing coordinates on the ship line heap for this
\ ship)
EQUB $2C \ Skip the next instruction by turning it into
\ $2C $B1 $BD, or BIT $BDB1 which does nothing apart
\ from affect the flags
LDA (XX19),Y \ Set XX14+1 to the first byte of the ship's line heap,
STA XX14+1 \ which contains the number of bytes in the heap
RTS
\ ******************************************************************************
\
\ Name: LL9 (Part 10 of 12)
\ Type: Subroutine
\ Category: Drawing ships
\ Summary: Draw ship: Check if ship is exploding, check if ship is in front
\ Deep dive: Drawing ships
\
\ ******************************************************************************
.PATCH2
\ We replace the JMP LL78 instruction at the end of part
\ 10 of LL9 with JSR PATCH2, so this effectively inserts
\ the call to LLX30 at the end of part 10, as required
JSR LLX30 \ Draw the laser line using flicker-free animation, by
\ first drawing the new laser line and then erasing the
\ corresponding old line from the screen
JMP LL78 \ Jump down to part 11
SAVE "extra.bin", LLX30, P%

412
src/elite-modify.py Normal file
View file

@ -0,0 +1,412 @@
#!/usr/bin/env python
#
# ******************************************************************************
#
# COMMODORE 64 ELITE FLICKER-FREE MODIFICATION SCRIPT
#
# Written by Mark Moxon
#
# This script applies flicker-free ship-drawing to Commodore 64 Elite, as
# described here:
#
# https://www.bbcelite.com/deep_dives/flicker-free_ship_drawing.html
#
# It does the following:
#
# * Decrypt the gma6 file
# * Modify the gma6 file to draw flicker-free ships
# * Encrypt the gma6 file
# * Modify the gma1 file to remove disk protection
#
# Run this script by changing directory to the folder containing the disk files
# and running the script with "python elite-modify.py"
#
# This modification script works with the elite[firebird_1986](pal)(v040486).g64
# disk image
#
# ******************************************************************************
from __future__ import print_function
import os
import sys
# Convert a C64 address into the corresponding offset within the gma6 file
def get_offset(addr):
return addr - load_address
# Insert a binary file into the game code, overwriting what's there
def insert_binary_file(data_block, addr, filename):
file = open(filename, "rb")
file_size = os.path.getsize(filename)
insert_from = get_offset(addr)
insert_to = insert_from + file_size
data_block[insert_from:insert_to] = file.read()
file.close()
print("[ Modify ] insert file {} at 0x{:02X}".format(filename, addr))
# Insert an array of bytes into the game code, overwriting what's there
def insert_bytes(data_block, addr, insert):
insert_from = get_offset(addr)
insert_to = insert_from + len(insert)
data_block[insert_from:insert_to] = insert
print("[ Modify ] insert {} bytes at 0x{:02X}".format(len(insert), addr))
# Insert a block of NOPs into the game code, overwriting what's there
def insert_nops(data_block, addr, count):
insert = [0xEA] * count
insert_bytes(data_block, addr, insert)
print("[ Modify ] insert {} NOPs at 0x{:02X}".format(count, addr))
# Fetch the platform (NTSC or PAL) from the command line arguments
if len(sys.argv) >= 2:
platform = sys.argv[1]
else:
platform = "pal"
# Print a progess message
print()
print("Modifying Commodore 64 Elite")
print("Platform: {}".format(platform.upper()))
# Configuration variables
load_address = 0x6A00 - 2
seed = 0x49
scramble_from = 0x6A00
scramble_to = 0x6A00 + 0x62D6
# Set up an array to hold the game binary, so we can modify it
data_block = bytearray()
# Load the main code file into data_block
elite_file = open("gma6", "rb")
data_block.extend(elite_file.read())
elite_file.close()
print()
print("[ Read ] gma6")
# Decrypt the main code file
updated_seed = seed
for n in range(scramble_to, scramble_from - 1, -1):
new = (data_block[n - load_address] - updated_seed) % 256
data_block[n - load_address] = new
updated_seed = new
print("[ Decrypt ] gma6")
# Write an output file containing the decrypted but unmodified game code, which
# we can use for debugging
output_file = open("gma6.decrypted", "wb")
output_file.write(data_block)
output_file.close()
print("[ Save ] gma6.decrypted")
# Set the addresses for the extra routines (LLX30, PATCH1, PATCH2) that we will
# append to the end of the main game code (where there is a bit of free space)
llx30 = 0xCCE0
patch1 = 0xCD1E
patch2 = 0xCD35
# We now modify the code to implement flicker-free ship drawing. The code
# changes are described here, which can be read alongside the following:
#
# https://www.bbcelite.com/deep_dives/backporting_the_flicker-free_algorithm.html
#
# The addresses in the following are from when the game binary is loaded into
# memory. They were calculated by analysing a memory dump of the running game,
# searching for patterns in the bytes to match them with the corrsponding code
# from the BBC Micro version (which is very similar, if you ignore any different
# addresses).
# SHPPT
#
# We start with the new version of SHPPT, which we have already assembled in
# BeebAsm and saved as the binary file shppt.bin, so we simply drop this over
# the top of the existing routine (which is slightly longer, so there is room).
insert_binary_file(data_block, 0x9932, "shppt.bin")
# LL9 (Part 1)
#
# This is the modification just after LL9. We insert the extra code with a call
# to the new PATCH1 routine, which implements the original instructions before
# moving on to the new code.
#
# From: LDA #31
# STA XX4
#
# To: JSR PATCH1
# NOP
insert_bytes(data_block, 0x9A8A, [
0x20, patch1 % 256, patch1 // 256 # JSR PATCH1
])
insert_nops(data_block, 0x9A8D, 1)
# LL9 (Part 9)
#
# This is the modification at EE31.
#
# From: LDA #%00001000
# BIT XX1+31
# BEQ LL74
# JSR LL155
#
# To: LDY #9
# LDA (XX0),Y
# STA XX20
# NOP
# NOP
# NOP
insert_bytes(data_block, 0x9F2A, [
0xA0, 0x09, # LDY #9
0xB1, 0x57, # LDA (XX0),Y
0x85, 0xAE # STA XX20
])
insert_nops(data_block, 0x9F30, 3)
# LL9 (Part 9)
#
# This is the modification just after LL74.
#
# From: LDY #9
# LDA (XX0),Y
# STA XX20
# LDY #0
# STY U
# STY XX17
# INC U
#
# To: LDY #0
# STY XX17
# NOP x10
insert_bytes(data_block, 0x9F39, [
0xA0, 0x00, # LDY #0
0x84, 0x9F # STY XX17
])
insert_nops(data_block, 0x9F3D, 10)
# LL9 (Part 9)
#
# This is the modification at the end of the routine.
#
# From: LDA XX15
# STA (XX19),Y
# INY
# LDA XX15+1
# STA (XX19),Y
# INY :
# LDA XX15+2
# STA (XX19),Y
# INY
# LDA XX15+3
# STA (XX19),Y
# INY
# STY U
#
# To: JSR LLX30
# NOP x21
insert_bytes(data_block, 0x9F87, [
0x20, llx30 % 256, llx30 // 256 # JSR LLX30
])
insert_nops(data_block, 0x9F8A, 21)
# LL9 (Part 10)
#
# This is the modification around LL75.
#
# From: STA T1
# LDY XX17
#
# To: STA CNT
# LDY #0
insert_bytes(data_block, 0x9FB4, [
0x85, 0x30, # STA CNT
0xA0, 0x00 # LDY #0
])
# LL9 (Part 10)
#
# This is the second INY after LL75.
#
# From: INY
#
# To: NOP
insert_nops(data_block, 0x9FC1, 1)
# LL9 (Part 10)
#
# This are the two modifications at LL79.
#
# From: LDA (V),Y
# TAX
# INY
# LDA (V),Y
# STA Q
# ... four lots of unchanged LDA/STA, 5 bytes each ...
# LDX Q
#
# To: INY
# LDA (V),Y
# TAX
# ... shuffle the LDA/STA block down by 4 bytes ...
# INY
# LDA (V),Y
# TAX
# NOP
# NOP
insert_bytes(data_block, 0x9FD9, [
0xC8, # INY
0xB1, 0x5B, # LDA (V),Y
0xAA # TAX
])
lda_sta_block = get_offset(0x9FDD)
for n in range(lda_sta_block, lda_sta_block + 4 * 5):
data_block[n] = data_block[n + 4]
insert_bytes(data_block, 0x9FF1, [
0xC8, # INY
0xB1, 0x5B, # LDA (V),Y
0xAA # TAX
])
insert_nops(data_block, 0x9FF5, 2)
# LL9 (Part 10)
#
# This is the modification at the end of the routine. The C64 version has an
# extra JMP LL80 instruction at this point that we can modify to jump to a
# new routine PATCH2, which lets us insert the extra JSR LLX30 without taking
# up any more bytes.
#
# From: JMP LL80
#
# To: JMP PATCH2
insert_bytes(data_block, 0xA010, [
0x4C, patch2 % 256, patch2 // 256 # JMP PATCH2
])
# LL9 (Part 11)
#
# This is the modification at LL80.
#
# We blank out the .LL80 section with 28 NOPs
insert_nops(data_block, 0xA13F, 28)
# LL9 (Part 11)
#
# We have already assembled the modified part 11 in BeebAsm and saved it as
# the binary file ll78.bin, so now we drop this over the top of the existing
# routine (which is exactly the same size).
insert_binary_file(data_block, 0xA15B, "ll78.bin")
# LL9 (Part 12)
#
# We have already assembled the modified part 11 in BeebAsm and saved it as
# the binary file ll115.bin, so now we drop this over the top of the existing
# routine (which is slightly longer, so there is room).
insert_binary_file(data_block, 0xA178, "ll155.bin")
# We now append the three extra routines required by the modifications to the
# end of the main binary (where there is enough free space for them):
#
# LLX30
# PATCH1
# PATCH2
#
# We have already assembled these in BeebAsm and saved them as the binary file
# extra.bin, so we simply append this file to the end.
elite_file = open("extra.bin", "rb")
data_block.extend(elite_file.read())
elite_file.close()
print("[ Modify ] append file extra.bin")
# All the modifications are done, so write the output file for gma6.modified,
# which we can use for debugging
output_file = open("gma6.modified", "wb")
output_file.write(data_block)
output_file.close()
print("[ Save ] gma6.modified")
# Encrypt the main code file
for n in range(scramble_from, scramble_to):
data_block[n - load_address] = (data_block[n - load_address] + data_block[n + 1 - load_address]) % 256
data_block[scramble_to - load_address] = (data_block[scramble_to - load_address] + seed) % 256
print("[ Encrypt ] gma6.modified")
# Write the output file for gma6.encrypted, which contains our modified game
# binary with the flicker-free code
output_file = open("gma6.encrypted", "wb")
output_file.write(data_block)
output_file.close()
print("[ Save ] gma6.encrypted")
# Finally, we need to remove the disk protection from gma1, as described here:
# https://www.lemon64.com/forum/viewtopic.php?t=67762&start=90
data_block = bytearray()
elite_file = open("gma1", "rb")
data_block.extend(elite_file.read())
elite_file.close()
print("[ Read ] gma1")
if platform == "pal":
# For elite[firebird_1986](pal)(v040486).g64
data_block[0x25] = 0xEA
data_block[0x26] = 0xEA
data_block[0x27] = 0xEA
data_block[0x2C] = 0xD0
else:
# For elite[firebird_1986](ntsc)(v060186)(!).g64
data_block[0x14] = 0xEA
data_block[0x16] = 0xEA
data_block[0x15] = 0xEA
print("[ Modify ] gma1")
output_file = open("gma1.modified", "wb")
output_file.write(data_block)
output_file.close()
print("[ Save ] gma1.modified")
print()