Add readme.txt and export.sh, disable DHGR again, fix minor glitches
authorNick Downing <nick@ndcode.org>
Thu, 16 Jun 2022 08:27:30 +0000 (18:27 +1000)
committerNick Downing <nick@ndcode.org>
Thu, 16 Jun 2022 08:39:45 +0000 (18:39 +1000)
disasm/Makefile
disasm/star_blazer.asm.patch
export.sh [new file with mode: 0755]
readme.txt [new file with mode: 0644]

index 1df4416..4660024 100644 (file)
@@ -27,7 +27,9 @@ star_blazer_tree_loader.bin: \
 star_blazer_hires_loader.bin \
 ../loader/tree_loader.bin \
 ../loader/star_blazer_tree_loader.bin
-       ./tree_encode_lc.py ${TREE_LOADER} ../loader/tree_loader.bin $< $@
+       # for DHGR we need the decompressor to use the language card for temp:
+       #./tree_encode_lc.py ${TREE_LOADER} ../loader/tree_loader.bin $< $@
+       ../loader/tree_encode.py ${TREE_LOADER} ../loader/tree_loader.bin $< $@
        -diff -q ../loader/star_blazer_tree_loader.bin $@
 
 star_blazer_hires_loader.bin: \
@@ -37,6 +39,7 @@ star_blazer.ihx \
        -diff -q ../loader/star_blazer_hires_loader.bin $@
 
 star_blazer.ihx: star_blazer.rel
+       # add for DHGR: -b data1=0xd000
        ${ASLINK} -n -m -u -i \
 -b zpage=0 \
 -b udata0=0x200 \
@@ -44,7 +47,6 @@ star_blazer.ihx: star_blazer.rel
 -b text=0x9fd \
 -b loader=0x2000 \
 -b data0=0x4000 \
--b data1=0xd000 \
 $@ $<
 
 star_blazer.rel: \
index 6580423..e408121 100644 (file)
@@ -1,9 +1,9 @@
---- star_blazer.asm0   2022-06-13 22:29:10.235959486 +1000
-+++ star_blazer.asm    2022-06-13 23:42:02.563905402 +1000
+--- star_blazer.asm0   2022-06-15 00:26:36.295842665 +1000
++++ star_blazer.asm    2022-06-16 16:12:05.836083121 +1000
 @@ -1,3 +1,8 @@
-+ALIGN = 0
-+DHGR = 1
-+PIXEL_SHAPE = 1
++ALIGN = 1
++DHGR = 0
++PIXEL_SHAPE = 0
 +SHAPE = 1
 +
  hw_kbd = 0xc000                               ; c000
diff --git a/export.sh b/export.sh
new file mode 100755 (executable)
index 0000000..9bf4acb
--- /dev/null
+++ b/export.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+rm -f star_disasm.zip
+zip star_disasm.zip readme.txt loader/star_blazer.bin loader/dejunk.py loader/star_blazer_dejunked0.bin loader/star_blazer_dejunked1.bin loader/hires_loader.bin loader/hires_loader.py loader/star_blazer_hires_loader.bin disasm/load.py disasm/mem.bin disasm/star_blazer.txt disasm/star_blazer.asm disasm/shape0.png disasm/shape_index.inc disasm/shape_data.inc disasm/pack.py
diff --git a/readme.txt b/readme.txt
new file mode 100644 (file)
index 0000000..0a07d0e
--- /dev/null
@@ -0,0 +1,295 @@
+Star Blazer disassembly notes
+
+- The original file I worked with is cracked by Mr. Xerox and obtained from:
+  https://archive.org/download/a2_Star_Blazer_1981_Star_Craft/Star_Blazer_1981_Star_Craft.do
+
+- The crack is a bit crude, basically it's a memory dump of how things looked
+  when the original loader was going to transfer control to the game code.
+
+- Since I did not want to disassemble a lot of junk I created a different crack
+  loader, it is similar to the original but it only loads the essential parts.
+
+- For details about this process see the files in /loader:
+
+  - The program /loader/dejunk.py overwrites parts of the original binary that
+    I think are junk, see the notes in the code about address range meanings.
+
+  - The files /loader/star_blazer_dejunked*.bin are dejunked with different
+    fill values, I run both of these to check that the dejunking did not break.
+
+  - The program /loader/hires_loader.py extracts the non-junk parts of the
+    dejunked binary (it works with the original or dejunked binary, but I
+    prefer the latter, since it makes the disassembly cleaner given that there
+    is one region where the alignment padding was not zeroed out originally),
+    concatenates them, and then splits them into parts for my crack loader.
+    It then installs my crack loader and composes the parts ready for loading.
+
+  - The binary /loader/hires_loader.bin is my short machine code program which
+    lives in the hires screen at $2000 and its function is to copy the tail of
+    the program from hires screen memory to the tail location at approx $7e00.
+    It also contains some initializations of zero page, registers and so forth.
+    I haven't included a separate source file as it gets disassembled later on.
+
+  - The result of the re-cracking is /loader/star_blazer_hires_loader.bin which
+    can be played the same as the original binary but is significantly smaller.
+
+- Then see the disassembly in /disasm, in particular /disasm/star_blazer.asm
+  is an ASxxxx source that assembles to /loader/star_blazer_hires_loader.bin.
+  It contains switches at the top of the file to control my game modifications
+  and if left alone (ALIGN = 1, SHAPE = 1) it will produce the original game.
+
+- The ASxxxx assembler used for this project is by Alan R. Baldwin and has a
+   home page at https://shop-pdp.net/ashtml/asxxxx.php. Use "as6500" for 6502.
+
+  - This assembler does not use the most conventional syntax since constants
+    are C-style, e.g. "0x2000" not "$2000", and addressing modes use square
+    brackets, e.g. "lda [0x2000],y" not "lda ($2000),y". The C-style constants
+    are good for projects that compbine C and assembly code, since you can use
+    common include files. The square bracket syntax is not good and I contacted
+    the author who told me it is for historical reasons and promised to fix it.
+
+  - Zero-page references are indicated by "*", e.g. "lda *0x20,x" produces the
+    2-byte instruction whereas "lda 0x20,x" should produce the 3-byte version.
+    (I say *should* because there is an inconsistency in this process that I
+    discovered recently and I will investigate it later and fix the assembler).
+    The assembler *can* generate zero-page references automatically if you use
+    the ".dpage" pseudo-op in the ".area zpage" section, but I haven't done so
+    in case there are places where the reassembled binary doesn't match the
+    original. Probably there aren't, but having control via "*" is quite good.
+
+- I have used a procedure like this to produce the disassembly:
+
+  - Run /disasm/load.py to perform the relocation that is normally done by the
+    hires loader and output mem.bin which is a straight memory dump (no DOS 3.3
+    header) which gets loaded at 0x9fd. This gives the disassembler a clearer
+    picture of what's where, but is not runnable, and does not remove the need
+    for the loader (the loader is also responsible for other initialization).
+
+  - Run my disassembler, which is not included here as it's beyond the scope
+    of this document, passing it a runtime trace file (also not included here),
+    and a manual text file that gives areas and names/sizes of known symbols.
+
+  - The manual text file /disasm/star_blazer.txt is included and could form the
+    basis of a SourceGen or similar project, however, it is pretty terse and
+    does not include all of the information inferred by the disassembler from
+    the trace file. I am working on a way to make the disassembler output this.
+    It would be relatively easy to make a SourceGen project from the asm output
+    of the disassembler, but it would be easier if the process was automated.
+
+  - Run my shape extractor and compiler, this is an optional process since the
+    original .db statements for the shapes are still in /disasm/star_blazer.asm
+    (if you compile with SHAPE = 0) but extracting and recompiling the shapes
+    gives you the opportunity to edit them. I haven't included sources for this
+    process, which is complex, but I do include /disasm/shape0.png for viewing.
+
+- To regenerate the game, use steps like this:
+
+  as6500 -l -o star_blazer.asm
+  aslink -n -m -u -i -b zpage=0 -b udata0=0x200 -b udata1=0x400 -b text=0x9fd -b loader=0x2000 -b data0=0x4000 star_blazer.ihx star_blazer.rel
+  ./pack.py star_blazer.ihx star_blazer_hires_loader.bin
+
+  - The /disasm/pack.py is similar to /loader/hires_loader.py and it moves the
+    sections around for loading. Basically the idea is to move the last 0x2000
+    bytes of the game binary (actually 0x2000 less the loader size) into the
+    hires screen where it will be loaded by BLOAD, and then relocate it at run-
+    time. This prevents BLOAD from having to load 8 kbytes of "gap" at 0x2000.
+
+- The disassembly is far from complete, as I have not figured out all of the
+  game logic, and there may be issues with identifying all relocatable symbols.
+
+  - Basically it is relocatable, but I noticed that it will not always proceed
+    to the next level, i.e. it sometimes gets stuck in a limbo mode in between
+    missions, where you can fly around and shoot, but there are no baddies.
+
+  - I fixed a similar problem that turned out to be a table referenced only by
+    its high address -- I changed something like "lda #0xNN" to "lda #>SYMBOL".
+
+- I have a reasonably good understanding of the game's data structures, its 
+  graphics package and its mathematics routines. I do not fully understand the
+  game physics (which was a major reason to do the disassembly) but I am quite
+  close to it, as I located things like the position and velocity of objects,
+  the angle of a missile, and even routines that look like homing the missile.
+
+- I discovered the following general principles about the engine:
+
+  - There are 0x100 shapes and (I think) 0x70 objects. I only have a tentative
+    understanding of the objects (see discussion of game microcode further on)
+    so I haven't included this yet. But essentially each object has a purpose,
+    e.g. I think 0x20..0x27 are stars in the star-field background, 0x41..0x43
+    are trees or cactuses, etc. The mapping of objects to shapes varies, but
+    within some limits, e.g. object 0x41 is shape 0x78 (tree) or 0x79 (cactus).
+    The maximum number of an object onscreen is dictated by its assigned slots
+    in the 0x70 objects, e.g. I think there can only be up to 3 trees/cactuses.
+
+  - Animation works by drawing shapes in "or" mode to make them appear, then
+    drawing them in "and-not" mode to make them disappear. This leaves a hole
+    in the screen, where any underlying objects are not visible after erasure.
+
+    - When drawing, the engine computes a number of things, such as the shape
+      address to use, the x coordinate mod 7 and so on. These are stored in the
+      object array, and reused (i.e. not recomputed) when it erases the shape.
+
+  - It seems to move the objects one at a time, relying on the fact that it
+    will soon re-draw an underlying object that was unintentionally erased.
+    You can see an artifact of this where a pair of objects scrolls along
+    the ground in a fixed relationship to each other, as one of them might be
+    animated with a "bite" out of it corresponding to previous position of the
+    other. The game uses only hires screen page 1, i.e. no double buffering.
+
+  - The playfield is logically 140 units wide (each is a pair of HGR pixels)
+    and (from memory) 160 units high, the remaining 32 lines being used for
+    the score, the ground, and the various displays like the current mission.
+
+  - Coordinates are kept as (x, y) bytes where 0x80 is the centre of the
+    screen (I think) and so the playfield extends somewhat in each direction
+    beyond the screen. Shapes are clipped if they are drawn partially off the
+    screen. Action can happen off-screen, e.g. a bomb hits a target and you
+    complete the mission, or a missile curves off-screen and comes back on.
+
+  - Velocities are kept with more precision, possibly 16 or 24 bits. There is
+    some complicated logic with 4-bit shifts, which may be to save on storage.
+
+  - I would like to understand the logic of how it generates terrain better,
+    but I suspect that it's somewhat controlled by the extended playfield,
+    e.g. a tree scrolls off the visible screen and continues to scroll to the
+    left invisibly, until it hits the left edge of the playfield, whereupon
+    it is immediately regenerated at a new invisible location somewhere in
+    the invisible right portion of the playfield. This theory is supported
+    by fields in "struct object" and routines that I found whose job is to
+    randomize the position and velocity of an object within given x and y
+    bounds per object. Quite a bit of the object's personality and generation
+    behaviour can be controlled with just these fields, e.g. ground-based
+    objects have the y-limits set the same, so that their y isn't randomized.
+
+  - There are two basic kinds of shapes, the shiftable kind which can be drawn
+    at any x-position on the screen, and the non-shiftable kind which can only
+    be drawn at byte-aligned positions. The shiftable kind is stored with 7
+    pre-shifted shapes, and the "struct shape" contains a pointer to the middle
+    one of these shapes, with indexing by up to +/- 3 * the shape size. This
+    is done to save time and code since a multiply by +/- 3 is cheaper than 7.
+
+    - Shiftable shapes are drawn at only even positions, due to the 140-pixel
+      logical screen width, but still require 7 pre-shifted shapes since even
+      screen-positions can be even or odd within respect to 7-bit screen bytes.
+
+    - The first 8 shapes correspond to individual pixels (really pixel pairs)
+      in different colours, i.e. the 8 HGR colours. These are drawn with a
+      special routine. As an experiment, I tried replacing these with ordinary
+      shapes and commenting the special routine (PIXEL_SHAPE = 1) which worked.
+
+  - Each scan line of each shape is either hi-bit clear (uses black, white,
+    green and purple) or set (uses black, white, blue and orange). The hi-bit
+    never changes within a scan line. When rendering the hi-bit is "ored" into
+    the screen like any other bit, so blue and orange will take precedence.
+
+  - Each shape has a dimension in logical units which defines its collision
+    rectangle, as well as a dimension in physical units (bytes) which defines
+    its drawn rectangle. In general the physical dimension is predictable from
+    the logical dimension, and this means that in some cases padding is drawn
+    on-screen in order that the logical dimension be as the designer intended.
+
+  - Non-shiftable shapes are drawn in general by replacing the previous screen
+    memory contents rather than "oring". The routine that does this is called
+    "draw_misc" in the disassembly, as it does miscellaneous parts like titles,
+    scoring etc. There is a data table which I haven't fully decoded but which
+    contains 16-byte entries, describing (I think) strings to draw and where.
+    The physical (byte) width and drawn rectangle is critical to this process,
+    as I discovered that if you change these, then the text gets all messed up.
+
+    - The "draw_misc" routine also has the ability to mask the shape data with
+      an alternating pair of masks, and this is used to draw the "STAR BLAZER"
+      title screen with colour cycling. Such shape data is stored with hi-bit
+      set, in order that the hi-bit can be selectively masked off as required.
+
+    - The shape table contains some blank rectangles, which obviously would not
+      have any effect when drawn in "or" mode. I discovered that these are used
+      for erasing parts of the screen when drawn with "draw_misc". Their width
+      is important, e.g. a blank rectangle erases "HIGH" before drawing "SCORE"
+      and it then seems to advance by the width of this before drawing "SCORE".
+
+- I discovered the following general principles about the data structures:
+
+  - There is essentially a "struct shape" and "struct object" containing the
+    variables that control a shape or an object, but they are implemented as a
+    separate array (indexed by shape or object number) for each field of the
+    struct, including lo and hi of pointers. This is quite normal in 6502 code.
+
+  - Interestingly the "struct object" seems to have what are essentially sub-
+    classes, because some of the arrays do not implement the entire 0x70-entry
+    range -- so you see code like "lda #object_XXX - 0x40,x" which means the
+    XXX field of "struct object" is only stored for e.g. objects 0x40..0x6f.
+
+  - In the disassembly, most lines which use an addressing mode like "NNNN,x"
+    are annotated in the comment field with a range like "x=40..6f". My custom
+    disassembler has extracte this information from a trace of the runnin game,
+    in which I attempted to exercise at least some of the game levels/features.
+    However, the range is only what it's *seen* and might be larger in reality.
+
+  - The disassembler uses the information about locations accessed by indexing
+    instructions, to build a partition of the data space into separate arrays.
+    It is independent of the base address that happened to be used for access.
+    Overlapping regions are merged, as it assumes they must be the same array.
+
+  - Interestingly, I found a few cases of what I think are game bugs, where the
+    author did not anticipate his access overrunning into a neighbouring array,
+    although it's possible that it was intentional and I didn't understand it.
+
+- I do not well understand the scoring and how the game proceeds through the
+  missions, but I did locate the important variables, so it would be easy to
+  figure it out. This hasn't been my major priority, which is why I didn't yet.
+
+- The part I am presently attacking is to understand the gameplay at a finer
+  level, in particular the collision detection, and what happens for various
+  kinds of collisions (figuring this out will also provide insights into the
+  scoring and how it proceeds through the missions, hence I tackle this first).
+
+  - In an earlier version of the disassembly I had located routines for things
+    like intersection of collision rectangles, but I deleted these symbols from
+    the present disassembly as my naming was based on a slightly earlier way of
+    thinking. It would be relatively easy to re-find and re-annotate this code.
+
+  - It turns out that the collision code is quite integrated into the rest of
+    the game logic. The game seems to use kind of an internal microcode which
+    consists of zero-terminated lists of bytes, and basically each game object
+    has various microcode routines, and many of the microcode bytes seem to be
+    the indices of other game objects that it needs to collision-test against.
+
+  - There are also other bytes such as 0xf0, which I think are not indices of
+    game objects, but rather, microcode commands, and you can see "cmp #0xf0"
+    and similar comparison chains throughout the code to implement these. I am
+    not sure if different kinds of collisions are implemented by different 0xfN
+    commands or by multiple object-indexed microcode tables or a combination.
+
+- I am making the disassembly available in its present state (warts and all)
+  so that others can pick it up if they want to and progress things. In the
+  present .zip I have omitted a lot of my files to keep it to a manageable
+  package, and even then, there is still a lot to take in (hires loader, etc).
+
+- My complete work directory with my emulator, shape extractor and compiler,
+  etc, is available at the following git repository on one of my servers:
+
+  https://git.ndcode.org/public/star_disasm.git
+
+  - Unfortunately the gitweb viewer on my server is broken. I think people are
+    hitting it from the Internet and causing it to crash and restart, and then
+    systemd is not letting it restart after a while. A git client still works.
+
+  - As I am not really ready to make a proper release, I haven't bothered with
+    LICENSE files and such. However, I intend to release my part of the work
+    (not the copyrighted material obviously) under a MIT license. This includes
+    the disassembler, the tracing infrastructure, etc. It's quite sophisticated
+    so I also considered GPLv2, but overall I prefer a more permissive license.
+
+  - It is a work in progress, and I am not all that happy with how it handles
+    the shape editing and various other things. The disassembler also does not
+    have good support for the control file or the ability to add comments or
+    override the operand fields in instructions, and so whilst it can do a lot
+    automatically, it's hard to deal with the case where it gets things wrong.
+    It also does not have good support for immediate operands yet, although it
+    handles all the other addressing modes intelligently. I plan to add an enum
+    feature so that it can more readably decompile constants and the microcode.
+
+- I will not be able to work on the project for a bit, so please enjoy for now.
+
+Nick Downing
+nick@ndcode.org