I started reverse engineering Where in the World is Carmen Sandiego (Enhanced DOS edition) and I'm trying to find how it generates its random seeds so I search on int 1a and the first thing I find is it's doing TANDY SOUNDS?
Funny fact: I was trying to get an online assembler to spit out the machine code for "int 1a" but couldn't get it to, so I just went "fuck it, I can probably just do that in my head!"
Here's something I didn't know existed until just now: Where in the World is Carmen Sandiego checks your name against the list dossier list and rejects you if you use any of those names.
stretch goal: hack in at least on enby criminal with appropriate pronouns. maybe I'll just put myself in the game as one of the criminals you can apprehend
I think I might be able to do the hack I want by changing one byte.
I'm trying to change it so it has "daily challenges", and I think I can fix that by just switching a INT 1A from subfunction 00 to 04, making it seed the random function with the date instead of the ticks-since-midnight
why does ghidra's "search by instruction pattern" default to BINARY? what kind of a freak remembers the machine code for INT 21 on x86 in BINARY? it's CD21h, not 1100110100100001!
it's like "aww, did someone have second thoughts about making PRINT always take a length, and got tired of having to manually calculate lengths so you just wrapped it?
okay so when you start a game (well, technically when you restart), the game rolls 3 dice: 0-31: where the shit was stolen from 0-2: which item it is from that location 0-8: whodunnit
and select_string is a confusing function to reverse engineer, but knowing the name I gave it gives it away: it advances through the list until it's on the nth string and returns it
they have invented a Pronoun Markup Language. It's \x80 for He/She It's \x81 for he/she It's \x82 for his/her
so a string will be "\x80 mentioned \x81 liked seafood and offered me a ride in \x82 motorcycle" and it'll fill it out based on the pronouns of the suspect
I'm thinking I might do a "full"(ish) disassembly of this game. I've thought for a long while (basically ever since I knew Where In North Dakota is Carmen Sandiego? existed) that there should be an SDK for making your own version of this game, for whatever arbitrary geographical area you want.
I say "full" in quotes because I don't think I need to reverse the whole game to make it customizable, just enough to let you customize the locations, bad guys, hints, search types, etc.
sadly they didn't design the game as a completely empty husk that just loads datafiles. That would have been the smart thing to do, since they could then trivially make new versions.
turns out this version of the game has impressive support for older video cards. Here's Hercules support, which looks horrible without aspect ratio correction!
this blit function seems to take a useless first argument, a second argument that's the height, a third argument that's the width, and a fourth argument that doesn't seem to do anything.
I've been working on cities.dat. I can now confirm that this game (Where in the World is Carmen Sandiego Enhanced (DOS, 1990)) has 30 cities, and they're the same 30 cities as the 1985 original.
why in the fuck is loading the data for Paris suddenly grabbing some random data out of Kigali? this implies some weird things about the compression, or the data normalization
they seek to position X read 1 byte read 99 more bytes then seek to position X+100
now if you know how both math and random access files work, you'll realize something the programmers of Where in the World is Carmen Sandiego? Enhanced (1990, DOS) did not:
THEY'RE SEEKING TO THE POSITION THEY'RE ALREADY AT
the way this game does the investigations is interesting. so the basic gameplay is that you're in location X, you get 3 hints, which lead you to location Y, where the whole process repeats.
But if you savescum to experience the same pursuit again, they'll always go through the same places... but if you don't get the hints, they won't be there.
Hah! the game apparently calculates some info ahead of time, but only a few steps. I changed who the suspect was by memory editing, and it didn't take effect... until I got to the third location.
Since I went from a robbery by Fast Eddie B to one by Merey LaRoc, it means the pronouns changed when I got to London.
ok I ran my dosspin tool to gibberish every byte of the save game file (it's only 102 bytes, so this is easy!) and none of them change where you start. very interesting... I'm guessing either the values are spread out too much for my gibberishing to reach, or you need to modify multiple bytes at once
looking at interrupts, and I think I found a bug. they set handlers for various CPU errors, but they accidentally set 10 (COPROCESSOR ERROR) twice, instead of the 05 (BOUND check)/10 (COPRPOCESSOR) interrupts they save
okay I've figured out there's a shared format they're using here. it chunks the file into chunks, which have a 16-bit ID (unique per file, but not globally), an offset, and 16-bit length
so like, midisnd.dat will have 12 entries, and the first 11 are 200-500 bytes each, and then the last is 3k. presumably it's each song and then some config info?
idea for a test: it's easy to spot which chunk in a city is the image, because it's the biggest. Here's a way to determine if it's looking up by IDs or offsets/indices: swap the IDs of two images
okay I think it has a very simple 1-byte CRC check on the chunks, which are optionally not run. I can't make the math work but I'm reasonably sure that's what it is
ugh. TODO for my eventual Good DOS Debugger: Instant Video display. I don't know exactly how DOSBox-X is doing it, but while single-stepping the debugger, the display never updates. I can dump the ram at A000:0000 and see what updated, but not on the screen in DOSBox
I think this compression is specifically designed for ASCII text, which is annoying because they've also got compressed images... which probably use a DIFFERENT COMPRESSION!
"vs ses oa is isgit's tc eital and largest t u anhtA ttggh os nnotosnhrdsmarosogdn ss drte tishoth's isdhsceohtsnthminder of isgit's t nuorhdhtpast\x00 geru is slightltsn oaller than ndhd na and is o nnsgtgstbtst oa dotlalssaaolootbiaoht Sal gh, sonuhvia and sl ghh\x00isgit, ontvdn ss nhsiaalgarsnadlfnaatawlarst oadrlhrs i is a rugged land dooousr'casrbhe nrdsgs fountainsnht iah"
that's supposed to read: "\x03Lima is Peru's capital and largest city. A well-known landmark is the Archbishop's Palace, a reminder of Peru's colonial past\x00Peru is slightly smaller than Alaska and is bordered by Ecuador, Colombia, Brazil, Bolivia and Chile\x00Peru, once the center of the mighty Incan Empire, is a rugged land dominated by the Andes Mountains. Forests and jungles cover half its land area\x00"
C:\DOSBox-X\drive_c\carmen\py>python datfile.py cities.dat --dump=12803 --decompress "\x03Sydney, with a population of more than 3.3 million people, is Australia's largest city. A well-known sight is Sydney's distinctively designed Opera House\x00An island continent, Australia is nearly as large as the United States but has only one-fifteenth the population\x00The capital of Australia is Canberra, located in the southeast corner of the country between Sydney and Melbourne\x00"
It starts with \x03 to indicate there's three strings: then it describes the city three times. at runtime it uses select_string function with a random input to select one of the three strings
b'\x05asked about the exchange rate for yen\x00was practicing Japanese characters\x00said\x81planned to take photographs of Mount Fuji\x00asked about tours of the Imperial Palace\x00was interested in visiting Shinto shrines\x00'
tool that'd really be handy right now: a "live" version of binxelview, so I can step through the DOSBox-x debugger and see how memory is changing in real time, as an image.
I'm stepping through a high-level loading routine I don't understand yet, trying to figure out when it decompresses an image by watching the RAM it uses for file loading and decompression and spotting when the image appears
I'm in Paris, I look at work ram, I see the image of the Eiffel. I head to Rome, and before I load the next image, I can see that the Eiffle tower in workram now has the wrong stride. That's odd, because it means it had to rewrite the image in memory, the image it's about to unload.
I think this might be the GUI system doing a screenshot of the image under a window, so it can restore it at the end. And it still does that here, even though we'll never need to restore that image: we're about to overwrite it
Here's what I want a tool to do: I hit a breakpoint in the debugger, I turn it on, set another breakpoint, and hit go. between those two breakpoints, every time a CALL instruction is hit, it dumps my selected memory region. If it's identical to the last dump, it's ignored. At the end, each dump is rendered as an image, and the combined set are an animation I can scroll through.
wait no, the colors are wrong... I bet I'm seeing it decompress the binary, but that's using the full width of the bytes. it then gets expanded out to a 16-color image.
I think the memory allocation system here is that every malloc returns 2 extra bytes, which is a pointer to the previous block. unless it's an odd number, in which case it's a free block. and pointer to the previous block, once you make it even again
the only problem with using Ghidra to hack children's games instead of, like, Serious Things like firmwares or malware or whatever, is sometimes you have to make a label named NUM_MOUNTAIN_CLIMBING_HINTS
man, running on 4 hours of sleep is killing me. I can't even remember the MS-DOS interrupt to open a file!
I know reading it is int 21 ah=3f, closing it is int 21 ah=3d, and I'll never forget that seeking is int 21 ah=42, but how do you open a file? I mean, not the int 21 ax=6c00 way, that one is only for DOS 4.0+, and obviously a game released in 1990 isn't gonna use that.
the annoying thing is that MS C Compiler 5.1 is the most mundane-ass DOS application. If I had a 32bit windows install rather than 64bit, it would probably just run natively on my system
a fun kind of reverse engineering tactic that I practice probably more than I should is a version of The Scream Test (which is the principle that the easiest way to find who "owns" a server is to turn it off and see who screams): if you don't know what some code does, break it. and see what screams.
I think I may have found unused graphics for a feature that'd change the Acme Detective Agency at the beginning to be season-specific. There's summer, fall, winter, and spring variants, but the game seems to be hardcoded to summer
I accidentally applied a patch backwards and put the detective to sleep, forever. They're in Rome and they've just slept through about two months of nothing
I finally figured out how it calculates travel times. It's the difference in X coordinate between the two cities, plus the difference between the Y coordinate, plus one. that quantity divided by 40, then has 2 added. if the result is over 7, it's set to 7.
Weird! that's not how you measure distance, Carmen.
also, it's the 90s, I can afford a sqrt(). I should fix it up for my version.
or use a squared lookup table. you could do this REAL easy by making it a table search: there's only 6 possible results: 2,3,4,5,6,7. each entry in the lookup table contains the maximum squared distance that can generate that number of hours
@foone I did it lol https://docs.google.com/spreadsheets/d/e/2PACX-1vTr5GBfUNkxzVN7RoAiMg081ihERAFs93oE_m_1dPj67IxG-pkMQ41zoVVDlK0DJQepfeRe3NcA-9z8/pubhtml
hope this answered any questions you had
I'm confused by the graphics detection routines. I thought it was returning 0 for "no graphics" or something, but it turns out 0 means MCGA. So the GraphicsMode enum goes: 0: MCGA 1: CGA 2: Hercules 3: EGA 4: Tandy 5: VGA 6: ???
I just realized the portable Where in the World is Carmen Sandiego? is based on the same version I'm hacking, meaning it's in-scope for me to get this, dump the ROM, and compare.
That just increased the cost and complexity of this project by bunch
WAIT HOLD ALL THE PHONES. Here's a photo from a MS-DOS version. It does that thing some companies (like Sierra) did back in the day, and included both 3.5" and 5.25" disks in the package.
I don't want to go through a million platforms but all the other ports of this game tweaked some art here and there or put in different location-photos, but all of them have the same basic tall-window-on-the-left, smaller-window-in-the-top-right, four-buttons-in-lower-right design
The answer for "what's wrong with these floppies?" is that they're double-notched. That's needed for double-sided disks... on systems which have single-sided drives! The PC has basically always been double-sided, so they only need one notch, on the top/a side.
here's why they shipped it on a double-notched disk anyway: Broderbund was releasing games on a bunch of other systems that DID have single-sided drives. For simplicity they just bought Xty-thousand double-notched disks
is it gonna matter? not in the slightest (assuming there's no format-mismatching, which their shouldn't be: these are all the same density of disks, I think).
The PC doesn't check for a notch there, so it won't notice either.
also after all this wondering about "how many disks does Carmen Sandiego Enhanced (1990, DOS) come on?" is even sillier because I ALREADY KNEW THE ANSWER, I JUST FORGOT I KNEW IT
just looking at the files, not the code (and not having seen original disk images yet that I can recall), I bet the answer is that they put CITIES.DAT on DISK2. the whole game - cities.dat is ~300kb, with cities.dat being 168kb.
They could do the whole game - carmen.dat and cities.dat in only 200kb, which'd give them 160kb (luxury!) for a fancy installer.
This game autodetects everything (video and audio modes) and you can install it by just doing "copy A:*.* C:\CARMEN" on each disk, so I don't think they would have needed a fancy installer.
I should just check. I'm sure disk images can be tracked down in places.
the video and audio detection seems to be excellent, by the way. it just silently figures it out, without asking questions or requiring special arguments or configuration. Perfect for a game aimed at the little childrens.
I did not realize they implemented a file browser in this program! I only found it by hiding all the DAT files from the EXE, to see if it'd ask me to put in floppies in.
similar things in the test.com file. I moved stuff around in the memory map and it's not erroring now. I've probably created endless glitches elsewhere though
okay I reverted back to my old mapping, then created a new memory mapping: I made up some bytes at 2000:xxxx where it incorrectly thinks it's going, and set up a JMP $CORRECT_ADDRESS there by editing the bytes, then telling Ghidra it's a thunk.
so the program has three main code segments, as it has approximately 111kb of code The problem is that ghidra gets confused when the relative addresses are too big.
so the first one is at 1000:0000 and the second was at 1fb7:0009. I moved it to 5000:7000, and the second segment seems to be working fine now.
the problem is that I was only able to do that because the segment is only 82a7h long. the first segment, the 1000:0000 one, is FB79 long. So I can't just move it so it's in the middle of a segment, since it'll end up spanning into the next 64k chunk, which is where ghidra fucks up
well, if nothing else, I think this has caused it to stop thinking there's jumps into the middle of functions. so now I can just manually thunk every cross-segment call, by creating the 2000:0000 segment that ghidra is imagining exists
I'm kinda surprised they're so dithered. with the support for EGA/MCGA/VGA monitors, they could have pulled something like sierra did and encoded the dithering into their compression. Then when they're displaying on higher-colordepth displays they could swap it out for an intermediate color.
Since they devoted an entire word to gender, we can truthfully state that Where in the World is Carmen Sandiego? (enhanced, DOS, 1990) believes there are 65536 genders.
BTW, my plan for expanding the program is simple: I'm gonna bypass a lot of code/data, by stuffing my own allocation into the memory space of carmen, which'll load extra data off the disk, in a CUSTOM.DAT file
this'll be (relatively) easy to do, since it turns out this program only needs 432 KB, since it targets a 512 KB RAM machine. Since it's no longer 1990, I think I can safely bump that up a bit? I won't need more than another 64 KB, which means I'll just bump the game up to 496 KB memory required. Completely doable in any 640 KB or more machine!
The way it actually works is the game uses "He/he/Him/him" for the pronouns, so \x80 is uppercase "He", \x81 is lowercase, \x82 is uppercase "Him", and \x83 is lowercase.
trying to figure out how to properly decode the fonts in this game is REALLY reminding me why I constantly cheat with The Death Generator. Staring at a decompilation/disassembly and hex editor is no fun
I got my floppy copy in the mail, I just need to image it.
Fun fact from the box: It has a letter from the player character to their cousin, and I believe this is the only place in the game and associated media that they name your character.
well my "ignore the problem" solution of using bochscpu to embed a 16bit x86 emulator has failed. it's somehow broken and it's broken in the rust library or C core, not the python, and I really don't want to have to deal with debugging this.
time to switch to a completely different x86 emulator? PROBABLY!
I'm implementing unicorn as an x86 emulator to do the decompression, but I'm single-stepping the processor and I'm aiding debugging by showing what instruction I'm on.
but instead of having to set up an x86 disassembly engine, I'm just parsing a plain text ghidra dump of the disassembly. I'm parsing it with regexes
to avoid the complexity of generating functions and mapping them into the address space of the emulated PC, I instead designed a simple syntax:
a 16bit segmented address plus a number. that function is emulated as if it returned that number in AX. There are no other options. I suspect I'll be able to emulate up to 80% of complex subfunctions with this one bit of functionality
I forgot about callee cleanup. fucking stdcall is callee cleanup. I can't have a generic int blah(){return 0x1234;} function because it needs to know how many words of arguments were pushed.
I took a look at the 1985 version to see if it had any other graphics command line options (it doesn't), but I did discover in passing that it uses a different pronoun system than the 1990 Enhanced version!
hacking a computer system by changing my pronouns to they/them so that it'll use up more memory composing strings referring to me and overflow the buffer
Where in the World is Carmen Sandiego? (1985) has an invert-y-axis option for the joystick, just in case you want to use flight simulator controls to navigate a menu
My best guess is that this game has between 4-6 compression algorithms, depending on how you count them. Possibly more are hidden in the bowels of this program.
this is not the game to do it with, but I really wanna try swapping out the drawing routines for one of these games once. go into a VESA mode where I can run at 1024x768 or something, and just make the drawing write to that buffer instead. Could I make BIGSCREEN DOS GAMES?
maybe I'll try it with railroad tycoon sometime. that game has loadable graphics modules. if I figure out enough of how it works, I could write my own driver for VESA Railroads
You also have to remember that it's not going to succeed more than, like, 6-8 times? There's just not that much memory in the system that this can touch, since it's not supporting any of the endless varieties of breaking the 640k barrier
there's a story on Old New Thing somewhere about Windows 95 accidentally breaking a DOS game, because it did this same trick of allocating all the memory, but since Win95 was running as the DPMS, it meant it had access to all of windows 95's virtual memory. including the swap.
So instead of mallocing all 8mb or whatever your 486 had, it malloced all that and then tried to use up YOUR ENTIRE HARD DRIVE, slowly.
And then it crashed because it didn't expect to succeed that many times. It had a fixed array of handles to memory, and it overflowed because it was run on a system with HUNDREDS OF MEGABYTES OF RAM, which is clearly impossible and unthinkable
I think the solution was that win95 just defaults DOS programs to maxing out at 16mb. It won't let them allocate more than that unless you adjust the EXE options
I'm not sure if anyone has ever designed a paletted graphics system that uses more than 256 colors. Probably at some point someone thought it was a good idea.
This game was released in 1990 but it has a hint that refers to the currency of Brazil as the "cruzado". But in 1989, it had been replaced by the cruzado novo. Clearly someone was using an out of date encyclopedia!
so I fly into Reykjavík, and immediately sleep for 8 hours. In the morning, I can go to either the airport or the hotel, but it'll take 3 hours to get to either.
Question: where am I right now, if I'm not at the hotel or the airport?
I mean, if your hotel is near Reykjavik or Keflavik, then you could be in one of the "wild" hot springs out in rural Iceland, like the one near Hella. That's where I'd go, anyway!
in a bed at the hotel attached to the airport. You could theoretically walk to the hotel lobby or the airport terminal in, at most, 15 minutes. You just have ADHD and are attempting to be realistic in your expectations. (don’t worry, me too)
Ah, now, that one I can answer from personal experience - interviewing for my first job, the company put me up in a hotel overnight beforehand. Problem: they accidentally booked me into the hotel chain's branch in [suburb], and were then confused when they arrived at the airport hotel to interview me. The shuttlebus back to the airport had just left when we twigged it, too, so it took a good hour+ to make my way over there...
They initialized the SoundBlaster DSP backwards. You're supposed to send a 0 to the reset port, wait 3 microseconds, send a 1, then wait up to 100 microseconds for an 0xAA to show up on the data port.
They instead send a 1, then a 0, then immediately start trying to read the data port.
they read from the ports instead of measuring time, because that'll take a certain amount of time on x86. I'm too tired to confirm if their timing logic is sound. It's possible they're just assuming the PC is slow enough that it'll wait long enough
I appreciate your lack of judgment. Back in those times it might have actually been a totally reasonable assumption. I mean, better than spinning the CPU, the totally next level timing delay mechanism of the times.
@tekhedd oh yeah, it made sense back in the day. that's a valid method of timing, I'm just not 100% sure they're actually matching the docs on how long they should wait, especially on later and faster systems
I suspect there may be an issue here: I identified a variable as containing the Soundblaster IO port, right? and I'm assuming everything that uses it is Soundblaster code.
But it may just be "soundcard IO port" and there's other sound device code mixed in here. So that's why some of it doesn't make sense as soundblaster, it's actually tandy 3voice or something
That reminds me of a game (I think it was Space Crusade) which was very glitchy on my family’s 486 DX2 66 and eventually led to me discovering a use for the Turbo button that dropped it to 7Mhz (or so the seven segment display on the front claimed).
I did some experimenting with MSVC 5.1, and it's weird. I get the same strings in the exe as carmen.exe has, but the code itself looks completely different.
either I set up my compiler wrong, or this game is full of assembly even for very simple functions
I don't know exactly what this function does (I know it sets some flags based on something in the graphics context) but I DO know one important thing about it:
wait I bet it's drivers! like, one version of this function is called by VGA_DrawFuncUnknown and nothing else. Another one? CGA/Hercules. the third? EGA The last? Tandy.
They compiled the 4 video drivers separately, and then linked them into the EXE, with no deduplication across compile units
yeah. Found another: VGAMalloc is the same as CGAMalloc (and Hercules doesn't have it's own HerculesMalloc, because it's in the same code unit as CGA: So it just uses CGAMalloc) Tandy has TandyMalloc.
But not EGAMalloc. That one is completely different.
it's also off by one. because 0,0 is silly, you're always drawing at least one pixel. So DrawLine(0, -5) draws a six pixel wide horizontal line to the left
the internal audio API used by this game is interesting. LoadAndPlaySoundChunk is called with a chunk name from digisnd.dat, but you can also pass -1 or 0. I'm not sure what -1 does yet (maybe silence a currently playing sound?) but 0 means "wait until the sound finishes"
I thought it might just be playing from MIDISND.DAT instead (since the computer noise is very beepy, maybe it's just a synth sound?) but MIDISND.DAT starts at chunk id 218 and goes up.
huh. weird. when you try to backspace too far in the name entry screen, it goes "duh-nuh" at you, but that isn't connected to a LoadAndPlaySoundChunk call.
So it's using a different function for this ONE NOISE?
maybe it's hardcoded to pc speaker and I can't tell the difference between soundblaster and pc speaker because they're both coming out of the same laptop
the way the game works is that it loads CARMEN.DAT always, then if you have a sound card it supports, it loads DIGISND.DAT which replaces chunks 200-216 in memory with the DIGISND.DAT ones, which are PCM. But if you don't have a sound card, it still has the CARMEN.DAT ones loaded, and they're all pc speaker sound effects.
I think they generated their hints wrong. The *22 chunk for a city says something like "$SUSPECT was going to an opera with the president" or "$SUSPECT would be having tea with the Emperor", right?
but it's also got "drove away in a vehicle flying a green, blue, and yellow flag". which'd be fine, except that hint is also in *19!
I think they accidentally duplicated it when they generated the cities.dat file
I might have explained this before, but normally a near call to a far function will break, because it'll pop 4 bytes off the stack for the return address, when the near call only pushed 2.
So you fix this by doing push CS first, so it'll pop the 2 from the call, and then the 2 you placed before.
in 32bit we do 32bit calls and 32bit returns. in 64bit we do 64bit calls and 64bit returns.
in 16bit we can do 16bit calls and 16 bits returns, 32bit calls and 32bit returns, and sometimes we do a 16bit call to a 32bit return because it's slightly fewer bytes
Actually, it supports EGA/CGA/Hercules/MCGA as well, so I could definitely try running Carmen in MartyPC. Maybe next session, I'm already halfway through a lot of nasty stuff
@gloriouscow I'm specifically thinking of logging breakpoints of the sorts where you can attach an expression. So it's not just 0823:A35C DrawString, it's something like 0823:A35C DrawString(25, 60, "Foobar"), because you can define it with some kind of simple expression language. like I tell it when it hits 0823:A35C, it treats stack[4:6],stack[6:8] as ints, and stack[8:10] as a string. OllyDBG and X64dbg do this, and it's very handy for understanding more complex code
1. This is a flat-earth-ass flight path. Apparently Where in the World is Carmen Sandiego? takes place on a rectangle-planet. 2. the world doesn't wrap. This path is longer than "just" crossing the pacific, which is how this flight actually goes.
I wonder if an easy fix would be to have 3 maps, centered on the US / Europe / Asia, compute the linear distance on each and use the map with the shorter distance and most centered.
this also tells me that I'm probably right about this game being primarily written in C, but I'm wrong about which compiler was used. MSC5.0 doesn't match: it saves DX, when the setjmp used in carmen doesn't.
okay it looks closer, but it doesn't exactly match. I think it's just that I'm in the wrong model. I only have Small installed. lemme find the floppy disk to install Compact/Medium/Large
after extensive cross-referencing with the msc5.0 manual and the msc5.1 libraries being opened in a parallel copy of ghidra, I have finally been able to determine that the function I named sprintf_maybe is, in fact, _sprintf.
I'm currently figuring out functions through the amazing insight of "the linker is simple and linear"
which means when I have _memmove, FUNC_1fb7_6db0, and _strcmp in the EXE, FUNC_1fb7_6db0 is probably not going to be an adlib sound driver. it's going to be something from the libc.
I've done this a lot especially in embedded stuff where you have an open source peripheral library or RTOS bolted onto closed source application code.
Find one xref to a SFR, match to the corresponding vendor HAL function, then you probably get 30 functions with minimal effort that are right before/after in the same order as the .c
okay I've got all the libc stuff named, other than some internal functions (which I don't have names for), and one weird memmove-ish function that I just named "memmoveish"
it looks very similar to memmove, but with an extra check or two, but I can't match it to anything in the library
1991's The Treehouse uses DAT files, with some of the same names as carmen... but my parser fails on it. I think it's a variation in the format, so I'm a byte off or something
You know what time it is, then? That's right! It's time to spend ten times as much time as you wanted to spend on this project making your own tools :'D.
I'm getting some crashes. I think I'm gonna switch away from CFFI to just making a C wrapper around the code, and subprocessing that. That'll make it easier to debug why it's crashing
The misplaced entries (like Cairo having a leader hint of "left in a vehicle flying a red, white and black flag") are like that in the original data files. Brøderbund just got their hints miscategorized sometimes.
I'm like 90% sure that this game actually matches building types to what sorts of hints it gives you, and I'm also like 90% sure that this should have been obvious to me long ago
Idly playing Where in the USA is Carman Sandiego, and found an unexpected example of "things that have changed since 1990": The IMAGE for New Hampshire! It's the Old Man of the Mountain, which collapsed in 2004.
NH still uses the image all over everything. its such a funny metaphor for NH in general. Clinging on to the glory of the past despite its complete irrelelevence today. I live there....
arg the way this game does travel can be really annoying if you are in New Delhi and need to go to the USSR, but misclick on Oslo instead of Moscow, you can't just fly to Moscow from Oslo. You have to go back to New Delhi first
idea for debugging feature for dosbox: press a button, then for the next X seonds, all modifications to the display memory are recorded along with the backtrace of what code changed it. So you could see a button get drawn, and check what code did that.
oh good lord. when you open the Hall of Fame window, it paints the background light blue, then loads the background image which overwrites the light blue with dark blue
POP QUIX: The usual way to zero out a register on x86 is XOR AX,AX. This'd be only 2 bytes (31 C0). The compiler knows this. Why didn't it use XOR AX, AX here, instead of the bigger MOV AX, 0x0?
I named this variable SoundBlasterPort but now, thanks to crossreferencing with the Prince of Persia disassembly, I know it's actually sound_blaster_port
Total funcs: 762 Unnamed funcs: 293 % named: 61.5%
118 of those named functions have been marked as identical to ones from Prince Of Persia (or vice versa... I have no idea which game had this code first)
Prince of Persia -> Where in the World is Carmen Sandiego (enhanced) -> Where in the USA is Carmen Sandiego (enhanced) -> Galleons of Glory: The Secret Voyage of Magellan
the game picks between "they flew off to X" and "they drove off to X" and "they rowed off to X" and "they sailed off to X" but it doesn't seem to do this with any smarts. or if it does, the database is incorrect.
The game also refers to the capitol of china as Peking, which is weird considering it's been Beijing since 1945. I know it took a long while for everywhere to catch up, but by 1990 pretty much everyone was using Beijing. I guess they used an old atlas?
only a few years ago I told my mother that Beijing and Peking were the same city and she was surprised. I imagine thinking they’re two different cities is quite common.
I'm experimenting with a way to show how DOS games render themselves. Basically I'm recording a lossless video of the game running on a very slow CPU, then removing all the frames where nothing happens, and I'm playing it back sped up a lot.
The highlight of this video is how terrible the handling of the mouse cursor is! it's getting peeled and restored constantly
the mouse cursor appearing and disappearing is because they don't have multiple frame buffers: they have to hide the mouse cursor before they can draw anything, or the cursor would corrupt the newly drawn stuff if it happened to be over it. so they solve this by hiding the cursor before every drawing command and showing it afterwards.
but instead of doing it once per screen, they're doing it once per command.
This normally would be invisible because all this happens over a single frame (or a couple), but running this slow makes it visible. the GUI system they're using (I'm just calling it the broderbund UI in my reverse engineering work) DOES support avoiding this mess: you can tell it to hide the cursor, then when each sub-command tries to hide/restore it, it stays hidden, but they're not using it here.
ghidra (at least in x86-16bit) mode, has a real annoying bug where it decides instead of just passing a pointer-to-struct as an argument, the code is passing a pointer to the first member of the struct, just cast back to a pointer.
right after this it checks if the mouse is even enabled (hey, it's 1990, not everyone has a mouse!)
I'd think that you would check that before you try to hide and redraw the cursor, but maybe this is exactly why I'm not employed writing educational games in 1990?
LES loads the far pointer at MousePos.x into the segment selector ES and the register DX. This is a far pointer in segmented mode, a 16bit segment selector plus a 16bit offset, like ES:DX or SS:BP or DS:1234
I wonder if this compiler is smart enough to do this or this is the ghostly hand of the most dreaded adversary of reverse engineers: HUMAN WRITTEN ASSEMBLY
so I think digipres.club/@foone/114611650… does make some sense: it's hiding and redrawing the cursor because it already moved the mouse position, and wants the on-screen cursor to match up .
so the code that uses the soundblaster auto-detects the IRQ in use by simply setting up a handler for every possible SB interrupt, then asking the SB to fire an interrupt. then it sees which one triggered.
Makes sense, but I wonder why this wasn't universal? why were programs always asking me which IRQ to use? maybe this isn't compatible with less-accurate SB clones?
the menu items 1-indexed, sort of. it treats 0 as "the whole menu itself"
so like: set_menu_enabled(TRUE, 1, Menu_Game); sets the first item in Game to enabled, but set_menu_enabled(TRUE, 0, Menu_Game); sets the whole Game menu to enabled.
the second thing the main() does (after setjmp) is try to unload the game.
this is a side-effect of how they're using setjmp to make main() a sort of event handler, so when they need to load the game's resources they don't know they're not already loaded, so it first tries to unload them, fails because they're not loaded, and THEN loads them
@KeyJ this code does specifically turn off the printer/serial port IRQs while it's testing, but I don't doubt they could have easily missed one possible source of IRQs
The logical explanation is that the mouse position is updated outside of the drawing loop, for VBL reasons. So clear must refer to the last drawn position. (Ask me how I know!)
ohh I wonder if I could do this for my research for my video on blobbers 🤔 there's actually very little information out there on how to optimize the drawing
Are you just setting the emulator to very low cycles?
@eniko yeah! I'm setting the cycles down to 15-50 (depending on the game) while recording, then I ffmpeg it out to individual PNGs so I can deal with it as frames
well i know one of the big ones is lands of lore, because it doesn't just draw the scene but it also intersperses critters into it
i spent hours looking at the source for the scummvm engine but it's almost entirely uncommented and they do stuff like bitwise ops using raw numbers everywhere so it was just too hard to follow and figure out what was going on :/
@eniko Is the back buffer of double buffered rendering code always in the same place?
I would've presumed it would depend on the given software's compilation or freed memory, though I'm more of a higher level programmer in that it's kind of hidden from the drawing cycles I've dealt with.
@AT1ST @eniko yeah it depends on the game and what graphics hardware it targets. but it's usually easy to figure out where it is:
set a write breakpoint on the visible screen, and when it's hit, you now know what code writes to the screen. probably that's just a memcpy from main ram or vram, depending
It's still "Peking" in German, for example. Reading the German Wikipedia etyomolgy section, that follows a Chinese postal romanization, which, apparently, fell out of use between 1980s and early 2000s. en.wikipedia.org/wiki/Names_of… suggests that, yes, mid-80s would be a typical swich time. Maybe the editor was German?
@StompyRobot yeah and that's the sort of thing I'm doing now. but it's not quick to calculate the address of the screen and then set a breakpoint. I'm thinking of an overkill tool that'd make it super quick to do this across many places in a game
I wonder how it even came to be, maybe one of the 37 assorted cancelled PCs Apple engineers were working on was one with a MIPS CPU or something, maybe something related to Rhapsody
@CutInBismuth I think it makes some images marginally smaller to compress them in a different way, so they swapped back and forth depending on which compressor was better for a given image
I had the original Prince of Persia on my then top of the line Amstead PPC. It was a groundbreaker. I think the first to use a form of motion capture to create the character movement animations.
I nearly named my kid ??2@YAPAXI@Z. never say we don't hold tight the scars of these compilers; authors of the many, many tomes of our chosen misfortunes
Wait, doesn't C have a goto command that you can use with text labels? I seem to recall it, my professor, having a discussion with us about when it was okay to use go to, as well as the dangers of it in my first year C class, many years ago
479K likes, 2,997 comments - cobrakaiseries on November 28, 2024: "Johnny’s a point A to point B type of sensei. 👊 Never let a "curvature" slow you down.".
for a game ostensibly teaching geography that's horrendous! (Otoh for intending to run on home PCs with 8088, 386, 486sx (emulated floating point only), that might be excusable? CORDIC integer trig is amazing but avoiding it might be forgivable.)
I honestly expected this game to be written in ASM and now follow any calling convention? Or maybe things were less chaotic than I really knew back then?
@montyontherun it's partially in ASM (although possible just some included libraries) but it's mainly C, since I can tell it was (mostly) compiled with Microsoft C 5.0
Working on some compression tricks, I am assuming 10 bits per symbol, in order to be able to preprocess 8bpp graphics with delta compression in both x and y. And I'll have 3 symbols left just in case (for end of stream markers or other tricks).
I had a client that used the computer mouse inverted.
But not in software. I mean physically turning the mouse around 180° and having the cord come out where, uh, the tail would be, I guess.
(Now that I put it like that, it makes sense why someone might do that.)
The end result being both X/Y axes are inverted, and the buttons are in the wrong place.
But it *worked*. That's the way the brain learnt it. It's like spending your whole career in ETAOIN SHRDLU only to discover the rest of the world is on QWERTY. You'd probably just stick with what works.
@onfy They did make the RS/6000, including that one weird PowerPC ThinkPad, so there's that…
All the RISC computer manufacturers just couldn't get rid of the idea that *their* workstation will sell despite being so expensive and they'll get rich despite lacking any plan for Step 2 in their 3-step scheme. Shame.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Funny fact: I was trying to get an online assembler to spit out the machine code for "int 1a" but couldn't get it to, so I just went "fuck it, I can probably just do that in my head!"
Turns out I can. My brain is weird.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •PRONOUNS DETECTED: THIS GAME IS WOKE
sadly they don't have they/them on here. What about the non-binary criminals, huh?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I think I might be able to do the hack I want by changing one byte.
I'm trying to change it so it has "daily challenges", and I think I can fix that by just switching a INT 1A from subfunction 00 to 04, making it seed the random function with the date instead of the ticks-since-midnight
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •NORMAL CODE
random(*(byte *)*(undefined2 *)
(*(int *)(*(int *)0x39a6 * 0xe + local_c * 2 + 0x1d02) * 2 +
*(int *)(local_c * 2 + 0x24b)) - 1);
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •male: 0
female: 4
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •why? because they have strings like:
char* HE="He\0\0She\0"
char* HIS="His\0Hers\0";
char* HIM="Him\0Her\0";
so they can do like:
printf("Follow %s to %s lair, and capture %s alive!", badguy->name, HIS+badguy->gender, HIM+badguy->gender);
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Player's don't have genders. Only thieves have genders.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •why does ghidra's "search by instruction pattern" default to BINARY?
what kind of a freak remembers the machine code for INT 21 on x86 in BINARY?
it's CD21h, not 1100110100100001!
what are you, some kind of nerd?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I love reversing a string and it's:
void printString(char* str, int length);
and I go look what calls it, reverse that function, and it's:
void printStringSimple(char *str){
printString(str, strlen(str));
}
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •it's like "aww, did someone have second thoughts about making PRINT always take a length, and got tired of having to manually calculate lengths so you just wrapped it?
and your compiler didn't inline SHIT?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •0-31: where the shit was stolen from
0-2: which item it is from that location
0-8: whodunnit
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •For the second one, it's:
0: mask of Priam
1: Achilles's heel
2: sibyl's secret.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •It's got 1 added to it so you won't get Carmen Sandiego, as a rookie at least.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •so the game uses a pattern like this:
char * RANKS="Rookie\0Sleuth\0Private Eye\0Investigator\0Ace Detective\0"
and then latter they do:
char* your_rank = select_string(RANKS, player->rank);
and select_string is a confusing function to reverse engineer, but knowing the name I gave it gives it away: it advances through the list until it's on the nth string and returns it
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Ghidra is officially sexist. It'll automatically detect the word "Female" and mark it as a string, but not the word "Male"!
Why? SEXISM!
or the fact the default minimum length for strings is 5 characters, so "female" is long enough but "male" isn't.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •they have invented a Pronoun Markup Language.
It's \x80 for He/She
It's \x81 for he/she
It's \x82 for his/her
so a string will be "\x80 mentioned \x81 liked seafood and offered me a ride in \x82 motorcycle"
and it'll fill it out based on the pronouns of the suspect
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •in trying to hack myself into the game, it glitched and said I had "Hobby: Male"
no... I haven't done that in ages!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •so in addition to the 5 listed attributes (and their name), the game tracks one hidden attribute:
food preference.
There are only two options:
00=Mexican
01=Seafood
what an odd binary
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •You could always do, like, "Where in Middle Earth is Carmen Sandiego?"
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •so when the game starts, it loads:
ACME.DAT
CARMEN.DAT
MIDISND.DAT
DIGISND.DAT
CITIES.DAT
Interestingly, it uses the same code to load the last three, suggesting they're some kind of basic container format
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •this blit function seems to take a useless first argument, a second argument that's the height, a third argument that's the width, and a fourth argument that doesn't seem to do anything.
notice anything missing? like... a lot of things?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •this game uses a fun text encoding method: both-ended null terminated!
It stores city names with a nul at the beginning because it reads them backwards. For some fucking reason.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •they seek to position X
read 1 byte
read 99 more bytes
then seek to position X+100
now if you know how both math and random access files work, you'll realize something the programmers of Where in the World is Carmen Sandiego? Enhanced (1990, DOS) did not:
THEY'RE SEEKING TO THE POSITION THEY'RE ALREADY AT
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •the way this game does the investigations is interesting.
so the basic gameplay is that you're in location X, you get 3 hints, which lead you to location Y, where the whole process repeats.
But if you savescum to experience the same pursuit again, they'll always go through the same places... but if you don't get the hints, they won't be there.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Hah! the game apparently calculates some info ahead of time, but only a few steps. I changed who the suspect was by memory editing, and it didn't take effect... until I got to the third location.
Since I went from a robbery by Fast Eddie B to one by Merey LaRoc, it means the pronouns changed when I got to London.
Congrats on coming out as a trans woman, Merey.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •ahh good. it's always fun to find code that looks like:
do{
while(variable!=0);
some one has a custom tick handler that's permutating a global!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •looking at interrupts, and I think I found a bug.
they set handlers for various CPU errors, but they accidentally set 10 (COPROCESSOR ERROR) twice, instead of the 05 (BOUND check)/10 (COPRPOCESSOR) interrupts they save
someone copy-pasted and missed a bit
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I finally found the two helper functions they use to get and set vectors!
all the 30 other places I've seen them set/get vectors, they do it manually, but hey, maybe they use the helpers too
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •presumably it's each song and then some config info?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •cities.dat is very interesting. There's 30 cities in total, but 491 entries in it!
So they must be doing something odd there, that doesn't divide equally. Maybe one city-chunk gives IDs of the others?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •darn. turns out you can't just renumber the chunks, because they have to be in increasing order.
so maybe I just need to leave the chunk indexes as is, and instead of moving the entries around, I move where they're pointing?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Bingo! I'm in Athens, but I'm seeing the image for Baghdad, and apparently with the Baghdad palette?
So one of these other chunks must be the palette for a city. Or it selects from a selection of palettes? Maybe they've just got a couple defined.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •okay I figured out the cities.dat IDs:
They're all 1XXYY (in decimal):
XX is the city number (0-29), YY is the sub-chunk-id.
So like:
YY=0: City name
YY=2: City image.
They go between 00 and 22, and not all numbers need to be present.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •hmm, reading a buffer and then summing all the values of the bytes in it.
suspicious behavior.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I can't make the math work but I'm reasonably sure that's what it is
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Instant Video display.
I don't know exactly how DOSBox-X is doing it, but while single-stepping the debugger, the display never updates. I can dump the ram at A000:0000 and see what updated, but not on the screen in DOSBox
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •found a suspicious array, which goes:
[
(-1,0),
(-1,1),
(0,1),
(1,1),
(1,0),
(1,-1),
(0, -1),
(-1,-1),
(0,0)
]
POP QUIZ: why does the font renderer need this array? how are they being "lazy" with this array?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Sensitive content
there's also this code in the for-loop that steps through this array:
if index==8:
color=white
else:
color=black
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Sensitive content
@dividuum got it:
they're drawing the font 9 times, offset in each of the 8 directions, and in black. then they draw it in white, with no offset.
It's a pixel-outliner! By drawing their pixel font offset in each direction, they get a black outline on their font.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •The Answer to the DRM questions for Where in the world is Carmen Sandiego? Enhanced (DOS, 1990) are, in no particular order:
23
Kent
dragon
calcium
1796
Warren
revenue
1792
Willard
1937
Crater
Tanzania
Hartford
Duluth
London
Gem
Silent
squeaker
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •if ((0x80 >> ((byte)local_4 & 7) &
(int)(char)*(byte *)((int)((int *)param_1 + 1) + (local_4 >> 3))) != 0) {
COULD YOU USE SOME MORE CASTS MAYBE?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •oh it's because ghidra's near/far pointer support is shit.
I had param2 defined as a byte*32 and it was casting it to a byte* before using it
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •well I found the decompression method.
as always, I hate it. decompression routines are probably my least favorite thing to reverse engineer
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •it looks like this chunk has length 256, which means 253 usable bytes, and it expands to 374 bytes.
Not the greatest compression. a little better than just doing 6-bit ASCII.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •it's some kind of shifting bit mask but it starts at encoding values in 4 bits, then it can increase (or decrease, I guess) based on the input stream.
then it has an output filter, where if the number specified wasn't 8 bits, it's actually an index into a predefined text table
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •aetonisrdlhugfcwypbmk,vSA.T'PMxBCIRGDWHqE-zNFKL0j:51YJ8\U?73Q;2!469
\r\nOVXZ()*+"#$%&<=>/@[]^_`
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •"\x03Lima is Peru's capital and largest city. A well-known landmark is the Archbishop's Palace, a reminder of Peru's colonial past\x00Peru is slightly smaller than Alaska and is bordered by Ecuador, Colombia, Brazil, Bolivia and Chile\x00Peru, once the center of the mighty Incan Empire, is a rugged land dominated by the Andes Mountains. Forests and jungles cover half its land area\x00"
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •it was a trivial off-by-one error.
I was doing saved_byte=input
[3]but while I needed the 3rd byte, that's at input[2]
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •yess!
C:\DOSBox-X\drive_c\carmen\py>python datfile.py cities.dat --dump=12803 --decompress
"\x03Sydney, with a population of more than 3.3 million people, is Australia's largest city. A well-known sight is Sydney's distinctively designed Opera House\x00An island continent, Australia is nearly as large as the United States but has only one-fifteenth the population\x00The capital of Australia is Canberra, located in the southeast corner of the country between Sydney and Melbourne\x00"
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •okay now that I can decode the chunks (well, most of them) I can identify a lot more of them:
00 Name and (some other info)
01 ???
02 Image
03 City descriptions
04 Items to steal
10 ???
11&up: Hints leading here
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •So like, the 12 chunk for Tokyo says:
b'\x05asked about the exchange rate for yen\x00was practicing Japanese characters\x00said\x81planned to take photographs of Mount Fuji\x00asked about tours of the Imperial Palace\x00was interested in visiting Shinto shrines\x00'
So it picks from one of those 5 options
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •b'\x02asked questions about Shinto rituals\x00said\x81was researching an archipelago\x00'
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •a "live" version of binxelview, so I can step through the DOSBox-x debugger and see how memory is changing in real time, as an image.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •That's odd, because it means it had to rewrite the image in memory, the image it's about to unload.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I hit a breakpoint in the debugger, I turn it on, set another breakpoint, and hit go.
between those two breakpoints, every time a CALL instruction is hit, it dumps my selected memory region. If it's identical to the last dump, it's ignored.
At the end, each dump is rendered as an image, and the combined set are an animation I can scroll through.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •it's in a function I already found, temporarily named "blit_related".
I guess they don't decode the image until RIGHT before it needs to go up on the screen!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Strange!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •write the byte 04 every 69 bytes
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •oh sweet jesus, that's the left two pixels of the image.
it's loading the image vertically!
at least it's top to bottom.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •yeah, doom did that too, but Doom was a 2.5D image that had to do pseudo-raycasting.
THIS GAME DOES NOT
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •it allocates a 1024 byte buffer, then makes a pointer to the end of it, minus -0x42?
why would you need a link to the end of a new, freshly cleared buffer, minus 62?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •unless it's an odd number, in which case it's a free block. and pointer to the previous block, once you make it even again
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •You see this little About dialog box? Guess how many times the DrawText function is called?
Once! and just to draw "Where in the World is Carmen Sandiego?".
The rest of the text is draw elsewhere, and I have no idea why.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •The game loads the BoldFont first, then the SmallFont, then the NormalFont.
Annoyingly this isn't how they're laid out in memory:
It's SmallFont, then BoldFont, then NormalFont
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •font_alloc = malloc(local_a);
if (font_alloc == (void *)0x0) {
font_alloc = (void *)0x0;
}
Ahh yes. remember, if you get a null pointer back from malloc(), make sure to set that variable to NULL so it won't be left as... NULL?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •man, running on 4 hours of sleep is killing me.
I can't even remember the MS-DOS interrupt to open a file!
I know reading it is int 21 ah=3f, closing it is int 21 ah=3d, and I'll never forget that seeking is int 21 ah=42, but how do you open a file?
I mean, not the int 21 ax=6c00 way, that one is only for DOS 4.0+, and obviously a game released in 1990 isn't gonna use that.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •ahh, now that I've looked it up, it seems I was wrong!
closing isn't 3D, that's 3E! 3D is open!
no wonder I couldn't remember it, I had it confused with another call
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •what the fuck do you mean that carmen.dat is opened on the first call to finish_draw_maybe()?
like, I know there's a "maybe" in that name, but it's not THAT big of a maybe.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •it actually LoadDatFile, which makes a HELL of a lot more sense
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •maybe do it for everything MS-DOS.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I think I failed to load the cursor, which caused it to corrupt the mouse cursor catastrophically
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I did a little looking into the contents of MIDISND.DAT
It's got 12 small tracks, and each of them is a valid MIDI file if you remove the first byte.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •heh. I was checking different near-death animations by overriding the randomness, so I had to tell my debugger to set AX to 0
guess which animation that is? The one with the AXe.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •why do they store the day of the week as a 16bit int?
future proofing in case the calendar gets updated and has more than 256 days in the week?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •They're in Rome and they've just slept through about two months of nothing
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I finally figured out how it calculates travel times.
It's the difference in X coordinate between the two cities, plus the difference between the Y coordinate, plus one.
that quantity divided by 40, then has 2 added. if the result is over 7, it's set to 7.
Weird! that's not how you measure distance, Carmen.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •also, it's the 90s, I can afford a sqrt().
I should fix it up for my version.
or use a squared lookup table. you could do this REAL easy by making it a table search: there's only 6 possible results: 2,3,4,5,6,7. each entry in the lookup table contains the maximum squared distance that can generate that number of hours
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •here's all 30 city locations:gist.github.com/foone/09925178…
it's currently way too 6am to do more calculations, though. I'll do that tomorrow
Carmen Sandiego city locations
GistFoone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Good news: @modulusshift did the calculations for me!
digipres.club/@modulusshift/11…
~ (@modulusshift@digipres.club)
digipres.clubFoone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •PUSH ES
PUSH AX
RETF
why must you hurt me, carmen sandiego?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •So the GraphicsMode enum goes:
0: MCGA
1: CGA
2: Hercules
3: EGA
4: Tandy
5: VGA
6: ???
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I find-replaced the background from palette entry 0 to palette entry C:
Now I can confirm how big this image is. Previously it was set into a black background, which made it harder
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •worst thing that could happen just happened:
I just realized the portable Where in the World is Carmen Sandiego? is based on the same version I'm hacking, meaning it's in-scope for me to get this, dump the ROM, and compare.
That just increased the cost and complexity of this project by bunch
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •WAIT HOLD ALL THE PHONES.
Here's a photo from a MS-DOS version. It does that thing some companies (like Sierra) did back in the day, and included both 3.5" and 5.25" disks in the package.
BUT WHY ARE THERE SO MANY DISKS?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •ARG, they mislabeled this.
Admittedly, this isn't really their fault, this is confusing shit.
This is the 1992 Where in the World Is Carmen Sandiego? Deluxe, not the 1990 Where in the World Is Carmen Sandiego? Enhanced.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •okay I finally found a boxed copy of the Enhanced 1990 DOS edition. (confusingly labeled the 1993 edition)
It comes on two 5.25" disks: presumably double-density, so that's 720kb in total.
Floppy Disk Pop Quiz: What's weird about these floppies, specifically given that this is MS-DOS version?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I happened to look at mobygames, and noticed two interesting things.
First, the Mac version is very similar to the DOS version, other than the expected changes you'd get from it being on a monochrome system with a GUI.
But wow, that's a completely different font! Is that built into macs or something? (EDIT: @amr confirms it is)
(also, the dialogue box is top-aligned. DOS bottom-aligns them)
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •The PC has basically always been double-sided, so they only need one notch, on the top/a side.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Broderbund was releasing games on a bunch of other systems that DID have single-sided drives. For simplicity they just bought Xty-thousand double-notched disks
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •is it gonna matter? not in the slightest (assuming there's no format-mismatching, which their shouldn't be: these are all the same density of disks, I think).
The PC doesn't check for a notch there, so it won't notice either.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •It's just funny because this is, like, technically wrong?. These aren't PC disks, but the difference doesn't matter, so why not?
It probably saved them a decent amount of money because of bulk discounts and inventory simplicity.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I am currently, as in this very thread, reverse engineering Carmen Sandiego Enhanced (1990, DOS)!
I've seen the code that asks for you to put in the other disk! And it only asks for DISK1 and DISK2!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •just looking at the files, not the code (and not having seen original disk images yet that I can recall), I bet the answer is that they put CITIES.DAT on DISK2.
the whole game - cities.dat is ~300kb, with cities.dat being 168kb.
They could do the whole game - carmen.dat and cities.dat in only 200kb, which'd give them 160kb (luxury!) for a fancy installer.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I should just check. I'm sure disk images can be tracked down in places.
the video and audio detection seems to be excellent, by the way. it just silently figures it out, without asking questions or requiring special arguments or configuration.
Perfect for a game aimed at the little childrens.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I found two different copies of the disk images, in different places.
both are imaged off a 3.5" disk version, which of course comes on only one (double density, 720kb) disk!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •So I've got code at 17DA:08AA, which is E8 5D F7. DOSBox decodes that as CALL 000A.
Manually decoding it myself, it should be a relative jump, and it's a jump to $-0x8a3. following the jump it ends up at 17DA:000A.
BUT GHIDRA thinks this code is at 1fb7:08aa, and it decodes it as call SUB_2000_fb7a, which doesn't exist.
I'm not sure how (0x08aa+3)-0x8a3 = 2000:fb7a. Something weird is going on. Why is the number BIGGER?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •eww. They're using the NEAR version of CALL to call a FAR procedure.
You might say "wait, won't that break when it tries to do RETF?" and yes, it would, unless they manually do PUSH CS before they call it!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •a call FAR absolute would be 5 bytes for the call, whereas push CS + call NEAR is 3+1 bytes
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Anyway it seems it doesn't have a VideoDetect function, it's a DriverDetect function, since it's used for sound too.
First it goes through the video drivers in the following order:
VGA, TGA, EGA, HGA, HERC, and CGA.
Then it goes into the audio drivers:
stdsnd, adlib, covox, gblast, ibmg, sblast, tandy.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •stdsnd is pc speaker,
adlib is adlib, covox is the speech thing, gblast is game blaster, most likely, ibmg is... I'm not sure. The PS-1 Audio card?
sblash is soundblaster and tandy is tandy 3-voice
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I'm an idiot, this isn't a driver check... it's an argv check!
you can pass "ega" or "vga" or whatever to carmen.exe to select those types.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •the other argument you can pass is ROSTER=$FILENAME
This lets you reset which file it uses for the list of registered players, setting it to something other than the default ACME.DAT
Not mentioned in the manual, but I can see how that might be useful for schools and such
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •The problem is that ghidra gets confused when the relative addresses are too big.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •so the first one is at 1000:0000 and the second was at 1fb7:0009. I moved it to 5000:7000, and the second segment seems to be working fine now.
the problem is that I was only able to do that because the segment is only 82a7h long. the first segment, the 1000:0000 one, is FB79 long. So I can't just move it so it's in the middle of a segment, since it'll end up spanning into the next 64k chunk, which is where ghidra fucks up
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •9000:8006 9a d7 05 b7 1f CALLF SUB_2000_0147
Hey ghidra I can read the machine code. That's CALL FAR 1fb7:05d7, not CALL FAR 2000:0147! WHY ARE YOU CONFUSED BY THIS?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •so now I can just manually thunk every cross-segment call, by creating the 2000:0000 segment that ghidra is imagining exists
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I was extracting the portraits of the people you talk to, and it turns out they're number 1-36. naturally I checked all 256 possible options.
but it turns out every thing above 37 either:
1. crashes
2. shows nothing
3. shows pixel gibberish.
EXCEPT 238. 238 renders a bellhop perfectly, just like 5 does
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •it has been zero days since Ghidra has done something I can't understand and seems to be obviously wrong.
I've got B8 B0 26: this decodes to mov ax, 0x26b0. a 16bit immediate, moving into a 16bit register.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •ghidra disassembles this as:
b8 b0 26 MOV uVar1 ,0x26b0
uVar is defined as a ushort: a 16bit type.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •all makes sense, right? 16bit to 16bit!
so ghidra decompiles it as uVar1._0_1_ = 0xb0;
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •the most annoying thing?
this is picking between two strings to display, and those strings are "he" and "she".
EVEN IN 35 YEAR OLD COMPUTER GAMES I CANNOT ESCAPE GENDER PROBLEMS!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •unfortunately due to an oversight it believes those 65536 genders are allocated as:
0: He/he/Him/him
1-65535: She/she/Her/her
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Since it's no longer 1990, I think I can safely bump that up a bit? I won't need more than another 64 KB, which means I'll just bump the game up to 496 KB memory required. Completely doable in any 640 KB or more machine!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •the applyPronouns function lets you adjust how it's encoded dynamically. Fancy!
So how it works is you do something like this:
applyPronouns("\80 was bald", 0x80, "he\0him")
and it'll return "he was bald", right? But it's more than just a simple find-replace...
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Because you can do:
applyPronouns("I saw \81. \80 was bald!", 0x80, "he\0him")
and it'll return "I saw him. he was bald!".
See, you can specify multiple replacements at once, by using \x80, \x81, \x82 and so on.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I got my floppy copy in the mail, I just need to image it.
Fun fact from the box: It has a letter from the player character to their cousin, and I believe this is the only place in the game and associated media that they name your character.
It's Dale.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •They're laid out like this:
Disk 1:
CARMEN.EXE
CARMEN.DAT
Disk 2:
CITIES.DAT
MIDISND.DAT
DIGISND.DAT
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Finally, we know the answer to the age-old question of Where in the World is Carmen Sandiego?
The answer is "My floppy drive"
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •well my "ignore the problem" solution of using bochscpu to embed a 16bit x86 emulator has failed. it's somehow broken and it's broken in the rust library or C core, not the python, and I really don't want to have to deal with debugging this.
time to switch to a completely different x86 emulator? PROBABLY!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •unicorn has great documentation.
at least I assume it would be great, if it existed
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •x86 16bit segmentation and regular expressions:
BECAUSE FUCK IT
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I'm implementing unicorn as an x86 emulator to do the decompression, but I'm single-stepping the processor and I'm aiding debugging by showing what instruction I'm on.
but instead of having to set up an x86 disassembly engine, I'm just parsing a plain text ghidra dump of the disassembly. I'm parsing it with regexes
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •my latest bad idea: DUMBPATCH.
to avoid the complexity of generating functions and mapping them into the address space of the emulated PC, I instead designed a simple syntax:
a 16bit segmented address plus a number. that function is emulated as if it returned that number in AX. There are no other options. I suspect I'll be able to emulate up to 80% of complex subfunctions with this one bit of functionality
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I need this because the decompression routine I'm emulating isn't entirely standalone: it calls malloc() at the beginning and free() at the end
so I'm replacing malloc() with a static value and free() with a return value no one will check
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •ideally I should be able to patch arbitrary python in there and do some kind of interop to return values to python
but that's hard. and way easier unflexible thing this is 80% of what I need that for
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •back on hacking Enhanced, DOS, 1990.
My best guess is that this game has between 4-6 compression algorithms, depending on how you count them. Possibly more are hidden in the bowels of this program.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •This code mallocs 65516 bytes in a loop until malloc returns zero.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •totally normal part of starting program: allocate all the RAM in the system.
I mean, it's DOS. There's nothing else running that could possibly call malloc. So why not?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •There's just not that much memory in the system that this can touch, since it's not supporting any of the endless varieties of breaking the 640k barrier
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •there's a story on Old New Thing somewhere about Windows 95 accidentally breaking a DOS game, because it did this same trick of allocating all the memory, but since Win95 was running as the DPMS, it meant it had access to all of windows 95's virtual memory. including the swap.
So instead of mallocing all 8mb or whatever your 486 had, it malloced all that and then tried to use up YOUR ENTIRE HARD DRIVE, slowly.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •this game runs mostly in a 16 color mode, with some high-end modes being basically 16-colors within 64 or 256 colors, right?
SO WHY DOES IT USE 16-BIT INTEGERS FOR COLOR INDICES?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •note to self: patch out the Romani slur in one of the hints for Budapest
EDIT: both of them
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •The description of Hungary says it's bordered by Czechoslovakia, Austria, Yugoslavia, Romania, and the Soviet Union.
Two of those are still right!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •so I fly into Reykjavík, and immediately sleep for 8 hours. In the morning, I can go to either the airport or the hotel, but it'll take 3 hours to get to either.
Question: where am I right now, if I'm not at the hotel or the airport?
Z̈oé ⛵
in reply to Foone🏳️⚧️ • • •yomimono, still on earth
in reply to Foone🏳️⚧️ • • •Crystal Huff (they/them)
in reply to Foone🏳️⚧️ • • •Aatheus
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Aatheus • • •Aatheus
in reply to Foone🏳️⚧️ • • •Graham Sutherland / Polynomial
in reply to Foone🏳️⚧️ • • •Large Print Edition (Delia~!)
in reply to Foone🏳️⚧️ • • •Kevin Boyd (he/him) 🇨🇦
in reply to Foone🏳️⚧️ • • •~
in reply to Foone🏳️⚧️ • • •Pxl Phile
in reply to Foone🏳️⚧️ • • •Ben A L Jemmett
in reply to Foone🏳️⚧️ • • •C.
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to C. • • •Spindel
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •They initialized the SoundBlaster DSP backwards.
You're supposed to send a 0 to the reset port, wait 3 microseconds, send a 1, then wait up to 100 microseconds for an 0xAA to show up on the data port.
They instead send a 1, then a 0, then immediately start trying to read the data port.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •tekhedd
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to tekhedd • • •tekhedd
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •they're sending a... internal soundblaster test command?
(DSP 0xF0)
I dunno why this code is like this.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I suspect there may be an issue here: I identified a variable as containing the Soundblaster IO port, right? and I'm assuming everything that uses it is Soundblaster code.
But it may just be "soundcard IO port" and there's other sound device code mixed in here. So that's why some of it doesn't make sense as soundblaster, it's actually tandy 3voice or something
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •it's a loop that runs 256 times!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Tom Forsyth
in reply to Foone🏳️⚧️ • • •Joel Michael
in reply to Foone🏳️⚧️ • • •@jbqueru@floss.social
in reply to Foone🏳️⚧️ • • •Coreworlder 🎲
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •there's code in here specifically to detect if it's running on an IBM PS/1 by looking at the CMOS area?
WHAT THE
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •the menu system limits menus to having a maximum of 32 items.
which is weird because ONLY 17 WILL FIT ON SCREEN
Otte Homan - remember Geordie
in reply to Foone🏳️⚧️ • • •cerement
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I did some experimenting with MSVC 5.1, and it's weird. I get the same strings in the exe as carmen.exe has, but the code itself looks completely different.
either I set up my compiler wrong, or this game is full of assembly even for very simple functions
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I don't know exactly what this function does (I know it sets some flags based on something in the graphics context) but I DO know one important thing about it:
they included it in the final binary FOUR TIMES.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •byte-identical.
this is a compiler & linker from 1988, it doesn't understand how to merge identical copies of functions apparently
Clark Breyman (he/him)
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Clark Breyman (he/him) • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I found another function which has 4 copies.
I'm starting to suspect this program originally had 4 C source files and the linker wasn't optimizing this
anparker
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •wait I bet it's drivers!
like, one version of this function is called by VGA_DrawFuncUnknown and nothing else.
Another one? CGA/Hercules.
the third? EGA
The last? Tandy.
They compiled the 4 video drivers separately, and then linked them into the EXE, with no deduplication across compile units
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •yeah. Found another: VGAMalloc is the same as CGAMalloc (and Hercules doesn't have it's own HerculesMalloc, because it's in the same code unit as CGA: So it just uses CGAMalloc)
Tandy has TandyMalloc.
But not EGAMalloc. That one is completely different.
Walter van Holst
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Walter van Holst • • •@whvholst the function just mallocs the param passed in, so it doesn't care about layout.
except for EGA. which I don't understand yet
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •the DrawLine API is weird.
To draw the horizontal underline for the hotkeys in the menu, it calls DrawLine(0, -width).
It's DrawLine(int y, int x), and yeah you pass negative numbers
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •because 0,0 is silly, you're always drawing at least one pixel. So DrawLine(0, -5) draws a six pixel wide horizontal line to the left
Klairi Flurry
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •PUSH BX
PUSH ES
PUSH SI
CALL StartPlayingSound
POP BX
POP ES
POP SI
since when has the x86 stack been FIFO instead of LIFO?
KeyJ
in reply to Foone🏳️⚧️ • • •Dentaku (Thomas Renger)
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •LoadAndPlaySoundChunk is called with a chunk name from digisnd.dat, but you can also pass -1 or 0. I'm not sure what -1 does yet (maybe silence a currently playing sound?) but 0 means "wait until the sound finishes"
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I'm not really sure why it works that way, especially because calling LoadAndPlaySoundChunk(0) is equivalent to calling WaitUntilSoundFinishes().
So why not just do that instead?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •uh oh. the computer noise is triggered with:
LoadAndPlaySoundChunk(217)
but I look in the DIGISND.DAT file and it has chunks 200-216.
So either my DAT file parsing is wrong or it's loading sounds from elsewhere, somehow? because the sound DOES play, so it's not just an error
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I thought it might just be playing from MIDISND.DAT instead (since the computer noise is very beepy, maybe it's just a synth sound?) but MIDISND.DAT starts at chunk id 218 and goes up.
WHERE IS 217?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •huh. weird. when you try to backspace too far in the name entry screen, it goes "duh-nuh" at you, but that isn't connected to a LoadAndPlaySoundChunk call.
So it's using a different function for this ONE NOISE?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •YEP. muted my soundblaster (MIXER SB 0:0) and it's still duh-nuhing at me.
why would you do this to me, brøderbund?
Olivier Galibert
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Olivier Galibert • • •Olivier Galibert
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •ah-ha! I found 217.
DIGISND.DAT has PCM sound effects for 200-216.
But there's also chunks in CARMEN.DAT for 200-229.
I didn't think the ones in CARMEN.DAT were sound files because they're so small... but they're just the right size to be PC speaker sound effects!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •they hardcoded two sound effects into the EXE and the rest are loaded from the DAT files.
eww. Someone hacked something in at the last moment!
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Adriano
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •there's 729 functions in the EXE.
I've named (in some way, counting placeholders) 355 of them, or 49%
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •by placeholders I mean things like "pcjr_sound_related" or "VGAFunc8"
and 13 of those function names include the word "maybe"
Briala
in reply to Foone🏳️⚧️ • • •Andrew Zonenberg
in reply to Foone🏳️⚧️ • • •Sounds like most of my IDBs.
"UART_func4", "Socket_func2"
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I think they generated their hints wrong.
The *22 chunk for a city says something like "$SUSPECT was going to an opera with the president" or "$SUSPECT would be having tea with the Emperor", right?
but it's also got "drove away in a vehicle flying a green, blue, and yellow flag". which'd be fine, except that hint is also in *19!
I think they accidentally duplicated it when they generated the cities.dat file
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •ugh. ghidra really doesn't understand that you can call far functions using near calls.
and the compiler for this LOVES using them.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I might have explained this before, but normally a near call to a far function will break, because it'll pop 4 bytes off the stack for the return address, when the near call only pushed 2.
So you fix this by doing push CS first, so it'll pop the 2 from the call, and then the 2 you placed before.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •so you'll see, for example, it decompiles a strlen as:
uint1 = strlen(0x1000, some_String);
which is less than useful
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •in 32bit we do 32bit calls and 32bit returns.
in 64bit we do 64bit calls and 64bit returns.
in 16bit we can do 16bit calls and 16 bits returns, 32bit calls and 32bit returns, and sometimes we do a 16bit call to a 32bit return because it's slightly fewer bytes
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •one of my favorite stupid methods of reversing is "break it"
what's this function do? well, lemme disable it, and see what breaks.
Apparently this is the "restore the image under the cursor" function.
myrmepropagandist reshared this.
Kit Bashir
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •gloriouscow
in reply to Foone🏳️⚧️ • • •i will convert you someday. mark my words
just as soon as i implement, like twenty years of hardware
Foone🏳️⚧️
in reply to gloriouscow • • •gloriouscow
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to gloriouscow • • •@gloriouscow I think so, yeah.
Actually, it supports EGA/CGA/Hercules/MCGA as well, so I could definitely try running Carmen in MartyPC.
Maybe next session, I'm already halfway through a lot of nasty stuff
gloriouscow
in reply to Foone🏳️⚧️ • • •i can add breakpoint logging for you definitely tho
what do you imagine that looking like, a timestamp with cs:ip and breakpoint name when hit?
Foone🏳️⚧️
in reply to gloriouscow • • •gloriouscow
in reply to Foone🏳️⚧️ • • •yeah i've got something similar planned when i add Rhai scripting. you'll be able to attach a script that is evaluated when the breakpoint is hit
the script interpreter will have access to logging output facilities and the entire machine state
but that's a little ways off still
Foone🏳️⚧️
in reply to gloriouscow • • •gloriouscow
in reply to Foone🏳️⚧️ • • •it'll really kick things up a notch.
i'm still waffling about using lua instead but i wrinkle my nose when i look at lua code
Foone🏳️⚧️
in reply to gloriouscow • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •2. the world doesn't wrap. This path is longer than "just" crossing the pacific, which is how this flight actually goes.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Troldann Arothin
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Troldann Arothin • • •@troldann of course!
(there's only 435 routes, I can precalculate them offline and just embed the answers in the code)
Joel Michael
in reply to Foone🏳️⚧️ • • •Bill Ricker
in reply to Foone🏳️⚧️ • • •@troldann
Only 30 destinations? I guess that was just enough to keep each play through somewhat different without switching floppies 💾
30×(30−1)÷2
Foone🏳️⚧️
in reply to Bill Ricker • • •@BRicker @troldann
yeah, there's only 30. They had room for more, but I guess they had to stop somewhere.
The second disk has the CITIES.DAT file, which is 168kb. There's still 102kb free on that disk, so it would be doable to add another 20 or so cities
Bill Ricker
in reply to Foone🏳️⚧️ • • •gkrnours
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •arg, this function is saving and storing part of it's own return address.
It's a farcall (in 16bit mode), and it is looking at the stack to read the segment portion of the address, so it can save it away in a struct.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •this is a SaveRegisters function, which also saves the cs:ip of the calling function.
but there's also a RestoreRegisters function, which ALSO restores the cs:ip of the calling function.
and then it returns.
to the restored cs:ip
THIS FUNCTION IS A DYNAMIC GOTO
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •gonna have to dig out the appropriate compiler and check if setjmp/longjmp compiles the same.
then cry
✧✦Catherine✦✧
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •okay so, the way main works is like this:
it calls initGame(), then setjmp.
if setjmp returns 0, it initializes the game.
if it returns 2, it goes into the main game loop.
except it's not really a loop? because the functions longjmp back to main(). it's a distributed dynamic goto loop
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •like if you do File->New, it longjmps(&env, 1).
which causes the game to reload from the beginning.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •*smacks forehead*
of course this compiler doesn't take ANSI C. it's from three years BEFORE ANSI C
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I only have Small installed. lemme find the floppy disk to install Compact/Medium/Large
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •it's not large... because large saves DX.
uh-oh. was MSC5 right all along, I just had the wrong model?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •yeah. msc5 matches as well, if I set it to the right model.
WELL THAT WAS A WASTE OF TIME
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I have a sneaky suspicion it was build with MSC5.1, not 5.0.
it's going to take an annoyingly long amount of time to verify that theory
Klaus Frank
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •it turns out what I thought was my MSC5.0 install WAS 5.1
so I need to install MSC5.0, not MSC5.1
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •yeah it's definitely not 5.0.
ugh. it's not 5.1 either. there may be some minor patch that I don't have access to
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •__aaltstkovr
that's a name, all right.
Graham Sutherland / Polynomial
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Total funcs: 756
Unnamed funcs: 360
% named: 52.4%
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •after extensive cross-referencing with the msc5.0 manual and the msc5.1 libraries being opened in a parallel copy of ghidra, I have finally been able to determine that the function I named sprintf_maybe is, in fact, _sprintf.
my hard work, as always, pays amazing dividends
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I'm currently figuring out functions through the amazing insight of "the linker is simple and linear"
which means when I have _memmove, FUNC_1fb7_6db0, and _strcmp in the EXE, FUNC_1fb7_6db0 is probably not going to be an adlib sound driver. it's going to be something from the libc.
Cassandrich reshared this.
Andrew Zonenberg
in reply to Foone🏳️⚧️ • • •I've done this a lot especially in embedded stuff where you have an open source peripheral library or RTOS bolted onto closed source application code.
Find one xref to a SFR, match to the corresponding vendor HAL function, then you probably get 30 functions with minimal effort that are right before/after in the same order as the .c
Cassandrich reshared this.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •okay I've got all the libc stuff named, other than some internal functions (which I don't have names for), and one weird memmove-ish function that I just named "memmoveish"
it looks very similar to memmove, but with an extra check or two, but I can't match it to anything in the library
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Total funcs: 758
Unnamed funcs: 332
% named: 56.2%
pretty good for a day's work: nearly 4% done
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •made a discovery:
Galleons of Glory: The Secret Voyage of Magellan, released by Brøderbund in 1990, uses the same DAT format for its game files.
I haven't looked into the EXE yet, but that definitely sounds like they're sharing code
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •oh wow, it looks like Prince of Persia (DOS) also uses this DAT format!
Sadly, while the source for Prince of Persia is available... it's for the Apple II version. The DOS version is a complete reimplementation
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •SDLPoP is based on reverse engineering of the DOS PoP, maybe I can see how they implement DAT file reading.
github.com/NagyD/SDLPoP
GitHub - NagyD/SDLPoP: An open-source port of Prince of Persia, based on the disassembly of the DOS version.
GitHubFoone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •William D. Jones
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to William D. Jones • • •William D. Jones
in reply to Foone🏳️⚧️ • • •Henryk Plötz
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Henryk Plötz • • •@henryk ahh, neat.
Not a tool I have access to, sadly.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •hey look, Prince of Persia uses the same setjmp/longjmp mainloop design!
github.com/NagyD/SDLPoP/blob/7…
SDLPoP/src/seg000.c at 7bd3bb85d8f7d4cb8ef72557ea0e65d80ba0906d · NagyD/SDLPoP
GitHubFoone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •the compression has a fun quirk: images can be compressed either top to bottom or left to right.
and the game switches between the two compression formats on a per-image basis.
So the developers just compressed each image both ways and used the smaller one. clever.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I've now got a boolean that has three values (true, false, and 'image')
but it's okay, I have a permit: I'm non-binary.
Kataemaus
in reply to Foone🏳️⚧️ • • •Andrew Zonenberg
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Andrew Zonenberg • • •Andrew Zonenberg
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Andrew Zonenberg • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I have successfully extracted the first image from the game, using the ported SDLPoP compression code!
1 compression method down, 3 to go.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •gloriouscow
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to gloriouscow • • •@gloriouscow everyone* knows canada only has one city, and it's Montreal.
* the 1990 game Where in the World is Carmen Sandiego? Enhanced
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Sydney: fine.
Singapore: fine.
San Marino: fine.
Rome: PYTHON CRASHED
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •huh.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Pxl Phile
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Pxl Phile • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •working on a full dat exporter, to build a JSON of all the hints.
and I'm running into pronoun issues. Story of my fucking life.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •yeah looks good.
gist.github.com/foone/82de72a0…
The misplaced entries (like Cairo having a leader hint of "left in a vehicle flying a red, white and black flag") are like that in the original data files. Brøderbund just got their hints miscategorized sometimes.
cities.json
GistFoone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •It's the Old Man of the Mountain, which collapsed in 2004.
Zarky
in reply to Foone🏳️⚧️ • • •William - W1WRA
in reply to Foone🏳️⚧️ • • •Video_Game_King
in reply to Foone🏳️⚧️ • • •Allan Chow
in reply to Foone🏳️⚧️ • • •rezmason
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to rezmason • • •LAbare
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •if you are in New Delhi and need to go to the USSR, but misclick on Oslo instead of Moscow, you can't just fly to Moscow from Oslo. You have to go back to New Delhi first
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •dstu
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •fun fact about Prince of Persia (which I am doing research on because of how it reuses code from Carmen or vice versa):
A copy of it leaked with symbols included, but it's not the most normal version you can imagine... it's the mac port recompiled for MIPS.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •press a button, then for the next X seonds, all modifications to the display memory are recorded along with the backtrace of what code changed it. So you could see a button get drawn, and check what code did that.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •the original PC ran an 8088 at 4.77mhz, which DOSBox emulates as 240 cycles.
so this is approximately equivalent to a half-megahertz PC
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •neko68k
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •PUSH DS
PUSH peel_ptr
PUSH DS
PUSH peel_ptr
the pointer so great they pushed it twice!
The Good, Mad and Ugly
in reply to Foone🏳️⚧️ • • •Tom Forsyth
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •running this software at 15 cycles/second, I can confirm that the creators of it definitely didn't do that.
their general approach is "I KNOW PROGRAMMERS WHO TRY TO AVOID OVERDRAW AND THEY'RE ALL COWARDS
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •then white.
then it starts redrawing the background.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •this only happens with movable dialogs. unmovable dialogs don't flash black+white.
which makes me think it's a bug rather than an intentional decision
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Troldann Arothin
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Troldann Arothin • • •monotonehell 🏳️🌈🇦🇺 (Andrew)
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to monotonehell 🏳️🌈🇦🇺 (Andrew) • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •b8 13 29 MOV AX ,0x2913
50 PUSH AX
b8 00 00 MOV AX ,0x0
50 PUSH AX
POP QUIX: The usual way to zero out a register on x86 is XOR AX,AX. This'd be only 2 bytes (31 C0). The compiler knows this. Why didn't it use XOR AX, AX here, instead of the bigger MOV AX, 0x0?
(It's not because optimizations were off!)
Je ne suis pas goth
in reply to Foone🏳️⚧️ • • •leah & glitches & bits, oh my!
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to leah & glitches & bits, oh my! • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •here's a hint: that disassembly is from the EXE, not from the memory of a running program.
(why would that matter?)
leah & glitches & bits, oh my!
in reply to Foone🏳️⚧️ • • •leah & glitches & bits, oh my!
in reply to leah & glitches & bits, oh my! • • •Foone🏳️⚧️
in reply to leah & glitches & bits, oh my! • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •@millihertz got it:
oldbytes.space/@millihertz/114…
leah & glitches & bits, oh my!
2025-05-25 06:09:40
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Total funcs: 762
Unnamed funcs: 293
% named: 61.5%
118 of those named functions have been marked as identical to ones from Prince Of Persia (or vice versa... I have no idea which game had this code first)
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •my initial theory of how the code sharing went:
Prince of Persia ->
Where in the World is Carmen Sandiego (enhanced) ->
Where in the USA is Carmen Sandiego (enhanced) ->
Galleons of Glory: The Secret Voyage of Magellan
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •1000:700b MOV CX,0x20
TimingLoop:
1000:700e LOOP TimingLoop
ahh, the good ol' days when "32 instructions" was a meaningful unit of time.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •2. WHY DID I HAVE TO READ THE DOSBOX-X SOURCE CODE TO FIND THIS OUT?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •the game picks between "they flew off to X" and "they drove off to X" and "they rowed off to X" and "they sailed off to X" but it doesn't seem to do this with any smarts.
or if it does, the database is incorrect.
carmen apparently drove off to nepal from canada
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •abadidea
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •another way in which this game shows that it's from 1990 is that the librarians will tell you anything about their patrons.
that shit stopped after 2001
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •You're in Sri Lanka! YOUR currency is rupees!
наб
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to наб • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I'm experimenting with a way to show how DOS games render themselves.
Basically I'm recording a lossless video of the game running on a very slow CPU, then removing all the frames where nothing happens, and I'm playing it back sped up a lot.
The highlight of this video is how terrible the handling of the mouse cursor is! it's getting peeled and restored constantly
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •the mouse cursor appearing and disappearing is because they don't have multiple frame buffers: they have to hide the mouse cursor before they can draw anything, or the cursor would corrupt the newly drawn stuff if it happened to be over it.
so they solve this by hiding the cursor before every drawing command and showing it afterwards.
but instead of doing it once per screen, they're doing it once per command.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •the GUI system they're using (I'm just calling it the broderbund UI in my reverse engineering work) DOES support avoiding this mess: you can tell it to hide the cursor, then when each sub-command tries to hide/restore it, it stays hidden, but they're not using it here.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •which is of course equivalent, but it means you get this code:
offset2_rect(-y - param_3->bottom,-x - param_3->right,
(Rect *)CONCAT22((char *)ds,¶m_3->bottom),
(Rect *)CONCAT22((char *)ds,¶m_3->bottom));
instead of:
offset2_rect(-y - param_3->bottom,-x - param_3->right, param_3, param_3);
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •broderbund::hide_cursor();
broderbund::show_cursor();
WERE YOU PUNKS GETTING PAID BY THE CYCLE?
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •right after this it checks if the mouse is even enabled (hey, it's 1990, not everyone has a mouse!)
I'd think that you would check that before you try to hide and redraw the cursor, but maybe this is exactly why I'm not employed writing educational games in 1990?
Hirvox
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •MousePos is a struct with 2 shorts, x & y. they need MousePos.x & MousePos.y into CX and DX. BUT HOW?
LES DX, [MousePos.x]
MOV CX, ES
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •okay on an original 8086, LDS + MOV REG,REG is 29+2=31 cycles.
MOV REG, MEM*2 is 18*2=36 cycles.
I GUESS?
Graham Sutherland / Polynomial
in reply to Foone🏳️⚧️ • • •Stumpy The Mutt
in reply to Graham Sutherland / Polynomial • • •Foone🏳️⚧️
in reply to Stumpy The Mutt • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •compilers are programs. programs are predictable (with enough effort. if it was easy, we wouldn't need people like me)
humans are not predictable. reverse engineering what a human is doing is much, much harder.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •so I think digipres.club/@foone/114611650… does make some sense: it's hiding and redrawing the cursor because it already moved the mouse position, and wants the on-screen cursor to match up .
Foone🏳️⚧️
2025-06-02 03:29:53
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •it's just you'd think this would be like:
hide_cursor();
cursor.pos=newpos;
show_cursor();
but apparently the way the cursor hiding/showing works, hiding uses a saved position, while show_cursor uses the new global position.
tekhedd
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •so the code that uses the soundblaster auto-detects the IRQ in use by simply setting up a handler for every possible SB interrupt, then asking the SB to fire an interrupt. then it sees which one triggered.
Makes sense, but I wonder why this wasn't universal? why were programs always asking me which IRQ to use? maybe this isn't compatible with less-accurate SB clones?
Joel Michael
in reply to Foone🏳️⚧️ • • •at a guess, in case something else was using one of the “regular” SB IRQs that happened to fire at the same time?
I do not miss assigning non-conflicting IRQs from usually one of 3 options on every card.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •currently in destructive-debugging mode.
I've a bunch of functions I tagged as "draw_relatedNN". Currently I'm down to 1, 2, 7, 9, 11, and 20.
So I'm running the game with them disabled (one at a time, natch) to see what doesn't render properly.
for example, when the first instruction of draw_related1 is a RET, suddenly animations don't play
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •I was gonna give up on draw_related7 since disabling it didn't seem to change anything.
then I tried to quit...
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •now everything is flat
Charles Pence
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Charles Pence • • •Jernej Simončič �
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •the menu items 1-indexed, sort of. it treats 0 as "the whole menu itself"
so like:
set_menu_enabled(TRUE, 1, Menu_Game);
sets the first item in Game to enabled, but
set_menu_enabled(TRUE, 0, Menu_Game);
sets the whole Game menu to enabled.
Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •the second thing the main() does (after setjmp) is try to unload the game.
this is a side-effect of how they're using setjmp to make main() a sort of event handler, so when they need to load the game's resources they don't know they're not already loaded, so it first tries to unload them, fails because they're not loaded, and THEN loads them
effika
in reply to Foone🏳️⚧️ • • •Martin's pipe dreams
in reply to Foone🏳️⚧️ • • •wyatt
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to wyatt • • •AndroidDreamer
in reply to Foone🏳️⚧️ • • •KeyJ
in reply to Foone🏳️⚧️ • • •Probably the other way round - it was likely too prone to false positives.
Printing something in the background (one of the few multi-tasking things DOS could do)? Instant IRQ7 misdetection.
Receiving a packet from a network card that's configured to an IRQ line which is also typical for a sound card? Well ...
Foone🏳️⚧️
in reply to KeyJ • • •Colin McMillen
in reply to Foone🏳️⚧️ • • •Graham Sutherland / Polynomial
in reply to Foone🏳️⚧️ • • •Kevin Riggle
in reply to Foone🏳️⚧️ • • •morgan
in reply to Foone🏳️⚧️ • • •The Lack Thereof
in reply to Foone🏳️⚧️ • • •I remember mouse cursors flickering a ton back in the day.
I was just happy to have a mouse at the time
Briala
in reply to Foone🏳️⚧️ • • •Pxl Phile
in reply to Foone🏳️⚧️ • • •Eniko Fox
in reply to Foone🏳️⚧️ • • •ohh I wonder if I could do this for my research for my video on blobbers 🤔 there's actually very little information out there on how to optimize the drawing
Are you just setting the emulator to very low cycles?
Foone🏳️⚧️
in reply to Eniko Fox • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Eniko Fox
in reply to Foone🏳️⚧️ • • •Eniko Fox
in reply to Eniko Fox • • •Foone🏳️⚧️
in reply to Eniko Fox • • •Eniko Fox
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Eniko Fox • • •Eniko Fox
in reply to Foone🏳️⚧️ • • •😮
well i know one of the big ones is lands of lore, because it doesn't just draw the scene but it also intersperses critters into it
i spent hours looking at the source for the scummvm engine but it's almost entirely uncommented and they do stuff like bitwise ops using raw numbers everywhere so it was just too hard to follow and figure out what was going on :/
Foone🏳️⚧️
in reply to Eniko Fox • • •Foone🏳️⚧️
in reply to Foone🏳️⚧️ • • •Eniko Fox
in reply to Foone🏳️⚧️ • • •Alexander The 1st
in reply to Foone🏳️⚧️ • • •@eniko Is the back buffer of double buffered rendering code always in the same place?
I would've presumed it would depend on the given software's compilation or freed memory, though I'm more of a higher level programmer in that it's kind of hidden from the drawing cycles I've dealt with.
Foone🏳️⚧️
in reply to Alexander The 1st • • •@AT1ST @eniko yeah it depends on the game and what graphics hardware it targets. but it's usually easy to figure out where it is:
set a write breakpoint on the visible screen, and when it's hit, you now know what code writes to the screen. probably that's just a memcpy from main ram or vram, depending
Bill Ricker
in reply to Foone🏳️⚧️ • • •Efi (nap pet) 🦊💤
in reply to Foone🏳️⚧️ • • •dr_barnowl
in reply to Foone🏳️⚧️ • • •Jonathan Hendry
in reply to Foone🏳️⚧️ • • •ROTOPE~1
in reply to Foone🏳️⚧️ • • •Max
in reply to Foone🏳️⚧️ • • •bmeia.gv.at/oeb-peking
Tobias
in reply to Foone🏳️⚧️ • • •Reading the German Wikipedia etyomolgy section, that follows a
Chinese postal romanization, which, apparently, fell out of use between 1980s and early 2000s.
en.wikipedia.org/wiki/Names_of… suggests that, yes, mid-80s would be a typical swich time.
Maybe the editor was German?
overview of the names of Beijing
Contributors to Wikimedia projects (Wikimedia Foundation, Inc.)gkrnours
in reply to Foone🏳️⚧️ • • •KF7CCC Andrew
in reply to Foone🏳️⚧️ • • •Google Books Ngram Viewer
books.google.comPhoenix Gee
in reply to Foone🏳️⚧️ • • •Kevin Boyd (he/him) 🇨🇦
in reply to Foone🏳️⚧️ • • •Captain | 🏳️🌈 🇨🇦
in reply to Foone🏳️⚧️ • • •Pepper
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Pepper • • •Sid (谷思谛) 🐺 🔜 GPN23
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Sid (谷思谛) 🐺 🔜 GPN23 • • •Rue Mohr
in reply to Foone🏳️⚧️ • • •Tom Forsyth
in reply to Foone🏳️⚧️ • • •Marcel Waldvogel
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Marcel Waldvogel • • •Marcel Waldvogel
in reply to Foone🏳️⚧️ • • •All's well!
rf
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to rf • • •Tom Forsyth
in reply to Foone🏳️⚧️ • • •chell (friend of eggbug)
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to chell (friend of eggbug) • • •Pxl Phile
in reply to Foone🏳️⚧️ • • •fuck yeah, that was specs of my first PC. A Siemens telewriter that came with an onboard Hercules gfx card.
For a wild reason when I added a CGA card it jumped to 4.78 MHz. I still miss that old box
Mayday! Mayday! Robot
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Mayday! Mayday! Robot • • •Mayday! Mayday! Robot
in reply to Foone🏳️⚧️ • • •Graham Sutherland / Polynomial
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Graham Sutherland / Polynomial • • •Graham Sutherland / Polynomial
in reply to Foone🏳️⚧️ • • •Devourer
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Devourer • • •the vessel of morganna
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to the vessel of morganna • • •Graham Sutherland / Polynomial
in reply to the vessel of morganna • • •@astraleureka digipres.club/@foone/114565324…
Foone🏳️⚧️
2025-05-24 23:08:44
the vessel of morganna
in reply to Graham Sutherland / Polynomial • • •Foone🏳️⚧️
in reply to the vessel of morganna • • •Pxl Phile
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Pxl Phile • • •nuxi
in reply to Foone🏳️⚧️ • • •Bill Ricker
in reply to Foone🏳️⚧️ • • •TBQF, some decades that's true IRL too.*
When m-i-l & darling went to see the Schlieman Gold of Troy during Glasnost, flights to Moscow were change planes in Finland or Poland.
*(for that SPECIFIC example. I do realize you gave an example of a rigid game mechanic. I just find it a peculiarly interesting example.)
Bill, organizer of stuff
in reply to Foone🏳️⚧️ • • •Bill Ricker
in reply to Foone🏳️⚧️ • • •Athena L.M.
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Athena L.M. • • •Rue Mohr
in reply to Foone🏳️⚧️ • • •GhostInCerulean
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to GhostInCerulean • • •mage_of_dragons
in reply to Foone🏳️⚧️ • • •Bill Ricker
in reply to Foone🏳️⚧️ • • •Mayday! Mayday! Robot
in reply to Foone🏳️⚧️ • • •Tom Dewar
in reply to Foone🏳️⚧️ • • •abrasive
in reply to Foone🏳️⚧️ • • •Yuki Meadows
in reply to Foone🏳️⚧️ • • •stilescrisis
in reply to Foone🏳️⚧️ • • •compiler-explorer/docs/AddingACompiler.md at main · compiler-explorer/compiler-explorer
GitHubBlackbirdSR72
in reply to Foone🏳️⚧️ • • •programming language
Contributors to Wikimedia projects (Wikimedia Foundation, Inc.)Canageek
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Canageek • • •Canageek
in reply to Foone🏳️⚧️ • • •æ ⎎
in reply to Foone🏳️⚧️ • • •longjmp()
Tim 🎮
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Tim 🎮 • • •Tom Forsyth
in reply to Foone🏳️⚧️ • • •instagram.com/cobrakaiseries/r…
Cobra Kai on Instagram: "Johnny’s a point A to point B type of sensei. 👊 Never let a "curvature" slow you down."
InstagramOneironaut
in reply to Foone🏳️⚧️ • • •Bill Ricker
in reply to Foone🏳️⚧️ • • •(Otoh for intending to run on home PCs with 8088, 386, 486sx (emulated floating point only), that might be excusable? CORDIC integer trig is amazing but avoiding it might be forgivable.)
s0 Traingirl Era
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to s0 Traingirl Era • • •MontyOnTheRun
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to MontyOnTheRun • • •MontyOnTheRun
in reply to Foone🏳️⚧️ • • •well, given that it is not graphically intensive, it makes sense. I just assumed people went the ASM way by default.
Thanks for the clarification!
Gabe
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Gabe • • •BetaRays
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to BetaRays • • •Oliver Sampson
in reply to Foone🏳️⚧️ • • •Rue Mohr
in reply to Foone🏳️⚧️ • • •StartPlayingSound:
POP AX
POP BX
POP ES
POP SI
CALL EnableSound
PUSH BX
PUSH ES
PUSH SI
PUSH AX
🤣
Timo J
in reply to Foone🏳️⚧️ • • •Datawuppi
in reply to Foone🏳️⚧️ • • •that reminds ne of my Amiga BASIC days. As it had no sleep or somesuch, it actually required:
FOR I 1 TO 1000; NEXT
IIRC that where ~3s or something.
And yes, that thing was in the manual for some of the example programs.
Ian Scott 🐙
in reply to Foone🏳️⚧️ • • •@jbqueru@floss.social
in reply to Foone🏳️⚧️ • • •Jeremy Visser
in reply to Foone🏳️⚧️ • • •I had a client that used the computer mouse inverted.
But not in software. I mean physically turning the mouse around 180° and having the cord come out where, uh, the tail would be, I guess.
(Now that I put it like that, it makes sense why someone might do that.)
The end result being both X/Y axes are inverted, and the buttons are in the wrong place.
But it *worked*. That's the way the brain learnt it. It's like spending your whole career in ETAOIN SHRDLU only to discover the rest of the world is on QWERTY. You'd probably just stick with what works.
Foone🏳️⚧️
in reply to Jeremy Visser • • •I knew a kid back in the 90s who played NES games with the controller backwards
Krutonium://
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Krutonium:// • • •Vincent Sparks
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Vincent Sparks • • •Ingo
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Ingo • • •Foone🏳️⚧️
Unknown parent • • •Foone🏳️⚧️
Unknown parent • • •Jonah
in reply to Foone🏳️⚧️ • • •Foone🏳️⚧️
in reply to Jonah • • •Jonah
in reply to Foone🏳️⚧️ • • •@onfy They did make the RS/6000, including that one weird PowerPC ThinkPad, so there's that…
All the RISC computer manufacturers just couldn't get rid of the idea that *their* workstation will sell despite being so expensive and they'll get rich despite lacking any plan for Step 2 in their 3-step scheme. Shame.