anyone feel like this is an invitation to crack a game?
yes, even a bible game?
anyone feel like this is an invitation to crack a game?
yes, even a bible game?
STEP ONE: unpack with UNP.
UNP 4.12 Executable file restore utility, written by Ben Castricum, 07/22/96
Special version registered to Otto Stock, distribution prohibited.
processing file : ..\BB\BB.EXE
DOS file size : 73447
file-structure : executable (EXE)
EXE part sizes : header 512 bytes, image 72935 bytes, overlay 0 bytes
processed with : LINK V3.60, V3.64, V3.65 or V5.01.21 /EXEPACK
action : decompressing... done
new file size : 81792
writing to file : ..\BB\BB.EXE
Step two: loaded it in ghidra, do a string search. We find the "Oh, oh! This doesn't seem to be an original disk!" at 1fed:27cd. Since this is segmented, we can't just search for that string.
but we CAN do a scalar search for 0x27cd!
Which leads us right to FUN_1000_87c7, containing this code:
FUN_1000_8798 seems to be the magic. It gets the current drive, then calls FUN_1000_8739.
FUN_1000_8739 in turn, calls a low-level BIOS disk drive call. Perfect chance to do some copy protection
Here we go. It gets passed a sector number, and it confirms that the sector doesn't exist, but the sector before and after it do.
(with a free disk subsystem reset and read of sector 1 in the middle)
They pass in sector_num of 0x2ca.
So presumably on the original disks they did some special formatting (or manually marked that sector as bad?) so that sectors 0x2c9 and 0x2cb existed, but 0x2ca didn't.
naturally if you copy this program onto another disk (or a hard drive), all those sectors will be valid, so it'll fail the check.
Anyway that'd be a pain to replicate on a real disk or a virtual DOSBox hard drive.
So, lets just patch out the check, or more accurately, the consequences of the check.
at 1000:87eb we have 74 1d:
JZ LAB_1000_880a
LAB_1000_880a is after the print-uh-oh stuff, and it's jumping if the result of FUN_1000_8798 is non-NULL.
sticking 74 1D into https://shell-storm.org/online/Online-Assembler-and-Disassembler/, it tells us it's "je 0x1f" (JE is a synonym for JZ).
So stick JMP 0x1f into the assembler, and we get EB 1D back.
So we need to find that 74 in the binary and turn it into EB
there's definitely some way to make ghidra tell you the fill offset of the current instruction, but I can't remember it, so I go to Bytes view, select the next 32 bytes or so, and search the EXE in a hex editor:
Bingo, at 0x882B. Change to EB & save it out to BBCRACKD.EXE
And we're in the game with no "YOU PIRATED THIS!" message.
Done.
@Canageek not usually. I've run into one application that did a tricksy anti-cracking behavior: It loaded some plugins when the "hey this is trialware, please buy" dialog appeared, so if you patched that nag screen out, you'd have no plugins
@foone Do you often get games checking for this and introducing errors? I know CRPG Addict has had issues with cracked games where they would introduce problems if you bypassed the copy protection in the most obvious way
@DotMaetrix apparently!
(I personally am a bridge-builder, not a bible-builder. Though I have been tempted to write my own bible more than once)
@foone you're telling me a Bible built this game?
this angel has no idea I just cracked their game
and there at offset C13B in the decompressed EXE, we've got the font.
it's 8x8 fixed width (like an NES font!) so we don't need to extract widths from anywhere to make a death generator
@TomF fun fact: that distinctive G is how I double checked I had the right font
@foone That capital G is a crime. Lucky thing this game isn't going to have to use it for anyone important.
SHIT WHO TOLD THEM?
@foone
Thou shalt not copy the floppy!
That one is a joke, but the credits do actually have an anti-piracy message followed by Exodus 20:15: Thou Shalt Not Steal
The funny thing is: They're inconsistent with the fact it says "Thou shalt not steal".
Why? Because this game has FOUR BIBLES in it. Only one of those is the King James Version.
NIV & Revised Standard say "You shall not steal."
Living Bible says "You must not steal."
Only the KJV says "Thou shalt not steal."
Come on guys, you could have made it switch between the different versions of the bible based on the current setting of which version to use!
I'm gonna have to hack that in, aren't I?
the file DAT3 has chunks named SODOMA and SODOMB
Come on guys, just 23 more and you'll be onto something!
there's some kind of compression going on here that's inexplicably bad?
I modified some of my Carmen Sandiego scripts to handle its files, so now I can see what chunks are where:
https://gist.github.com/foone/a8e2455dd0c5456e513cf69c56b727c8
you might notice I did DAT1, DAT2, DAT3, and then DAT5: DAT4 is completely different and apparently part of the copy protection?
hey @dosnostalgic: isn't there a way to mark clusters as bad in Norton Utilities? I didn't see it under DiskEdit
(I'm trying to recreate the copy protection)
1 bad sector, eh? hopefully it's the right one!
Doesn't work. Back to DOSBox so I can have a debugger!
oooooh! I'm totally wrong.
this is doing an int 25 DOS 1+ - ABSOLUTE DISK READ.
I set a cluster to BAD.
This thing doesn't even USE clusters! it's raw sector access!
meaning this is a trickier compression than I thought: They left out a sector! it's just not on the disk.
They had to do this in the formatting step!
looking into the freedos FORMAT source. maybe I can easily modify it to skip over 0x2CA
okay sector 2ca is on CHS 19/1/12
dang it, I started putting data into DEBUG.COM, then I realized I have no way to paste in the data I'm generating in a python script.
I mean, not without designing a new keyboard...
@galibert at the moment, just an emulator format.
@foone With a upd765, it's not trivial (it is with a wd1772 and friends). What is your target though, a real floppy or some emulator format?
keyboard written (or rather, a script for my scriptable automation keyboard project). it generates a disk format or passing to int 13 ah=5, but once it generates it it then types it in at the keyboard. So I'll run it, switch to 86box, and let the keyboardscript type it into DEBUG.COM
arg. I should have guessed debug.com has a tiny tiny input buffer, and it can't handle being asked to hold 68 bytes at once
meh this is a pain. to the NASM!
note to self: before you try to CALL a function, make sure you have a stack first.
I may have created the disk
20 bad sectors? No, I fucked up BAD somewhere.
I had off by one errors in two directions! but sadly they didn't cancel each other out.
@vfig no, this is a mostly death-generated-unrelated bit of nonsense
@foone wait, are you adding norton disk doctor to the death generator??
SCANDISK really doesn't like this disk. it's sitting here forever looking for a sector that doesn't exit
well that's probably not a great sign
@dryak I'm not sure yet!
@foone but did the original game ship on weirdly formatted disks? Or did they merely punch a whole in the physical media?
I wrote a simple test program to try reading the 3 affected sectors, and... they're all successfully read on my disk. I think I have YET ANOTHER off-by-one error, like I'm reformatting the wrong track.
this would be easier to deal with if I didn't have to juggle 2 emulators, one of which has debugging and one of which supports flux-level disks
I might just have to write a program to check every sector on the disk, to confirm where I'm overwriting
said program is now runnign
@evraire that sounds like a lot of work for 5am!
@foone Add support for flux-level disks to emulator? 🤔
BAD sectors start at 0x396? I was aiming for 0x02CA!
they go to 0x03a7: that's 17 sectors, like I was trying to write, but presumably they're showing as BAD because I put them in ENTIRELY THE WRONG PLACE
I told it to write to track 19h (dec 25)
but it's track DEC 19 that I was aiming for.
WHOOPS
@baffinsquid clearly you are learned in the ways of scripture!
@foone But Exodus is in the Old Testament, while newer instructions in Mark 16:15 state "Go into all the world and preach the gospel to all creation", which in terms of physical media should be interpreted as "Copy that Floppy"
bingo! it fails to read sector 0x2CA but neighboring sectors are fine.
I wonder if it'll pass scandisk? Lemme mark the cluster bad first.
nope, crashes scandisk as well. Shame.
lets see if I can copy files onto it
Sector not found writing drive A
Abort, Retry, Ignore, Fail?
WHAT DO YOU MEAN? IT'S MARKED BAD!
I probably have an off-by-one error in the FAT edits
I (mostly) WIN
The game loaded with no "bad disk" complaints.
it then froze because of there being some files missing from the disk, but I don't actually have enough space to write them all. I'm not really sure why that is.
but that's a separate issue! the important thing is, I bypassed the copy protection without cracking it!
this article confirms it came on 720kb 3.5" floppies or 1.2mb 5.25" floppies:
(there's CD copies on ebay. I ordered one, to see if they're using the same copy protection or if they came up with a different method/cracked it themselves)
https://www.tampabay.com/archive/1992/12/19/exploring-scriptures-electronically/
and this warez NFO says it came on 3 720k disks.
this internet archive upload is technically mislabeled: it says it's the demo, but it's actually the full game: it's just stuck in demo mode because of the copy protection.
https://archive.org/details/BibleBuilderSW1992EverbrightEducational
this site says it has some cracks for it. It'd be interesting to see how they work, but I don't have an account for them:
the angel wants me to insert disk #1.
man if I had a nickel for every time an angel talked to me about floppy disks...
okay the disks need to look like this:
DISK1:
bb.exe
bb.ico
dat1
dat2
dat4
DISK2:
dat5
DISK3:
dat3
my version isn't exactly right, I was assuming 1.44mb disks. I need to recreate my tool but with 720kb in mind. Then I can make 3 disk images that'll work
I also need to put the cracked version up on the internet archive to play, natch
I'm not 100% sure that disk file listing is original, but it seems to work. The game will ask you for different disks as you go around the program, and that seems to match
also I don't have a good way to get a stream image out of 86box (because it uses a format I don't have any other tools that support, 86f), so I'm gonna have to run the code on a real PC and then image the resulting disk. Yet another reason to fix my code for 720kb disks
also need to:
#1 figure out that FAT error. I'm in the wrong cluster or something.
#2 write a wrong-sector instead of a missing-sector. writing only 17 sectors per tracks might accidentally leave behind an un-erased 18th sector from the previous formatting, and that's gonna confuse everything
much to do. unfortunately it's 6am. so it'll have to be done elsewhen.
@MishaVanMollusq more importantly, can the tower of babel run doom?
@foone can you build a tower of Babel in it?
@foone I'd be curious to see what fsck.msdos would do with it.
@RueNahcMohr nothing, I think. This is lower level than filesystem
I'm doing a little research into copies of this game online, and so far I've found three distinct versions:
1. a 1996-dated version
2. a 1996-dated version with a savegame from 2021 because someone checked it worked and accidentally put the savegame in the zip/rar they distributed
3. the same 1996 version, but with a 2009 date on the accidental save game.
I need to build me a tool to make this sort of research more automatic. I wanna give it a dozen copies of the game and have it tell me how many variants I have and what files are different, what dates are different, etc.
then I just run it on EVERY GAME I CAN FIND
why would I hack one game when I could hack EVERY GAME ?
mwahahaha
the game is running at 640x350 resolution, an EGA format.
POP QUIZ: why did they split the opening image into two separate chunks in the data file?
@UpLateGeek Coming Soon! the new 21st century bible from Church™ that includes "THOU SHALT NOT INFRINGE COPYRIGHT" as a commandment!
@foone it’s not even stealing, it’s copyright infringement. Two completely different things.
@jernej__s it's planar, but that's not directly why they're doing it. It's because they don't want to have chunks that decompress to more than 64kb, because then they'd have to span segments
@foone 64k segment limit? Is EGA 640x350 mode also planar like VGA?
eww, this file format doesn't store the width, it stores the stride.
So it's (320,200) for an EGA 640x200 image, since it's 4 bits per pixel.
it checks each DAT file it opens to see if it starts with MZ.
I guess it can accidentally open EXE files or something?
SOME PROGRAMMERS NEED TO BE STOPPED
alloc16((((DAT_1000_c79a + 0xfU >> 1 | (uint)((DAT_1000_c79c & 1) != 0) << 0xf) >> 1 |
(uint)((DAT_1000_c79c >> 1 & 1) != 0) << 0xf) >> 1 |
(uint)((DAT_1000_c79c >> 2 & 1) != 0) << 0xf) >> 1 |
(uint)((DAT_1000_c79c >> 3 & 1) != 0) << 0xf);
code that indicates you're on 16-bit segmented x86 or you're about to break your PC:
*(uint *)0x0 = uVar2;
a fun part of naming functions that you don't understand yet is that it makes it look like the program was written by a really terrible programmer with a special weakness for function naming.
calculate_some_things()
COULD YOU BE MORE SPECIFIC PLZ
I found the decompression routine and I hate it. it's big and complicated and there's calls to subfunctions.
reimplementing this will be a nightmare
step 1:
fill a table of 256 words with 0xFFFF
<dasharezone>OH SHIT IS THAT A MOTHERFUCKING HUFFMAN TABLE?</dasharezone>
calculate_some_things CHANGES DS? THE FUCK?
segmented x86 is great, because in this code:
MOV AX, [1234]
CALL 0823:0000
MOV AX, [1234]
You can't be sure that AX is set to the same thing, even if the function at 0823:0000 doesn't touch the memory at 1234. It might be a DIFFERENT 1234, because 0823:0000 changed the value of DS
that tells me that someone wrote this in assembly because compilers do not do this.
frankly, humans shouldn't do it either, but unfortunately nothing stops them
void allocate_36800_bytes(void)
reasonable functions that imply good things about how your program is designed
I know programmers who use return values and parameters and they're all cowards
this program is either doing something sneaky and clever (overwriting its own code dynamically) or it's doing something stupid (calling a function with no body)
I pray (no pun intended) that it's the latter
I would say it's the result of a badly written debug macro, ie there's a function that only prints #IFDEF DEBUG, instead of a function that's only called if DEBUG is defined.
except it takes no parameters, so that'd only work if it's a debug function that examples globals and prints about them.
which is possible, this program is global-heavy
I typo'd that as "global-heaven" which I guess is also technically true, in more way than one. Heaven is a big part of this game.
@wolf480pl it is! and a very accurate one that describes what it does
@foone looks like a reverse-engineered name :P
@trucy main? C wimps!
REAL programmers put everything in _entry
@foone using functions is a sign of weakness, put all code in main()
char * strlen(void)
I AM GOING TO TRY TO EXPLODE YOU WITH YOUR MIND
call it twice in a row! that makes even more sense!
sometimes the length of a string takes some time to settle, so to avoid race conditions you should always call strlen twice and check that the results match
@usagi I pray for anything that lets me avoid having to reverse engineer self-modifying code
@foone@digipres.club You pray for ghost functions? :neocat_flop:
CLI
MOV SP ,word ptr CS :[DAT_1000_c780 ]
STI
ALRIGHT, FUCK YOU. I'M GOING TO BED
changing THE STACK POINTER? in a function called multiple times in the decompress?
WHAT THE FUCK ARE YOU DOING?
I had the bad idea of using 16bit protected mode to help me extract the decompress function, but there's a lot of reasons why that is probably a very bad idea
I kinda want to yank the function out of the EXE into a separate tool and just call it remotely, but it's so globals-based that I'm worried it'd break writing some address I'm not emulating.
So being able to have the pages not loaded and watching for page faults would be useful! unfortunately, 16bit real mode
maybe I should modify the emulator to give me Fake Protection. I need to write my own x86 emulator.
again. sigh.
11 years ago!?
it supports 20 entire instructions! (assuming you count Jcc as one, if not it's 25)
is that the same 25 instructions this code uses? NO!
also FUCK I wrote a 32bit x86 emulator! I need a 16bit emulator!
yes that's right, this isn't the first time I've gone "writing an x86 emulator would be easier than understanding this decompression code"
I hate decompression code THAT MUCH
in fairness, x86 is documented. I can look up how it works. I can't do that with this decompression
@wilkie it's entirely possible!
@foone are we the same person. I'm thankful to relate to somebody else in this very specific way.
what I want is a way to iteratively isolate a function.
Like I tell it "hey, grab these bytes from this EXE. that's a function with this calling function. now call it, but trap whenever it accesses global data I haven't defined or calls other functions"
then I can go through and add those extra functions until I have a magic Just Run The Function tool
I would also like this to be less than the slowest thing possible, but unfortunately my experience with x86-on-python has shown that to be tricky.
@StumpyTheMutt oh god group 4. had to deal with that in a previous life: the problem was that we had millions of group-4 encoded TIFFs, and needed to give them to users as PDFs.
so we had a tool that exploited the fact that both PDF and TIFF support group4, so it just swapped the headers. It, uh, mostly worked!
@foone Many eons ago I wrote a fax Group 4 decompressor in x86 assembler. Several senior people looked over the code and we all worked hard to remove every extraneous clock cycle we could find. It worked great and was really fast, but it would not be easy to figure out without the extensive commentary I added.
16bit decompression is way worse than 32bit decompression, btw.
there's multiple different DS here and I'm not sure when it's changing. sometimes it's loading stuff from 4 and 8 and that can't be normal, those aren't real addresss
okay so, see the "mov ES, [bp+06]"? That's normal code that is getting the arguments to the function, right? which are at +4 and +6 on the original stack pointer. They're accessing through BP because the stack pointer changes when they push those other registers, to preserve them.
HOWEVER, look at the DS and SS. SS is 1810, but DS is 2811.
The problem is that that instruction should be accessing DS:bp+06, not SS:bp+04. and those are DIFFERNT
SS:BP+06 is in linear addresses, 27748h
but DS:BP+04 is 37758h
those are different places. this shouldn't work, unless I'm missing something very badly
it IS pulling from the SS register, but I do not know why
wikipedia has a great article documenting the ModR/M format used in x86 encoding, including a very handy table explaining how it works (in 16bit mode, too!), but it never mentions segment selectors once.
I'm now reading the DOSBox source code to try and figure this out because I have a normal life
okay it's doing some weird effective-address calculating that means there's an implicit SS: prefix on that address.
Ghidra is not showing this to me, which is very very confusing
I can't find the exact details but that's definitely what's happening
after it makes that array of 256 0xFFFFs, it makes another array that's the values 0-255.
suspicious. definitely doing some kind of huffmanning
the function that sneakily changes DS also changes SI.
fun.
this is definitely hand written assembly because compilers make more sense than this
@vfig thanks!
@foone ah here, page 2-13 of https://edge.edx.org/c4x/BITSPilani/EEE231/asset/8086_family_Users_Manual_1_.pdf
@foone its a long time since i did 16 bit x86, but iirc SP and BP use SS segment by default, unless theres an override prefix.
@TomF thanks!
@foone Yes, I found a StackOverflow answer confirming my memory. Not very compelling I know but:
"Memory operands using esp or ebp as the base register default to SS, and so do the implicit accesses for stack instructions like push/pop/call/ret."
(odd they reference esp and ebp rather than sp and bp but OK)
@foone Wait wait wait I vaguely remember this. Hang on, I will 100% check this, but FROM HAZY MEMORY by default if the base register is BP or SP, then it's assumed to be a stack reference, and thus use SS.
Again, lemme check...
@foone Segment selector overrides is here:
https://wiki.osdev.org/X86-64_Instruction_Encoding#Legacy_Prefixes
0x36 is the SS segment override, but I'm not seeing it. You can see the 0x2E which is the CS: segment override.
@galibert god I WISH I could find a game written by people as mentally twisted as to attempt a multi-threaded cooperative-multitasking DECOMPRESSION ALGORITHM on DOS.
@foone Cooperative multithreading?
@galibert but the disk IO api is all synchronous! by technical necessity, really... although I guess maybe you could bypass both DOS and the BIOS and write to the controller directly, and use DMA and handle the IRQ yourself...
that might pass in the demoscene if you wanted to have smooth animation while loading
@foone well, you put the file reading and the actual decompression in different threads, switching when out of data... I could see not entirely sane people doing it
this is a totally normal thing to think in 2025, guys.
trying to script the DOSBox-X debugger by making a keyboard probably counts as using my powers for evil.
I might still do it though. Someday I'll make a nice clean way to interface with DOSBox from python, but that would require a lot more work than just using a keyboard I made
"what if I just smuggled in filenames using floating point variables?"
VERY NORMAL THOUGHTS, FOONE
anyone know off the top of their heads how many characters you can reliably store in your standard x87 floating point register?
sadly I'm giving up on the floating point idea. I'm just gonna rewrite the EXE between every iteration. it's a bit easier.
I ran into a minor problem because I had to rewrite some code, as I'd written "MOV DS,CS" which won't work: you can only move to segment registers like DS from general purpose registers.
So I rewrote it as:
MOV AX, CS
MOV CS, AX
Yeah that second line is a typo. I typed CS instead of DS. CS is where it gets instructions from, so the x86 will not let you write to it, although the encoding allows it. So yeah, that didn't work, it triggered a CPU fault
setting the ORG wrong in your x86 assembly really fucks it up. It was calculating the addresses all wrong!
fopen("w","1234568.ext")
WHY ISN'T IT WORKING?, I say, while hitting myself in the head
I miss the days when I could write:
fwrite(ptr, size, 1, fp);
and not worry about what segment ptr is in
ugh why am I even using fwrite. it's a pain when I'm writing assembly. I'm gonna give up and just call int 21h
normal thoughts for a python programmer:
"THIS C SHIT IS TOO HIGH LEVEL!"
I can't tell if I'm 5 bytes off when writing out this file. That's REALLY annoying
fuck it I'm overdumping every chunk by 5 bytes and I'll figure if I need to trim them later
@lizzy the what?
YES! I extracted and decompressed every chunk in DAT1!
so I hacked the game like this:
I wrote a little assembly program that basically just goes:
unsigned short size=64020;
ptr=loadChunk("internal.ext");
f=fopen("12345678.ext","wb");
fwrite(ptr,size,1,f);
fclose(f);
exit(0);
then I compile that and I wrote a tiny python script to inject it into the EXE (by just copying the old EXE and pasting my shit in at an offset), and then I call that EXE once per chunk.
well, I call a dosbox-x exe with a conf set to just run it and then exit.
but since I didn't want to deal with writing code to parse the size and filenames and shit from the command line, I just have my script write a new EXE per chunk.
It just takes the old EXE, applies patches over where size, "internal.ext", and "12345678.ext" are in the EXE, then it runs that new EXE
(that's why the filenames are a full 8.3 characters, so I can overwrite them with any valid DOS name)
it worked successfully so of course as soon as I try it on DAT2, it starts hanging.
TO THE DEBUGGER, BATMAN!
@lizzy oh right. I missed that. hah
ahh, I forgot the implicit state.
I'm injecting into the game when it first tries to load the title screen, which it does before it loads DAT2,DAT3,DAT5
hopefully I can just patch it to load DAT2 instead. That might break if it needs to use DAT1 resources first... I don't THINK it does? but I'm not sure.
... they put BIB100.MUS in DAT1 and DAT2.
MOTHERFUCKER
I can't just extract them all to the same folder, because there's duplicates. possibly different duplicates!
I should do this to Carmen when I get back to that project. Then instead of understanding it's compression, I just bypass it completely
yep, changing it to DAT2 breaks it. I can't swap DAT2 into the place of DAT1, because it does indeed need other chunks from it.
UGH.
maybe I can just add my own call to load_dat_file at the top of my assembly. I do NOT want to have to relocate it, that sounds like it'd be REALLY FUCKING HARD
my code starts right AFTER the chunk is loaded. I'd have to move it. MOTHERFUCKER
ALRIGHT HERE'S WHAT WE'RE DOING. WE'RE DOING IT LIVE.
loadChunk is gonna fail. Fine.
We ignore it, then call load_dat_file on the correct dat, then we call loadChunk again!
This is stupid, but it means I don't have to move the fucking start position!
IT WORKS! I have DAT2 now!
lets see how it breaks when we try DAT3...
all dats extracted. I need to write some code to convert the ART files, but they're simple and I understand most of them already. but now I have 89 example files to work on.
whoops, I got my lefts and rights backwards. silly little-endian formats.
there are two ART formats: TITLEA.ART is the first one, and TITLEB.ART is the second one.
ewww. two halves of one image but they're encoded differently!
WHERE'S YOUR HEAD?
I think what's happening is that there's some animation supported in this format, and the extra animation bytes in the header are throwing off the parsing of the image (explaining the color and scrolling) and then I don't support the sprites yet (explaining the missing head)
I fixed the scrolling problem, but the palettes are still deeply fuckt.
okay I think I've got all the non-animated images extracting properly.
other than one of jesuses which I can't figure out which palette it uses.
sadly this includes SODOMA.ART and SODOMB.ART, which render completely inaccurately.
but hopefully I will be able to fix this tomorrow
@th would you happen to be colorblind? :)
@foone they're the same picture?
@Computeum sadly my COM is getting injected into an EXE, so I do have to worry about segment registers. I'm existing inside its memory model.
The FCB handling is a good idea, but I was worried any arg handling the game already did would have messed it up. I've seen so many games overwrite the args with their own shit, it's a pain
@foone May I insert some really old news? DOS already parses the command line for you, putting up to two file names into prepared FCB. Add a zero byte and you'll fine to use stream calls - or stay with FCB handling.Using a 128 bye loop does no longer make a notable speed difference with today's machines due all their huge blocks and caching...
https://archive.org/details/The_MS-DOS_Encyclopedia_Ray_Duncan/page/109/mode/1up?view=theater
Also, doing it as a com program (tiny memory model) saves the need to copy segment registers or care for them at all. Plain code like on an 8 bit CPU :))
I did some looking into the file format. It's tricky because it doesn't seem to ever declare how many sub-images are in an image, it just determines it by the header length, and the header length is something like n*11+48?+16? and I'm only like 80% sure of those numbers.
I need to find the code in the binary for loading this. I've just been staring at the hex. it seemed simple enough, but the multi-image format is more complex than I thought
it's definitely multi-image, rather than simply animated. For example, the puzzle pieces at the end of a run are all in one chunk, they're just different images inside it.
time for some late-night reading
no, angel, I will never give you up
@retrovg nah, this uses on-disk copy protection.
although now I'm amused by the idea of a bible game that makes you enter verses from the bible to prove you're a christian
@foone is this a copy protection screen? It sounds like heresy.
@sapphicselene @retrovg This game is actually quite liberal on which translation it uses! It lets you pick between 4 editions:
* Revised Standard
* King James
* New International
* The Living Bible
@foone @retrovg I get locked from the game for using the NRSV instead of "The KJV that God Himself wrote down" lol
@Mondobizarrro he might, especially if you live in Sodom
what is the theological use in teaching children to identify hellenistic-era settlements in the Levant?
@nil I know, right? I can't wait to extract all their animations.
@foone i love the lil guy on the candle
PROTESTANT SPOTTED
(different religious traditions number the 10 commandments differently. The catholic 4th commandment is "Honour thy father and thy mother")
oh hello there spooky mario 64 creepypasta, what are YOU doing here in this EGA bible game?
okay I'm back to trying to do the floppy hack correctly (so, on a DD disk) and OH NO I have to figure out where the sector is again.
I hate converting between LBA and CHS
track 44, side B, sector 3
nope fucked up again
mov CX, 1300h ; TRACK 44
HEY FOONE, IS 44 IN HEX "13"?
BTW "BAD" is technically incorrect. Those sectors are not bad, in the sense we usually mean it: a spot on the disk that can no longer correctly store data, and reading from it will give checksum mismatches.
No, those sectors are just not there. The drive controller is on the track, reading for those sectors numbers, and it finds nothing that matches.
I'd love to show you what the low-level track looks like but I can't load .86f files the HxE tools. Once I do this on a real machine I'll have an image, though
FAILED AGAIN.
somehow sectors 321h, 323h, and 329h are bad.
the fuck?
"Note: This function does not set the INT 1E vector to point at the returned parameter table; it is the caller's responsibility to do so "
whaaaat
so the notes for int 13h/AH=05h say to call int13h/AH=17h first, to set up int 1E
but the notes for Int 13h/AH=17h say not to use it, because it doesn't support 1.44mb drives, so use int 13h,AH=18h instead. but int13h, AH=18h doesn't set up INT 1E, which is THE ONLY REASON I'M CALLING IT
fortunately DOS has a simple syscall I can use to set the vector. And surely the int 13h,AH=18 returns the new vector in the same registers as the dos set-interrupt syscall, right?
RIGHT?
NOPE! int13h, AH=18 returns it in ES:DI and int 21h,AH=25h takes it in DS:DI
okay I think the problem may be slightly weirder: norton disk edit told me the wrong format for this drive.
it's not 8 sectors a track, it's 9.
the one annoying thing about doing this in an accurate emulator is that it's accurately emulating how slow the floppy drive is
so my current format seems to work, but it leads to the whole track being bad, apparently.
sectors 02C7h through 02CF are bad.
I think that means I'm formatting the right track, but with the wrong sectors.
BOY IT WOULD BE NICE IF I COULD OPEN THIS IN A FLUX VISUALIZER
save me foxflux:
https://dbalsom.github.io/fluxfox/index.html
HEY WHO STOLE MY WHOLE TRACK? WHERE ARE THE SECTORS?
FUCK! I clobbered ES! my code assumed ES never got modified, but when I had to do all the set-interrupt stuff I had to clobber it myself
I'm trying to format the disk with some RANDOM MEMORY as the sector numbers! no wonder it doesn't fucking work
I'm amazed I didn't notice that in the debugger!
OH WAIT I DON'T HAVE A DEBUGGER
There we go.
Track 39, side B, and the sector numbers go 1,2,3,255,5,6,7,8,9.
No sector 4, so it'll fail to be found. Which is what I want.
@foone What software are you using for this? I don't think I've seen this one before - unless it's Applesauce, which I don't use very often.
@philpem FluxFox!
https://github.com/dbalsom/fluxfox
I'm using the web interface:
https://dbalsom.github.io/fluxfox/index.html
And tested in DOS. It passes the copy protection!
okay, so I've patched out the copy protection, easy.
I've created the hacked disk, as 1.44mb, then created it again as the correct 720kb.
BUT IT TURNS OUT THERE'S ONE MORE
That "DAT4" file? It's actually supposed to be to let you install this game to a hard drive, and it works by building signature of what your computer looks like (or as well as it can in DOS), and comparing it to a file.
It naturally encrypts the file. But it does it badly, so I can probably bypass it without too much trouble
this explains why all the copies online are incorrectly cracked: They zipped up their installation, which worked fine... on their machine.
as soon as you run it on a different machine, it fails
@foone OH DAAAAMN. This is wicked cool.
if you install this game and then update your BIOS it will fail copy protection.
if you install it and then upgrade to a VGA card, it'll fail.
add more ram? another floppy drive?
failed
I'm surprised this game didn't come with a dongle
this game might seriously have some anti-cracking features that I just narrowly avoided?
why is this game so protected?
now I gotta try to trigger the anti-cracking
nah, it's not an anti-crack routine, it's just more PC-fingerprinting.
nice try but all your nonsense boils down to one 16bit number and I can just copy out the correct result to stuff in the file
DAT4 is 760 bytes, and how many are actually used?
15. It only actually looks at 15 bytes of the fucking file
@nabijaczleweli YUP!
@foone is the rest of the file random uninitialised buffer contents, in time-honoured unix tradition?
okay now I have a fake DAT4 that works.
next step, make a utility to make my own DAT4 for any PC configuration.
then I don't have to touch the EXE to crack it: I just write out the correct DAT4 signature, so the game thinks it's okay.
step1: grab the 11 bytes starting at FFFF:0005, and XOR them with 57h. Write out 11 bytes
step 2: call Get Equipment List (int 11), and OR the result with C1CD. Write out 2 bytes.
step 3: a miracle happens, write out the result as 2 bytes.
the step 3 is FUN_1000_c43a, a function that does a lot of weird math on some local data and segment registers and such. I don't understand it and I'm not sure I want to. I think I might just cheat and yoink it out of the EXE
hah! one of the things that changes the signature is if single step is set.
So if you try to walk through the program with a traditional debugger, you'll change the signature just by debugging it
yeah this thing is trying to fingerprint what processor you have in the age before CPUID.
and it's doing it by abusing the cpu flags, to try and figure out what CPU you're using.
okay it returns a value in AH, AL.
AL will be 00 for 8086/80186
AL will be 02 for 286
AL will be 03 for 386
AL will be 04 for 486+
AH is if you have an FPU. It'll be equal to AL, unless AL is 00, then it'll be 01.
so, for example, a 486DX will be a 0404, a 386 with no FPU will be 0003
they definitely got this code from elsewhere, because it's partially pointless:
whether or not there's an FPU in the system is ALREADY determined by the equipment list!
I think I found the source.
It's based on official Intel code:
INTEL RECOMMENDED CPU / MCP IDENTIFICATION CODE
I found an ancient copy here: http://ftp.lanet.lv/ftp/mirror/x2ftp/msdos/programming/hardware/cpuid3.zip
Found it through this code by R. Collins who fixed it to detect some systems more reliably:
"The infinity of the coprocessor must be checked to determine the correct coprocessor id."
deep!
this code can handle the situation where you have a 386 but with a 80287 FPU installed instead of the 80387 FPU.
That's impressive!
OKAY buildsig is working. I just need to write up some docs. I might leave that until tomorrow, but I'm uploading the new version now.
And done. For the first time in the nearly TWELVE YEARS this game has been uploaded on the archive, it works properly now:
ok, the CD version came in.
The game calls itself Version 1.02! (The earlier floppy edition had no version number displayed)
They just took out the copy protection. Boring.
the only other change I've noticed is that they slightly rearranged the credits.
they may also have tweaked some command line argument parsing? I'm not sure, I haven't looked into that part of the floppy edition
I tested my funky-format on a real system now! It worked fine.
You can see it here: This track (39, on side B) has the normal 9 sectors, but one of them has entirely the wrong sector number, it's 255 instead of 4.
So when we make the DOS call to try and read sectors 0x2CA, it'll fail to find it, because that sector should be in this track, but isn't.
I used a nine-track formatting with one clearly-wrong sector instead of just telling it to format only 8 sectors, because I wanted to make sure I was overwriting all previous sectors.
This was easier (for Reasons) than hacking a flux stream to just have no sector there.
I emailed the developer to see if he can answer some questions about how/why it worked, and to ask if he can get me an original floppy disk (image?)
076萌SNS is a social network, courtesy of 076. It runs on GNU social, version 2.0.2-beta0, available under the GNU Affero General Public License.
All 076萌SNS content and data are available under the Creative Commons Attribution 3.0 license.