Brutkey

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

oh hey hardcoding the ship to type 0 causes some WEIRD problems when you restart a game.


Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

guh. I'm staring confused at this palette fade, wondering why it's only lowering the colors by 0x40.

VGA uses a 6-bit color palette, foone. the max value for any color channel is 0x3F!

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

okay I figured out where the fade-out and fade-in routines are and disabled them both. mostly.
fade-in I had to leave in place or the palette would never be set, but I did disable vsync so it finishes nigh-instantly

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

anyway decoding this has given me a new perspective on a childhood bug: I played this game when it was new, but it sometimes crashed at the end of cutscenes.
I've always figured that was a PIC timer problem of some kind, but now I'm thinking it might be because of the weird way they read back the VGA palette. Maybe that was crashing, because of my packard bell's VGA card?

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

it could also be the vsync. maybe it was waiting for a screen refresh signal that never came?

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

well I don't understand how to use the text drawing routine properly, but I sure as fuck know how to use it improperly.

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

It seems to be embedding coordinates into a 16bit integer but not in a way that makes any fucking sense to me

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

okay I was misunderstanding, I think.
it's just DI, the high byte being the y coord, the low byte being the X coord. although... the screen is too wide for this to work.

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

yeah something fucky is going on: it render ONE VALUE with an X coord >255

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

BUT HOW?

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

I love reverse engineering by breaking the code.

I confirmed which function is drawing the background stars by replacing the first byte of it with CB (RETF)

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

ghidra, if you know this code is at 231e:0bbe and it's MOV BYTE PTR CS:[0x44], 0

WHY THE FUCK do you think that points to 1fb9:40b4? it's CS! CS:44! and CS is 231e. LERN TO MATH

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

and if I double click the 44, I end up at 00:44

WRONG AGAIN, GHIDRA

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

231e:0002? that's not... No!

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

the game stores the activation status for 39 missions.

the game only has 17 missions

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

got damn ghidra. I have a disassembly that shows
CMP BYTE PTR [0x0], 0x1
and a decompilation that says:
if(*(char*)0x0 == 1){

I have told ghidra that DS for this segment is 1019. if I click on the 0x0 in the decompilation, I end up at 0000:0000. WRONG
if I click on on the [0x0] in the disassembly, I end up at 231e:0. ALSO WRONG

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

also yeah, fun segmented memory thing. NULL is a valid memory address and here it's used to determine if the mouse is enabled.

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

*puVar4 = *puVar4;

NO

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

the matching disassembly:

MOV AH, byte ptr [DI]
MOV byte ptr ES:[DI], AH

DO YOU KNOW WHAT SEGMENT PREFIXES MEAN, GHIDRA?

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

found the cheat code (it's well known (it's in the MANUAL), but I hadn't seen the (machine/decompiled) code until now.

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

found a copy-paste bug!

the code that draws these three red dots miscolors the center of the rightmost one

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

the code looks like:

MOV AL, 0x34
MOV byte ptr ES:[button_offset + 0xa07 ],AL
MOV byte ptr ES:[button_offset + 0xb46 ],AL
MOV byte ptr ES:[button_offset + 0xb48 ],AL
MOV byte ptr ES:[button_offset + 0xc87 ],AL
MOV AL, 0x32
MOV byte ptr ES:[button_offset + 0xb47 ],AL

but for the 3rd one, that second MOV AL,0x32 is instead MOV AL, 0x34.

that's a copy-paste mistake, that is.

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

so if your gun is on 1-laser mode, it fires every 13-somethings (frames?)
with it on 2-laser mode, it's every 19 somethings
on 3-laser mode, it's every 30 sometimes.

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

yeah it's frames. at least logic frames, I don't think this game unhooks logic from framerate

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

huh! this game doesn't implement highlighted text by the usual way of just drawing the text in a different color.

it instead iterates over the pixels in framebuffer and increases (or decreases, in the case of de-highlighting) the palette index

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

POP AH
PUSH AH
POP AH

IN OR OUT, MAKE UP YOUR MIND!

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

so I went and beat the game while having a breakpoint on a function that tells me conversation IDs:

1: Emer Kane
2: Titus scientist
3. Gimlak
4: Titus (planet)
5: Jelina (planet)
6: Death robots
7: Government
8: Modian
9: Devon Manta
10: Kima
11: Rigelian Supply Depot
12: Titus robot
13: Titus hyperdrive ship
14: Zookeeper alien
15: Rigelian with stolen face
16: Galaen (planet)

So... something is missing!

EDIT: Found 'em

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

why write

bool is_joystick_button_down(int button)

when you can instead write two nigh identical functions:

bool is_joystick_button_1_down()
bool is_joystick_button_2_down()

?

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

I know programmers who don't use copy paste and they're all cowards

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

the only difference between the code is if it ANDs the result against 0x10 (button 1) or 0x20 (button 2).

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

years ago I asked for the source for this game in the hopes of building a modernized version. I think I'm starting to see why I never got the source: it's slightly crap

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

this code does the following:
sets joy_left to false
sets joy_right to false
reads the state of the joystick
if this fails, it sets joy_left to false, joy_right to false, then returns.

YOU DID THAT TWICE. IT'S ALWAYS FALSE

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

this is in the function read_joystick_analog_x, which is the same as read_joystick_analog_y with one byte changed

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

it keeps track of how many enemies/planets are on screen by adding 2 to a global variable in the render_enemies_and_missiles function.

but why two? suspicious.

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

on 16-bit system, 2 is a very suspicious number

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

yep there's an array of pointers!

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

this file loading code is broken. it tries to load the file in 64kb chunks but it only saves the size as a 16bit variable, so a 64kb file will be recorded as 0, and any bigger file will break.

but fortunately the file is only 11kb so it works, as the read-second-chunk behavior never triggers

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

at least this is a compression algorithm that isn't too complicated: it's simple RLE.

0xFF is a marker, and is followed by a byte of repeat count(-1) and a byte of value.

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

although technically this is the second time I hacked this compression, I did figure this out already back in, like, 2013?

Foone🏳️‍⚧️🏳️‍⚧️
@foone@digipres.club

this game uses a ton of hardcoded offsets into data files. I wonder if this was done with linker nonsense or if they had to be manually hardcoded in.

the latter is frighteningly possible

Kevin Karhan :verified:
@kkarhan@infosec.space

@foone@digipres.club that's good, I guess...