I greatly enjoyed Oblivion, which was groundbreaking when it released. Its nature was green and vibrant, cities beautiful, quests intriguing. Certain things, however, were questionable; examples include faces and requiring the game CD to be inserted to allow play.
OllyDbg version 2.00 is an excellent debugger. The features I appreciated most were: visual representation of branches, modify (by assembling) in-place, and most of all the ability to save modified executable.
Begin by finding dialog string in program memory:
Text strings referenced in Oblivion, item 12170
Address = 00998F30
Command = PUSH OFFSET 009ED780
Comments = ASCII "Insert the Oblivion Disc."
Reference from:
00998F30 /. 68 80D79E00 PUSH OFFSET 009ED780
; Arg2 = ASCII "Insert the Oblivion Disc."
; function start at 00998F30
Stack:
0019FE4C [00960405 �. ; RETURN to Oblivion.00960405
00960403 |. FFD0 |CALL EAX
; calls "no CD" dialogue opener
Reaches following with arguments pointers to "no CD" strings:
0040D178 |. E8 A370FFFF CALL 00404220 ; \Oblivion.00404220
This is hit many times until one of the hits brings up the message dialog.
The condition is there to only catch that last hit:
INT3 breakpoints, item 0
Address = 0040D178
Module = Oblivion
Status = Cond
Disassembly = CALL 00404220
Comment =
Condition = EAX == 00A3086C
Revelation: the above code path was a red herring. It has nothing to do with displaying the error message; it does some sort of initialization of strings... maybe pre-loading localizations.
Reached only once, right before "no CD" dialog:
Update: also reached with CD inserted:
Last known call before dialog, but reached when CD is in too:
0096433B |. E8 1099AAFF CALL 0040DC50 ; \Oblivion.0040DC50
With no CD reaches:
0040DE37 |. E8 64ED0700 CALL 0048CBA0 ; [Oblivion.0048CBA0
; ...
0040DF86 |. 8B3D 54B29E00 MOV EDI,DWORD PTR DS:[<&USER32.MessageBoxA>]
; Entry point of procedure
; ...
0040DFB6 |. FFD6 |CALL ESI
Call into USER32.MessageBoxA to show the dialog:
0040E018 |. FFD7 |CALL EDI
Likely branch before USER32.MessageBoxA:
0040DF73 |> \A0 7051AD00 MOV AL,BYTE PTR DS:[0AD5170]
0040DF7A |. /0F85 B5000000 JNE 0040E035
; AL = 0 when no CD
; AL = ASCII of drive letter of CD drive containing CD when CD
Find writes to 0AD5170 via memory breakpoint on write.
The following writes 0 - probably some initial value:
00404AEE |. 881D 7051AD00 MOV BYTE PTR DS:[0AD5170],BL
The following writes drive letter of CD drive containing CD:
00404B40 |. A2 7051AD00 |MOV BYTE PTR DS:[0AD5170],AL
Start of "check many CD drives for CD" loop:
00404B00 |> /A0 7051AD00 /MOV AL,BYTE PTR DS:[0AD5170]
Returns 0 when argument is the drive containing the CD:
00404B32 |. E8 D2C85500 |CALL 00961409 ; \Oblivion.00961409
; returns 0 when drive contains CD (checks for :\OblivionLauncher.exe)
; returns nonzero when drive doesn't contain CD
; change to
00404B32 31C0 XOR EAX,EAX
00404B34 90 NOP
00404B35 90 NOP
00404B36 90 NOP
This causes the game to no longer require a CD, as long as the computer has at least one CD drive.
I want to remove this requirement.
This calls KERNEL32.GetDriveTypeA to check whether currently-checked drive is a CD drive:
00404B0E |. FFD5 CALL EBP
From API documentation, KERNEL32.GetDriveTypeA return types:
DRIVE_UNKNOWN 0
DRIVE_NO_ROOT_DIR 1
DRIVE_REMOVABLE 2
DRIVE_FIXED 3
DRIVE_REMOTE 4
DRIVE_CDROM 5
DRIVE_RAMDISK 6
This skips over the "does drive contain CD" if currently-checked drive is not a CD drive:
00404B10 |. 83F8 05 CMP EAX,5
00404B13 |. 75 32 JNE SHORT 00404B47
; branch taken when drive is not a CD drive
; change to
00404B10 |. 83F8 05 CMP EAX,5
00404B13 90 NOP
00404B14 90 NOP
This treats the first enumerated drive as a CD drive, irrespective of type.
Final approach
Thus, the entirety of the crack is made up of:
1. Force CD drive check stage to believe C: is a CD-ROM drive:
00404B10 |. 83F8 05 CMP EAX,5
00404B13 |. 75 32 JNE SHORT 00404B47
; branch taken when drive is not a CD drive
; change to
00404B10 |. 83F8 05 CMP EAX,5
00404B13 90 NOP
00404B14 90 NOP
2. Force CD check stage to believe C: contains the Oblivion CD
00404B32 |. E8 D2C85500 |CALL 00961409 ; \Oblivion.00961409
; returns 0 when drive contains CD (checks for \OblivionLauncher.exe)
; returns nonzero when drive doesn't contain CD
; change to
00404B32 31C0 XOR EAX,EAX
00404B34 90 NOP
00404B35 90 NOP
00404B36 90 NOP