FFXVIFix icon indicating copy to clipboard operation
FFXVIFix copied to clipboard

[Feature Request for problem introduced by patch 1.02] Separate Level-of-Detail Multiplier for cutscenes. Please read this!

Open ancient-animal opened this issue 1 year ago • 7 comments

The official patch 1.02 introduced a problem that previous version didn't have: the faces of characters are ruined. Now, I MUST use this "FFXVIFix" tool, and set the Level of Detail multiplier to at least 2.5. In previous versions, even without using this FFXVIFix tool, the faces were fine.

I quickly set all of the game's graphical settings to the highest levels, to determine whether one would improve the faces, but they didn't cause a change, so I must use the LOD Multiplier. If this problem doesn't affect other people, that means the game detects the quality of the graphics card, and determines that mine doesn't deserve the high-quality faces that previous versions already had.

While I have enough CPU performance to leave the multiplier at 2.5, I don't have enough GPU performance. Before version 1.02, I could leave the multiplier as 1.0, or just not use this "FFXVIFix" tool, and the faces were fine. Increasing the multiplier reduces my frame-rate, even though versions before 1.02 had a perfect frame-rate AND perfect faces.

To compensate, could you add a second Level-of-Detail multiplier? While I could control the character, LODMultiplier1 will be used, which I will set to 1.0. While your script detects that control of the character is not allowed, the game will use LODMultiplier2, which I will set to at least 2.5.

That will allow me to have a high frame-rate while I could control the character, and still have better faces during cutscenes. I do not need cutscenes to reach a frame-rate higher than 30, and yes, I have enough graphical power to reach 30 FPS in cutscenes while the LODMultiplier is set to 2.5.

You would be the Master of the Earth if you would add that!

ancient-animal avatar Nov 10 '24 15:11 ancient-animal

In case this is enough information to convince someone to add this feature, I'll mention this information about the part of the memory that shows the status of the ability to control the character (it equals "1" when control is allowed, and "0" when it's not):

ffxvi.exe+12322020
48 8D 64 24 D8
lea rsp,[rsp-28]

And this pattern seems stable:

uint8_t* ControlStatusScanResult = Memory::PatternScan(baseModule, "01 00 00 00 00 00 00 00 00 01 01 00 00 00 00 10 00 A7 02 00 00 00 ?? ?? ?? ?? 02 00 00 00 ?? ?? ?? ?? 02 00 00 00 ?? ?? ?? ?? 02 00 00 00 00 10 00 A7 02 00 00 00 ?? ?? ?? A6 02 00 00 00 ?? ?? 00 00 00 00 00 00 00 10 00 A7 02 00 00 00 ?? ?? ?? ?? 02 00 00 00");

If that pattern exists, that means I can control the character. I don't know whether that pattern has enough precision or is too generic and will appear in the memory more than once because other parts of the game have the same pattern.

Maybe it's not part of the "baseModule", but I don't know how else to check. These four addresses show it in the "Cheat Engine" program:

2A7019433
2A7019434
2A707FBCA
2A707FE4A

I've tried to add that plan to the source code, but it caused the game to be too slow. The booting screen that shows the "Square Enix" logo was too slow. I may be too stupid to add it. Maybe this plan cannot be done. I've assumed it's possible because the variable called "isMoviePlaying" seems to be updated often. I placed a sample line there and it printed a message in the log file often, so I've thought checking whether the character could be controlled could happen often, too. Imagine it checking once every 2500 milliseconds.

I won't mention this plan again because I don't want to bother people, but in case the creator believes this can be done, and wants to help, I will share a general idea of what parts would be added:

Setup:

float fLODMulti2 = 1.00f;

inipp::get_value(ini.sections["Level of Detail"], "Multiplier2", fLODMulti2);

if ((float)fLODMulti2 < 0.10f || (float)fLODMulti2 > 10.00f) {
	fLODMulti2 = std::clamp((float)fLODMulti2, 0.10f, 10.00f);
	spdlog::warn("Config Parse: fLODMulti2 value invalid, clamped to {}", fLODMulti2);
}
spdlog::info("Config Parse: fLODMulti2: {}", fLODMulti2);

Most important part:

// Can I control the character?
uint8_t* ControlStatusScanResult = Memory::PatternScan(baseModule, "01 00 00 00 00 00 00 00 00 01 01 00 00 00 00 10 00 A7 02 00 00 00 ?? ?? ?? ?? 02 00 00 00 ?? ?? ?? ?? 02 00 00 00 ?? ?? ?? ?? 02 00 00 00 00 10 00 A7 02 00 00 00 ?? ?? ?? A6 02 00 00 00 ?? ?? 00 00 00 00 00 00 00 10 00 A7 02 00 00 00 ?? ?? ?? ?? 02 00 00 00");

uint8_t* LevelOfDetailScanResult = Memory::PatternScan(baseModule, "44 ?? ?? ?? ?? ?? ?? 75 ?? C5 ?? ?? ?? ?? ?? ?? ?? C5 ?? ?? E8 ?? ?? ?? ??");

if (ControlStatusScanResult)
{
	if (LevelOfDetailScanResult) {
		static SafetyHookMid LevelOfDetailMidHook{};
		LevelOfDetailMidHook = safetyhook::create_mid(LevelOfDetailScanResult,
		[](SafetyHookContext& ctx) {
			ctx.xmm6.f32[0] *= fLODMulti;
		});
	}
}

else
{
	if (LevelOfDetailScanResult) {
		static SafetyHookMid LevelOfDetailMidHook{};
		LevelOfDetailMidHook = safetyhook::create_mid(LevelOfDetailScanResult,
		[](SafetyHookContext& ctx) {
			ctx.xmm6.f32[0] *= fLODMulti2;
		});
	}
}

ancient-animal avatar Nov 18 '24 21:11 ancient-animal

Important Update: I've created a LUA script in Cheat Engine to automatically set the Level of Detail to "1.0" when I can control the character and "2.5" when I cannot. A month has passed and the script still is wonderful, but I must mention a problem I've noticed:

Today, I activated the "Disable fullscreen optimizations" box of the "Properties" menu of the "ffxvi.exe" file, just for curiosity, and I noticed that the four addresses mentioned in my last message didn't work. I've tried the "Exclusive Fullscreen" and "Windowed" modes, and the addresses didn't work. Then, I removed the checkmark from the "Disable fullscreen optimizations" box, and the addresses worked again. They correctly showed either "1" or "0", instead of gibberish. I've alternated the checkbox on an off multiple times and the same results happened.

I've decided to mentioned this in case someone else has tried those addresses and thought they were broken.

ancient-animal avatar Dec 08 '24 22:12 ancient-animal

More Information:

While the "Disable fullscreen optimizations" box is checked, these are the correct addresses:

2D7019433
2D7019434
2D707FBCA
2D707FE4A

This is the correct pattern while that box is active:

uint8_t* ControlStatusScanResult = Memory::PatternScan(baseModule, "01 00 00 00 00 00 00 00 00 01 01 00 00 00 00 10 00 D7 02 00 00 00 ?? ?? ?? ?? 02 00 00 00 ?? ?? ?? ?? 02 00 00 00 ?? ?? ?? ?? 02 00 00 00 00 10 00 D7 02 00 00 00 ?? ?? ?? D6 02 00 00 00 ?? ?? 00 00 00 00 00 00 00 10 00 D7 02 00 00 00 ?? ?? ?? ?? 02 00 00 00");

And THIS is the pattern that will function regardless of the status of that checkbox:

uint8_t* ControlStatusScanResult = Memory::PatternScan(baseModule, "01 00 00 00 00 00 00 00 00 01 01 00 00 00 00 10 00 ?? 02 00 00 00 ?? ?? ?? ?? 02 00 00 00 ?? ?? ?? ?? 02 00 00 00 ?? ?? ?? ?? 02 00 00 00 00 10 00 ?? 02 00 00 00 ?? ?? ?? ?? 02 00 00 00 ?? ?? 00 00 00 00 00 00 00 10 00 ?? 02 00 00 00 ?? ?? ?? ?? 02 00 00 00");

ancient-animal avatar Dec 09 '24 04:12 ancient-animal

In case this may help someone, I will describe my experience with version 1.03 of this game:

When I waited until the main menu has been completely loaded, then searched for this array of bytes with the "Cheat Engine" program, it correctly found the location of the variable that shows whether Clive can be controlled:

?? 00 00 00 00 00 00 00 00 01 ?? 00 00 00 00 10 00 ?? ?? ?? 00 00 ?? ?? ?? ?? ?? ?? 00 00 ?? ?? ?? ?? ?? ?? 00 00 ?? ?? ?? ?? ?? ?? 00 00 00 10 00 ?? ?? ?? 00 00 ?? ?? E2 ?? ?? ?? 00 00 ?? 00 00 00 00 00 00 00 00 10 00 ?? ?? ?? 00 00 ?? ??

The first spot will be set to "01" while Clive can be controlled. If he can't be controlled, because a cutscene or conversation or menu is active, the first spot will be set to "00". I've tried many times, while loading various areas and cutscenes, and it continued to work correctly every time.

In a previous message, I mentioned the current status of the "Disable fullscreen optimizations" box, but it doesn't affect this new method.

(Searching for that array of bytes before the main menu has been fully loaded caused the wrong variable to be found, but when I searched while the main menu was completely loaded, it continued to function reliably, even after I returned to the main menu and loaded a file again.)

ancient-animal avatar Mar 05 '25 02:03 ancient-animal

In your effort to add this to the source, was it too slow because you were doing a pattern scan every frame? If so, it might be worth checking if the address changes throughout gameplay, and if it does, figure out how to locate it dynamically so that you only need to do the scan once.

Drahsid avatar Mar 20 '25 08:03 Drahsid

It might have been done every frame. I didn't know how to limit it to once every 2500 milliseconds in the source of this "FFXVIFix" tool. I tried.

But while using "Cheat Engine", I played a full playthrough with version 1.02 and the plan worked perfectly throughout. The four possible addresses didn't change. When I could control the character, the Field-of-View was set to "1.0". When I couldn't control the character, the Field-of-View was set to a higher value. Each of the four possible addresses that determined whether I could control Clive were stable during the entire playthrough.

With version 1.03, the addresses are not stable, so, instead of searching for the address, I wait until the main menu has been fully loaded (the completion of the logo screens during startup), then I use the "Pattern Scan" feature of Cheat Engine to find the pattern I mentioned in my last post, and it found the correct variable attached to that pattern every time. I haven't done a full playthrough with version 1.03, but the pattern has been correct after many restarts of my computer.

I need to set up Cheat Engine during every startup of the game (with my automatic script), but then I don't need to touch it. Once every 2500 milliseconds, it checks the value of the address to determine whether I could control Clive, and sets the Field-of-View distance accordingly. The only difference is, the four possible address were always the same with version 1.02, but, with version 1.03, it's not the same, but a Pattern Scan finds the correct one every time.

To be clear, the correct variable seems to NOT be a part of "ffxvi.exe" (the same is true for versions 1.02 and 1.03). I don't know what it's a part of, so, with Cheat Engine, instead of searching the memory of "ffxvi.exe", I set the Memory Scan option to "All". Even though version 1.03 requires a memory scan, the result still will be found within a few seconds.

My personal goal has been achieved, so this discussion is intended just as an idea about considering adding this feature to this "FFXVIFix" tool.

ancient-animal avatar Mar 20 '25 12:03 ancient-animal

To avoid a manual memory scan every time, I've narrowed the search to these seven seemingly-reliable pointers:

"ffxvi.exe"+02743A80, with these offsets:  10,  90,  40,  18,  4A
"ffxvi.exe"+02743A80, with these offsets:  10,  F0,  98,  D8,  4A
"ffxvi.exe"+02743A80, with these offsets:  10,  F0,  A8,  78,  4A
"ffxvi.exe"+02743B10, with these offsets:  C0,  B0,  98,  D8,  4A
"ffxvi.exe"+02743B10, with these offsets:  C0,  B0,  A8,  78,  4A
"ffxvi.exe"+02743B10, with these offsets:  C0, 150,  40,  18,  4A
"ffxvi.exe"+02743B10, with these offsets:  C0, 158,  80,  88,  4A

I've added the Cheat Engine file to this message, so other people could use it without manually entering the offsets. (I needed to rename its extension to ".txt" to add it here. Rename the extension to ".CT" to use it, or open it with a normal text editor to view it.)

Final Fantasy XVI - 6 - 1 - 3 - 2.txt

Each one is a "Byte" that equals "1" when Clive can be controlled, and "0" when he cannot be (because of a cutscene, or conversation, or menu).

I will update this message if I've noticed one being unreliable.

ancient-animal avatar Apr 01 '25 23:04 ancient-animal