ccomrade / c1-launcher

Open-source Crysis executables.
298 stars 25 forks source link

DX10 refresh rate fix #13

Closed vlad54rus closed 3 years ago

vlad54rus commented 3 years ago

Crysis 1 CryRenderD3D10.dll (32 bit 6156): 0x16F3E0 > 0F 19 00 89 Crysis 1 CryRenderD3D10.dll (64 bit 6156): 0x1C8345 > 67 0F 18 20 Posted by some dude on a Russian forum, this patch seems to make the game run at the current desktop refresh rate instead of choosing the lowest available. Can this be added to c1-launcher?

ccomrade commented 3 years ago

Yes, it can be added for sure. I quickly checked what the patch does and it looks very tricky. The guy who made it has some experience with x86 assembly and D3D API for sure. Is it possible ask the guy if we can add the patch to c1-launcher? I would manually backport the patch to all game builds as usual. We also need someone to test this. My monitor seems to have only standard 60 Hz modes.

vlad54rus commented 3 years ago

I'll try to ask him. Regarding your monitor - custom (resolutions) display modes can be created from the GPU control panel. Try creating second resolution with 50 Hz refresh rate.

vlad54rus commented 3 years ago

Ok, i've got positive reply.

ccomrade commented 3 years ago

Good. Hopefully I'll have time for adding the patch in the next few days. What's his nickname? Does he have a GitHub account? I'll mention him in the features list together with you.

vlad54rus commented 3 years ago

His nickname is Guzz, doesn't have a GitHub account.

ccomrade commented 3 years ago

The 64-bit version actually doesn't work. I'll try to fix it. The 32-bit version works, but it can be simplified. All you need to do is to put 0x909090 (3 nops) at 0x16F3E0 offset in CryRenderD3D10.dll (6156 build). It seems the guy wanted to make these patches unreadable or maybe he just used some tool that chose these weird values.

vlad54rus commented 3 years ago

Hmm, the 64 bit one does seem to work on my machine, as well does putting same 3 nops at 0x1C8345

ccomrade commented 3 years ago

The 64-bit one either has no effect (the code is not called) or it crashes (the code is called). Putting 3 nops there crashes as well. The 64-bit offset actually points to completely different location than the 32-bit one.

Anyway, I've found what exactly the working 32-bit patch does. It's actually one big nop instruction, which prevents value of RefreshRate.Numerator inside DXGI_MODE_DESC from being set. The whole structure is initially filled with zeros, so RefreshRate.Numerator becomes zero always. Zero refresh rate means that the default refresh rate is used, which basically fixes the bug.

After some digging through the disassembly, I've also managed to find where the code is located in 64-bit version. The offset is 0x1C8F45 in 6156 build. There's only one little problem. In 64-bit code, both Numerator and Denominator are set at once with one mov instruction as a single 64-bit integer. Unfortunately, this makes setting Numerator to zero while keeping Denominator value impossible. There's not enough space for such instructions. It seems that Denominator is always set to 1. However, according to the documentation, when Numerator is set to zero, then value of Denominator doesn't matter at all and it can be set to zero as well. All we need is replacing that one mov instruction with nops. And it really works. I've also used 6 nops instead of 3 in 32-bit version to disable the second mov, which sets value of Denominator. Just to make sure that 32-bit and 64-bit versions of the patch are equal.

I've tested it on Windows 7 and it always uses the same refresh rate as configured in Windows display settings. No matter what resolutions are used on desktop and in Crysis. My 2K monitor actually supports 75 Hz on lower resolutions and there's also additional useless 50 Hz mode in FullHD. The patch fixes 2 things here. First, that useless 50 Hz mode is not used anymore. Second, when desktop is set to lower resolution and 75 Hz, Crysis runs at 75 Hz as well if the in-game resolution allows it. This is probably the expected behavior. It would be nice if someone with 120/144/240 Hz monitor could test it too.

vlad54rus commented 3 years ago

Strange, but putting nops in 0x1C8F45 crashes my game, while 0x1C8345 works. Do we have the same files? My CryRenderD3D10.dll is 3 100 896 bytes, version 1.1.1.6156, CRC32 is E78B79EE,

ccomrade commented 3 years ago

4 nops at 0x1C8F45 are needed. See the commit.

ccomrade commented 3 years ago

The file seems to be the same. You can also do right click > Properties > Digital Signatures > Crytek GmbH > Details. If it says "This digital signature is OK.", then it's the original unmodified file.

vlad54rus commented 3 years ago

That's exactly what i did - 4 nops, it just crashes.

ccomrade commented 3 years ago

Are you sure you put those nops at the right place? The resulting absolute address should be 0x381C8F45.

vlad54rus commented 3 years ago

I was hex-editing the file itself at 0x1C8345 while actual runtime memory shows these bytes at 0x1C8F45. So it's different between file patching and memory patching.

ccomrade commented 3 years ago

You can also test the latest EXEs directly: c1-launcher-beta-build.zip

vlad54rus commented 3 years ago

Works well, thanks. Although i still don't understand why the offset is different at runtime.

ccomrade commented 3 years ago

When DLL/EXE file is loaded into memory, many things need to be done. For example, some sections of an executable file require different memory protection, which can only be set per page. Page size is usually 4096 bytes, so every section with size not equal to this number or its multiples must be extended. The .text section, where all the machine code is, usually begins at second memory page, which is offset 0x1000. No idea which offset is used in file, but it's probably different to save some disk space. This is one of the reasons why in-file and in-memory offsets can be different.

Patching files on disk is pretty dirty anyway. For example, it invalidates digital signatures. Simple tools like hex editors also usually don't properly update various checksums and offsets in section headers, which can lead to strange issues. Editing files in memory doesn't allow you to insert anything. You can only overwrite content. This makes it quite safe because it can only invalidate checksums, which are usually checked only once when the file is loaded into memory.

vlad54rus commented 3 years ago

Thanks for explanation. Any plans on porting these fixes to Crysis Warhead? And since Warhead EXE's can't be replaced - what about DLL injection?

ccomrade commented 3 years ago

You need the original Crysis Warhead EXE because CryAction and CryGame is in there. Having 2 EXEs loaded within one process is very very tricky. Although it's possible, I don't really like it. Experimental working code is in the warhead branch. Only 64-bit version is supported because 32-bit EXE is also partially encrypted by the SecuROM DRM, so you need to decrypt it first. Unfortunately, Crytek even didn't release Warhead SDK with its CryGame source code. Even though Warhead's CryGame is probably very similar to Wars one, which has SDK with almost complete CryGame source code just like the original Crysis, we still don't have CryAction. Crytek never released CryAction source code for any Crysis game. I basically gave up adding Warhead support to this launcher. It would be really nice, but it's not really possible. I have so many other things to do these days anyway.

vlad54rus commented 3 years ago

What about CrySystem and CryRender patches? I didn't mean c1-launcher specifically as patching can be done by other means. I would like to see refresh rate bug fixed in Crysis Warhead. I've found the offset for 32 bit DLL (0x170390) but have trouble finding 64 bit one.

ccomrade commented 3 years ago

Try to find the instruction. It's quite unique.

vlad54rus commented 2 years ago

I see that it's a single mov instruction (mov [r13+18h], rbx). The instruction is the same between Crysis and Crysis Wars, as well as the disassembly of the function whete it is located, but i couldn't find anything simular in Crysis Warhead.

ccomrade commented 2 years ago

It seems D3D10 renderer is somewhat different in Crysis Warhead. I couldn't find it as well.

vlad54rus commented 2 years ago

Finally found it! CryRenderD3D10.dll (64 bit 711) File offset 0x1B7FE4 > 31 C0 90 90 0x1B7FEC > 90 90 90 90

ccomrade commented 2 years ago

Nice

vlad54rus commented 2 years ago

Damn, i took the wrong 32 bit dll (687). The 711 one also has different coding, whole DXGI structures are copied with one streamed mov (rep movsd). Had to do a jump to an unused space above and do patch here. CryRenderD3D10.dll (32 bit 711) File offset 0x169339 > E9 B3 FE FF FF CC CC 0x1691F1 > 31 C0 89 46 08 F3 A5 5E 5F 5D 5B C3