-
Notifications
You must be signed in to change notification settings - Fork 920
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Figure out the exact set of compiler flags used to produce Diablo.exe (1.09b) #111
Comments
It's worth noting that individual compilation units can have different optimization settings (just to keep in mind). Another comparison with Diablo.exe 1.09b: I actually want to highlight in particular the line Under O1:
Under O2:
|
The project now uses /O1 for both debug and retail builds. I figured that's what was used, since the output size is much closer (760 KB). The main reason I never changed it was in the early days optimizations broke the code entirely. It took a few months of fixing up before it was stable enough to change it. |
You may also be able to find out other compiler flags by looking at the binary header (MZ/PE) and seeing what differs. Imports too probably. |
Good call, I noticed the original binary has about 2x as many import descriptors. Which is weird, since it there's only a few differences in the KERNEL32 imports. We'll have to look at that sometime. Also, the release build seems to scramble the code a little more, where as the debug build is closer to the original. Setting aside debug sections of course. It could also be a good idea to "trace" the developers steps. We know the game was originally written in VC 4.20, but then upgraded to 5.10. So perhaps generating the project using 4.20's default settings could get us closer. |
Sounds reasonable. Report back the compiler flags used :) I still don't have a Windows box to play with. |
Warning: Big writeup ahead! :) As @mewmew wrote up over here, more often than not, the compiler we currently use (5.0 SP3) generated slightly different instructions than the original 1.09b binary. The first thing I checked is the linker versions. For that I grabbed every version ever, basically, and confirmed that the linker used must be from 5.0 SP3:
While browsing for compiler option info, I stumbled over the For some reason, the compiler used by Blizzard was just more "clever" than the one we use... After going off-track by checking the The output was:
Looking at that options list it instantly clicked. See that So I just fired up VS6SP6, upgraded the project file, compiled, checked in IDA, and voilà: The instructions are identical (at least for the function from PR #135)! So, what I guess happened:
The 1.09b .exe has a creation date of 2001-05-12. This means that the newest VC++6 version they could have used it Service Pack 5 (released April 2001). Service Pack 4 (released January 2001) would be a good contender as well. Anyways, that was my journey today. :) What's left to do:
In retrospective, all of this makes sense seeing that |
That's way cool! Excellent work. |
@seritools Wow that's a great read and find, the asm is exactly the same (excluding different memory locations)! |
Beautiful, now we just have to figure out how they automated everything 😉 It seems tedious to have used two different toolchains, so maybe there's another option we're missing or they modded their VC6 installation. Awhile back I documented the versions they used for D2:
The linker for both SP 4 and 5 generates code exactly the same, I didn't test SP 6 since it was made long after those patches. We can assume that the D2 devs used their current setup to compile D1 at the time, so |
@seritools Wow Dennis, that's great! Hats off. Very happy that you managed to pin this down. |
Do we get similar sizes with the optimization? I am curious what a bindiff would result in. It might be useless to know, but I think that it would tell us how close we are. |
seritools@6f5f573 Managed to link the exe with 5.10 while compiling with 6 :) However in this constellation, PDBs cannot be generated, since the linker exits mentioning that the pdb format is not supported. A release build is possible though, and reports the correct linker version. For working at getting the code accurate I'd recommend just linking with v6 for now (as long as we don't see differences too big to tolerate). Generating release builds with full PDB info makes it a lot easier imho. What do you think? |
A few more observations:
@galaxyhaxz has noted that the VC6 linker starts the program code at 0x1000, while the VC5 linker starts at 0x400, which is a difference of 3072 bytes, so the resulting binary could be down to 741KB already! I'm going to check SP2, SP1, SP0 next and try linking with VC5 instead to see if I can get that simple function to generate correctly and maybe generate a perfectly-sized binary :O EDIT:
Checked the exact byte counts: 1.09: 757760 bytes SP0: 761856 bytes (diff. 4096 bytes, at 3072 of which are because I currently link with VC6) |
Awesome work! I think I might try this, because I am not thinking the pdb info to be too useful at this point considering we literally can cross reference the binary. 1024 bytes are still a lot though, but it could be worse. I am curious if we can get closer with pdb info . Also @seritools for that binary that is really close, can you upload it ? I would like to do a bindiff on it. |
PDB info is nice while comparing functions, since you can just jump to the function in question in the new binary, without remembering adresses, and getting all the type info that we already have for the original binary as well. (Generating with pdb info results in a much bigger binary btw) Since the binaries are rounded up to the next 1024bytes, maybe the original binary was just 739k + a few bytes? Every instruction we missed could be the one dropping us down to 439KB. :) Like there was/is (already reported to @galaxyhaxz) a missing function NewCursor in devilution, which did nothing but call to SetCursor in the original binary. A couple of bytes "saved". Also every differing optimization contributes to that. I'm gonna be at my pc tomorrow and will upload the binary then. |
And here is the exe :) |
Comparing the size with @seritools binary:
So the Devilution binary actually has 0x2400 (9kb) more in the code section than the vanilla. This is surely due to many decompiled functions having excessive variables. Interesting though is that most data is stored in |
Yeah, I only got the size way down when using the VC6 libs, the file was quite a bit bigger with the VC5 ones. (Tbh it was surpriting to me that the VC6 libs were working at all, since it supposedly has a different lib format, but maybe that's just not the case for the standard libs) |
|
See issue #13. Many functions and data are declared as |
At least information about |
https://github.com/diasurgical/devil-nightly/issues/15 We're pretty much done 🎉 In the end, Blizzard made sure to keep their software up to date it seems 😄 |
We can probably be 100% sure now :) Found this by change while browsing reddit: http://bytepointer.com/articles/the_microsoft_rich_header.htm |
Woha that is exiting :D But does that also say that the linker was VC6 SP3? (probably wasn't updated in the later versions) |
That is such a great find! Wow, what a buried treasure!! |
Funny, I actually knew about the |
@AJenbo Linker was the same from SP3-SP6. However SP4/SP5 can generate different code if the Processor Pack is installed. @seritools Once again, a great discovery! Oh, it's so great to be working with code from 2001. Could you imagine if Diablo 1 was released now with all those micro-optimizations and stripping the Rich header+file names? :P After comparing the Rich header with 3 different tools, below is a list documenting each value in the header. Interesting is that "Utc12_2_C" is a C compiler and not C++. So it's possible all the files were treated as C even with .CPP extensions. Also, there doesn't appear to be any asm/MASM references, which I guess implies
|
That was one of my very first assumptions about the project haha |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
Small update, @mewmew and I spend some time this weekend testing things out and by adding the /TC flag to the compiler we where able to get the RICH header to reflect Utc12_2_C instead of Utc12_2_CPP. As /Tc did not appear to change thing we're fairly sure that the whole project needs to be written in C instead of C++. I have started gradually rewriting the parts the aren't C compliant, there are a few thing that I don't know exactly how to deal with, but I hope to get it down to just a few issues. |
|
I think that just depends on where you look up the version |
This issue tracks the effort to figure out the complier flags used to produce Diablo.exe (version 1.09b). It may be considered a subtask of #11, as given information about compiler flags, we can ensure that the same input source code produce the same output object code.
From what I can tell, it seems like
/O1
is used rather than/O2
. This is based on the padding between functions./O2
produces (given that #110 has been merged):While
/O1
produces:This is to be compared to the original version of Diablo.exe (1.09b):
The text was updated successfully, but these errors were encountered: