the cutting room mirror

688 Attack Sub (Genesis)

This article has a talk page!
view on tcrf.net

This page contains notes for the game 688 Attack Sub (Genesis).

Compressed Data#

For those who are wondering, these are the locations of all compressed blobs in the game. If you look, they'll appear to be PowerPacker-compressed blobs. But they're not quite PowerPacker format... and each blob has a size field stored elsewhere.

Address

Size Field

000146cc

$9C8 + 8

000150bc

$330 + 8

0002020e

$C04 + 8

00020e3a

$364 + 8

000211b4

$9FC + 8

00021bd8

$318 + 8

00021f06

$1D8 + 8

000220f4

$59C + 8

000226b8

$20C + 8

000228da

$B4 + 8

00023328

$CC4 + 8

00024014

$2BC + 8

000242e6

$E4C + 8

0002515a

$304 + 8

00025472

$2C0

00025732

$274

0002652c

$1A4C + 8

00027fa0

$4FC + 8

000284b2

$1210 + 8

000296ca

$304 + 8

000299e4

$E2C + 8

0002a818

$2C4 + 8

0002aaf2

$FE0 + 8

0002bada

$2A8 + 8

0002bd98

$12C0 + 8

0002d060

$2B0 + 8

000318d0

$9DC + 8

000322d4

$310 + 8

000325fa

$91C + 8

00032f3e

$2F8 + 8

0003324c

$7DC + 8

00033a50

$2A0 + 8

00033d06

$910 + 8

0003463e

$2D4 + 8

00034928

$29C + 8

00034bec

$160 + 8

00034d62

$45C + 8

000351e6

$188 + 8

000353d6

$49C

00035872

$460

00035cd2

$65C

0003632e

$57C

000368aa

$614

00036ebe

$57C

0003743a

$6C8

00037b02

$680

00038182

$704

00038886

$590

00038e16

$4E8

000392fe

$5F8

000398f6

$5D0

00039ec6

$654

0003a51a

$6BC

0003abd6

$718

0003b2fe

$804 + 8

0003bb2a

$2D0 + 8

0003be10

$8F8 + 8

0003c730

$2E0 + 8

0007e0d6

$844

0007e91a

$6BC

0007efd6

$7BC

0007f792

$8A4

00080036

$6A4

000806da

$6BC

00080d96

$4F4

0008128a

$5B0

0008183a

$8E4

0008211e

$554

00082672

$59C

00082c0e

$828

00083436

$79C

00083bd2

$504

000840d6

$46C

00084542

$4AC

00084d32

$1C34 + 8

000869ae

$564 + 8

00086f28

$355C + 8

0008a48c

$858 + 8

00095e9a

$8D0 + 8

00096792

$2D0 + 8

00096a78

$8C8 + 8

00097368

$2EC + 8

0009766a

$358 + 8

000979ca

$104 + 8

00097ae4

$1CC + 8

00097cb8

$8C + 8

00097d5a

$170 + 8

00097ed2

$78 + 8

Looks like there's no unused text or staff rolls here. :( Have the decompression anyway (input is the above table, columns separated by tabs).

// 26 june 2015
package main

import (
    "fmt"
    "os"
    "io/ioutil"
    "bufio"
    "encoding/binary"
    "strings"
    "strconv"
)

// utilities

func readbyte(from []byte, addr uint32) byte {
    return from[addr]
}

func readword(from []byte, addr uint32) uint16 {
    return binary.BigEndian.Uint16(from[addr:])
}

func readlong(from []byte, addr uint32) uint32 {
    return binary.BigEndian.Uint32(from[addr:])
}

func writebyte(to []byte, addr uint32, b byte) {
    to[addr] = b
}

func writeword(to []byte, addr uint32, w uint16) {
    binary.BigEndian.PutUint16(to[addr:], w)
}

func writelong(to []byte, addr uint32, l uint32) {
    binary.BigEndian.PutUint32(to[addr:], l)
}

func move_b(dest uint32, src uint32) uint32 {
    dest &= 0xFFFFFF00
    dest |= src & 0xFF
    return dest
}

func sub_b(dest uint32, src uint32) uint32 {
    b := byte(dest)
    b -= byte(src)
    return move_b(dest, uint32(b))
}

func add_w(dest uint32, src uint32) uint32 {
    w := uint16(dest)
    w += uint16(src)
    return move_w(dest, uint32(w))
}

func move_w(dest uint32, src uint32) uint32 {
    dest &= 0xFFFF0000
    dest |= src & 0xFFFF
    return dest
}

func dbf(reg *uint32) bool {
    w := uint16(*reg)
    w--
    *reg = move_w(*reg, uint32(w))
    return w != 0xFFFF
}

// note the parameter order here
func carryset(src uint32, dest uint32) bool {
    return dest < src
}

func aX_dX_w(aX uint32, dX uint32) uint32 {
    // sign-extend dX as a 16-bit value to 32-bits
    ddX := uint32(int32(int16(uint16(dX))))
    return aX + ddX
}

func addq_w(dest uint32, src uint32) uint32 {
    return add_w(dest, src)
}

func subq_w(dest uint32, src uint32) uint32 {
    return sub_w(dest, src)
}

var extend uint32 = 0

func roxl_l(dest uint32, src uint32) uint32 {
    for {
        highbit := src & 0x80000000
        dest <<= 1
        dest |= extend
        extend = highbit >> 31
        src--
        if src == 0 {
            break
        }
    }
    return dest
}

func subq_b(dest uint32, src uint32) uint32 {
    return sub_b(dest, src)
}

func sub_w(dest uint32, src uint32) uint32 {
    w := uint16(dest)
    w -= uint16(src)
    return move_w(dest, uint32(w))
}

// the decompressor itself

var ROM []byte
var RAM = make([]byte, 0x10000)

var d0, d1, d2, d3, d4, d5, d6, d7 uint32
var a0, a1, a2, a3, a4, a5, a6 uint32

var assumedEnd uint32

// (a0:source(ROM) a1:destination(RAM) d0:length -- )
func decomp() {
    a5 = 0x45F6     // RAM
    writelong(RAM, a5, readlong(ROM, a0 + 4))
    a0 += d0
    a2 = a1         // RAM
    a0 -= 4; d5 = readlong(ROM, a0)
    d1 = 0
    d1 = move_b(d1, d5)
    d5 >>= 8
    a1 += d5
    assumedEnd = a1
    a0 -= 4; d5 = readlong(ROM, a0)
    d5 >>= d1
    d7 = move_b(d7, 0x20)
    d7 = sub_b(d7, d1)

loc_FAF6:
    sub_FB62()
    if (d1 & 0xFF) != 0 {
        goto loc_FB1C
    }
    d2 = 0

loc_FAFE:
    d0 = 2
    sub_FB64()
    d2 = add_w(d2, d1)
    if (d1 & 0xFFFF) == 3 {
        goto loc_FAFE
    }

loc_FB0A:
    d0 = move_w(d0, 8)
    sub_FB64()
    a1--; writebyte(RAM, a1, byte(d1))
    if dbf(&d2) {
        goto loc_FB0A
    }
    if carryset(a1, a2) {
        goto loc_FB1C
    }
    return

loc_FB1C:
    d0 = 2
    sub_FB64()
    d0 = 0
    d0 = move_b(d0, uint32(readbyte(RAM, aX_dX_w(a5, d1))))
    d4 = d0
    d2 = move_w(d2, d1)
    d2 = addq_w(d2, 1)
    if (d2 & 0xFFF) != 4 {
        goto loc_FB4E
    }
    sub_FB62()
    d0 = d4
    if (d1 & 0xFF) != 0 {
        goto loc_FB3C
    }
    d0 = 7

loc_FB3C:
    sub_FB64()
    d3 = move_w(d3, d1)

loc_FB40:
    d0 = 3
    sub_FB64()
    d2 = add_w(d2, d1)
    if (d1 & 0xFFFF) == 7 {
        goto loc_FB40
    }
    goto loc_FB52

loc_FB4E:
    sub_FB64()
    d3 = move_w(d3, d1)

loc_FB52:
    d0 = move_b(d0, uint32(readbyte(RAM, aX_dX_w(a1, d3))))
    a1--; writebyte(RAM, a1, byte(d0))
    if dbf(&d2) {
        goto loc_FB52
    }
    if carryset(a1, a2) {
        goto loc_FAF6
    }
    return
}

func sub_FB62() {
    d0 = 1
    sub_FB64()
}

func sub_FB64() {
    d1 = 0
    d0 = subq_w(d0, 1)

loc_FB68:
    extend = d5 & 1
    d5 >>= 1
    d1 = roxl_l(d1, 1)
    d7 = subq_b(d7, 1)
    if (d7 & 0xFF) != 0 {
        goto loc_FB76
    }
    d7 = move_b(d7, 0x20)
    a0 -= 4; d5 = readlong(ROM, a0)

loc_FB76:
    if dbf(&d0) {
        goto loc_FB68
    }
    return
}

// the program
func main() {
    var err error

    if len(os.Args) != 3 {
        fmt.Fprintf(os.Stderr, "usage: %s ROM locations\n", os.Args[0])
        os.Exit(1)
    }

    ROM, err = ioutil.ReadFile(os.Args[1])
    if err != nil {
        fmt.Fprintf(os.Stderr, "error reading ROM: %v", err)
        os.Exit(1)
    }

    f, err := os.Open(os.Args[2])
    if err != nil {
        fmt.Fprintf(os.Stderr, "error opening location list: %v", err)
        os.Exit(1)
    }
    defer f.Close()
    r := bufio.NewScanner(f)
    for r.Scan() {
        line := r.Text()
        parts := strings.Split(line, "\t")
        add8 := strings.Contains(parts[1], "+ 8")
        parts[1] = strings.Replace(parts[1], "+ 8", "", -1)
        parts[1] = strings.Replace(parts[1], "$", "", -1)
        parts[0] = strings.TrimSpace(parts[0])
        parts[1] = strings.TrimSpace(parts[1])
        offset, err := strconv.ParseUint(parts[0], 16, 32)
        if err != nil {
            panic(err)
        }
        size, err := strconv.ParseUint(parts[1], 16, 32)
        if err != nil {
            panic(err)
        }
        if add8 {
            size += 8
        }
        a0 = uint32(offset)
        a1 = 0x8000
        d0 = uint32(size)
        decomp()
        err = ioutil.WriteFile(parts[0], RAM[0x8000:assumedEnd], 0644)
        if err != nil {
            fmt.Fprintf(os.Stderr, "error writing file %s: %v", parts[0], err)
            os.Exit(1)
        }
    }
    if err := r.Err(); err != nil {
        fmt.Fprintf(os.Stderr, "error reading location list: %v", err)
        os.Exit(1)
    }
}

Ed Fletcher#

It turns out that the string I was trying so hard to find was encoded differently. It's stored at $146A4, with each byte stored $20 lower than its actual ASCII value, and ending with an $FF byte.