Lyall / FFXVIFix

A fix for Final Fantasy XVI that adds ultrawide/narrower support, uncaps framerate in cutscenes, lets you adjust gameplay FOV and much more.
MIT License
496 stars 10 forks source link

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

Open ancient-animal opened 3 weeks ago

ancient-animal commented 3 weeks ago

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 commented 2 weeks ago

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;
        });
    }
}