oh, error strings that specify what function they are, my beloved

oh this is a weird thing I didn't know existed.
you embed an int3 in your code, then a short JMP, and the ASCII characters "WVIDEO".
If the ASCII code is found, it ends up outputting a debug string in the Watcom debugger.

http://www.ctyme.com/intr/rb-0010.htm

Int 03

this game is so weird. I'm like 99% sure it saves background you're currently on into the save file.
not, like, an index into which background, but the background ITSELF

correction: that's now 100%

it definitely does.

which is actually helpful.
I can load the game, move left and right, then save the game.
this'll export the current background to a file
yeah that didn't work
FIXED IT
I'm extracting all the backgrounds from Star Trek: Deep Space Nine: Harbringer.
I still haven't figured out how to attach the right palettes to images.
it's 256-color, and there's 43 palettes here. a bunch of them are similar so I can't just match them manually
yeah I'm gonna have to automate extracting all these images and then figure out the palette nonsense. there's a bunch of files here. 307 of them, although at least one is a duplicate.

my wizards-apprentice script keeps trying to open my text editor

why
what is there for you in the hex editor, keyboard/mouse macro?

ahh it's because dosbox's "capture mouse" is a toggle. I'm inadvertently un-capturing.
automating a DOS game that requires you to capture the mouse in DOSBox is a fucking nightmare.
You can't tell where the cursor is! you can't consistently move it, either!
and this game is 100% mouse driven. I'm not sure there even ARE any keyboard commands.
maybe I can spy into DOSBox and pull the X/Y coordinates out of the virtual mouse driver?
assuming the virtual driver even has an idea. the game might have put the mouse driver in relative mode, and is doing its own cursor movement nonsense
the worst part is that for greater reliability I'm restarting dosbox for every file.
so I can't even set up a standardized memory location to spy on, since it changes every time I reboot DOSBox
someday I'm gonna need to sit down and make my Universal External Debugging Interface.
A tcp/ip protocol for doing things like reading/writing memory and saving/loading savestates and providing input (keyboards, mice, controllers)
then go around and implement it in a bunch of emulators
so you could control an emulator from an external python script, or whatever language you want, so long as the UEDI is implemented
okay so I was able to find x/y cursor positions in RAM pretty easily. the trick is to do that in a way that can be done automatically/reliably.
or maybe I just need to stop restarting DOSbox.
I have a devious plan
so the mouse coords seem to consistently be at 2304500 bytes into the memory region where I find them, right? but that region moves around from launch to launch.
but it's also easy to find on the memory regions list:
it's 0x1001000 bytes long, which is a bit more than 16mb.
and guess what my dosbox conf says?
memsize=16 (mb)
so my script could launch DOSBox, then enumerate the memory regions allocated, and find the one that's 16mb, and that's the DOS RAM!

okay I confirmed this works manually. I can find the new DOS-RAM section, add 2304500 to it, and I get the x-pos of the cursor.

now to do it automatically.

I was kinda hoping there was an API that let me point at a process and go "hey what memory regions does this have mapped?"

NOPE

I looked up how cheat engine does it.
it's pretty simple.
you start at 0, and call VirtualQueryEx at that address.
then you increase your pointer by the size of the region you found there, and try again.

so you just call VirtualQuery repeatedly on a ton of memory addresses.

why not

windows is one of the most operating systems in all the world

got it. hacked a bit on this script and now I have a script that takes a PID and tells you the address of the DOS RAM in that process:

https://github.com/nccgroup/memaddressanalysis/blob/master/Windows/memanalysis.py

memaddressanalysis/memanalysis.py at master · nccgroup/memaddressanalysis

Research project related to memory address analysis - memaddressanalysis/memanalysis.py at master · nccgroup/memaddressanalysis

GitHub
and I have extracted all 306 distinct backgrounds.
the only problem? I don't have their palettes yet.
or rather, I do have their palettes, but I don't know which palette goes with which background.
here they all are, converted with a standardized palette.
most look "decent", but a bunch of clearly wrong.
so the game has some kind of "database" format, and it's in the .LST and .ALL files. I spotted it in ghidra, but I haven't tried to decode it yet.
the CB and CCS databases seem to reference palettes (or at least backgrounds), so one of them must be the datafile I'm looking for.

OH NO

I just realized my computer crashed earlier and I had ghidra open. I may have lost all my reverse engineering work ;_;

ahh, nope. it's at least partially here. cool.
they compiled it with some version of Watcom C from 1994. I should find that version and figure out how it encodes FILE* because I can't really tell what's custom code and what's statically linked stdio.h code

CCS I think is for scripts.

oh god I do not want to decode the scripting language this thing uses. it has a big interpreter here and it's huge

okay it's got a function that tries to find... three greys.
instead of putting them at standard positions in the palette, every time it loads a palette, it goes through all the palette entries and finds the color that's closest to that shade of grey.
The three shades are: black, rgb(161,161,161), and white.
huh.

I found the font renderer.
I wasn't even looking for one! this isn't a death generator thing!

but you can't stop Foone from Fooning

it calls malloc for every character it draws?

huh.

correction:
twice.

uhh.

I don't see any free()

@foone interesting way to prevent use-after-free 😜

I mean it's only allocating like, 24 bytes per character?
and this game has VERY little text.

but still.

you know you've been reversing too long when you have functions named stack_fuckery, more_stack_fuckery, and StackFucker9000
I should have kept my mouth shut, as StackFucker2TheReturn and StackFucker3ThisTimeItsPersonal just showed up

I think if you write a function like:

void debugPrint(char *message){
#ifdef DEBUG
fprintf(stderr, "ERROR: %s\n", message);
#endif
}

watcom will still compile it, to a function that does nothing, and it just does some stack manipulation and then throws it away.

and it looks like this game had a bunch of functions that only existed to do various things that have been defined out, so the machine code is full of these pointless functions that do fuck-all
StackFucker4BeyondThePortalOfTime
StackFuckerOrigins
StackFuckerForever
several of these are not even called from anywhere.
probably because they were originally only called from functions that no longer have any contents, thanks to a define.

this code appears to be strlen but it seems to count backwards from -1 and then inverts it at the end?

the fuck?

@foone I was about to type something like "I bet it is some kind of optimization for some weird architecture", but the more I read this code, the less it makes sense...
@foone Also... the thing is destroying the NUL terminator in the original string, which is also weird.
@foone that is truly cursed. Could it be some attempt at obfuscation, trying to hide the code that deals with anti-piracy or something like that? Or is it just weird compilers being weird?
@foone If my very rusty C doesn't let me down - the ~operator flips all bits, so a negative number becomes the positive counterpart but with an offset of 1 ...
Looks fun - where did you find that?
@mff I'm decompiling Star Trek: Deep Space Nine: Harbringer
@foone Presumably jump-if-zero is faster than an equality test to some other value and then jump-if-equal? But then why not start count at 1, increment rather than decrement it, and just return (count - 2) with no inversion?
@foone Is this some deranged magic to avoid setting the carry flag?

this is weird. the "Load_Dialogues" function tries to load .ccb files.

but there are no CCB files on the retail disc. Did... did they forget to include the dialogue files? IS THIS WHY THE GAME HAS NO SUBTITLES?

huh.
so the game has no "new_game" function.
instead, if you select "new game", it loads a special savegame named "new.can"
@foone I mean, /technically/ this is a new game. It's an odd way to go about it though! I wonder if it was a development decision to avoid having to recompile to tweak things.
@foone ooo I really like that tbh, will probably start doing this in my games :)
well now I've run into the minor problem where I can load the code in the dosbox debugger but debugging has gone non-linear, which is a problem. I hit "trace over" and it jumps to a different function completely.
I suspect this is because DOSBox is doing dynamic recompilation.
time to see if I can run this on slow-ass mode
Yep. I've got the palette names array now. Unfortunately it's just a bunch of pointers to strings, which I'd have to dump one by one, but I'm sure I can automate that somehow.
Also a problem is that this maps background numbers to palettes.
I don't know numbers, I know filenames
So I'm going to have to also dump the array of pointers to background filenames, so I can match them up
naturally, to celebrate this near-goal, memdumpbin has broken in dosbox

I run memdumpbin on what I can see in the debugger, it tells me "Memory dump binary success", and creates a file with 0 bytes in it.

weird.

@foone

Have you thought about using QEMU and GDB instead of DOSBox?

https://astralvx.com/debugging-16-bit-in-qemu-with-gdb-on-windows/

Debugging 16-bit in QEMU with GDB on Windows – Systems Research

@foone is this very uncommon? I've been playing with a point and click adventure engine of my own, and while there's no savegame support yet, that's one of the ways I had been thinking of implementing things eventually. Due to the way scripting works, with resources that can change depending on state, it feels like going through something like that simplifies the "reset" process

@foone Unconventional choice of 16-bit lengths (wait, is this 16 bit?), but otherwise I'd guess this is the equivalent of

for (unsigned int count=0; count<0xffff; count++) {
if (param_1[count] == '\0') break;
}
return count;

Except with the compiler changing array index to moving pointer and inverting the loop because testing for 0 is faster than equality. Used to be a pretty normal thing Borland compilers do, IIRC?

And yeah, 2's complement at the end is just a faster imul -1.

@foone strlen but it always returns even if there's no terminator found.
@foone I think thats a direct translation of an x86 instruction to C code, that has basically that exact behaviour, REPNE SCASB I think
@cheese3660 oh my god you're right. that's what the deal is!
thanks.
@foone @cheese3660 THANK YOU this was going to haunt me but sure enough!!!
@foone some kind of obfuscation?
@foone that actually sounds like a super clever way to get users to stop filing bugs complaining the compiler was deleting their functions
@foone Compilers will do that since it cannot know at compile time whether the function will be linked to externally. It could optimize out the call at the callers side but removing the function entirely would be on the linker (and normally would need a flag to remove unused symbols)
@ChartreuseK @foone Yep. However, marking it ‘static’ guarantees that it's not called elsewhere and then it does get deleted. (It also makes it more likely to get inlined.)
@foone games can have a little memory leakage, as a treat
@foone how the fuck did this game run in DOS? You'd think they'd show a little more care in such a restricted environment