The Linux True Final Millennium Tower

Or how to loose time trying to save some

The True Final Millennium Tower is the eponymous achievement for the last extra dungeon in the game Yakuza: Like a Dragon (龍が如く7 光と闇の行方). 

Long story short, this last dungeon has max stats enemies and in order to overcome it, one must spend hours and hours levelling up each character jobs to earn permanent stats and eventually mini-games to buy stats increase items. 

Easy mode (SAM) 

The easiest way to unlock that achievement would be to just pretend you earned it. Just go brag to the world you achieved the True Final Millennium Tower. But what if a suspicious mind verifies your steam profile ? 

Here comes your best buddy SAM, short for Steam Achievement Manager.

# Install and launch SAM Rewritten available via the AUR
pamac build samrewritten-git
samrewritten 1235140 -p

You’ll access a nice GUI that lets you manage achievements for your Steam game library, and manually unlock the one we’re interested in.

Yakuza 7 Steam Achievement Manager achievements page

Let’s click that Locked button and we’re done.
Or maybe not, what about that sense of completion.. Let’s try something else.

Time Saving Mode (Trainer) 

I wasn’t quite ready to level up legitimately either, so I went to look for a trainer that would save me quite some time. A trainer is a small graphical program that basically looks for your game/app and offers you a few switches or keyboard shortcut to overwrites specific values in the memory for different effects. This is basically a non-official cheating utility.

Disclaimers

  • Cheating on a multiplayer game could get you banned.
  • Along with other warez, trainers are usually closed source binaries so consider those suspicious by default, it could embed malicious code.
  • Injecting non validated values into your game variables could corrupt your savefile(s).

I settled on FLiNG’s ‘Yakuza Like a Dragon v1.0 Plus 30 Trainer’. I took the risk using it because:

  1. It targets windows, so I might be safe on Linux, it’s unlikely the dev would have gone the extra step of embedding a linux malicious or shellcode, but that’s a bad assumption, that’s the kind of reasoning puts your computer into a botnet network, don’t do that.
  2. Based on the comments on various sources, trainer works and the infamous FLiNG actually answers users comments and seemed committed and helpful, again this does not certify the binary is safe, just brings a bit more trust.

Now, how the fuck do I use a Windows trainer for a Windows game on a Linux system ? Well, provided you’re using Proton via Steam, the protontricks utility does it for you actually.

# Install protontricks available via the AUR
pamac build protontricks

# Launch Yakuza 7 via Steam
steam steam://rungameid/1235140

# Launch Yakuza 7 trainer via Protontricks
protontricks -c "wine /home/ao/Downloads/Yakuza\ Like\ a\ Dragon\ v1.0\ Plus\ 30\ Trainer.exe" 1235140

What it does is:

  • As you entered the steam AppID, it gets the game WINE_PREFIX which usually lies in your 'SteamLibrary/steamapps/compatdata/<AppID>/pfx/' path.
  • Runs the command provided via the -c argument, which basically launches the trainer (with full path provided) via wine in the prefix specified above.

Yakuza 7 FLiNG trainer

It works ! But unfortunately upon clicking any switch..

Yakuza 7 FLiNG trainer error

At this point, I had already spent hours trying to successfully launch the trainer via wine, in the correct WINE_PREFIX so it launches correctly and find the game process.
There’s probably not much to do about this error anyway, by default any wine launched application is launched in administrator mode by default so that’s not an issue, maybe a linux file permission issue.
But it certainly is because the trainer is a lightweight, yet complex piece of engineering specifically hard-coded to target an early version of the game on the Windows platform.
I wasn’t willing to troubleshoot this any further.

True Final Mode (Game Conqueror) 

Game Conqueror is a GUI for scanmem utility, that’s the closest thing as CheatEngine we got for Linux. 

# Install and launch gameconqueror available in the official manjaro repo
sudo pacman -Sy gameconqueror
gameconqueror &

First you need to have your game launched, ’cause we can’t do anything until the memory is populated from the runtime. Then you gotta identify the ProcessID of your game. Because wine/proton launches a bunch of processes, the easiest is to find the process using either the most CPU or memory in your system monitor. Once you get the PID, enter it in gameconqueror.

Yakuza 7 Game Conqueror

Now, within the GUI, start by setting a specific value you’ve identified in the game and click Search. The program will look through the memory and note down every piece of memory with that exact value. Go back to the game, execute an action that will change the previous value (but still identified as the same variable, for example player’s HP), enter it in gameconqueror and search again. The program will now go through all the previously noted down entries and keep those who changed to the new value.
Processing by elimination you should be left with a fistful of addresses, try changing their value and check how it behaves in the game. Try using different datatypes if you can’t find your value’s address, or it behaves weirdly in the game upon changing those, for example here the Max HP/MP are int64, using uint64 to change those won’t work and Current MP is int32 so using an int64 will overflow and corrupt an other variable.
Finally it supports evaluating expression in case you need more complex pattern to recognize.

Yakuza 7 Ichiban max stats

Of course, as the game version changes, the binary is likely to have been modified and all the addresses you found will probably be offset in the new binary so you might have to search for those again. 

Unfortunately, loading the addresses back after I relaunched the game was wrong and I lost all fields, offset was different, but I could reconstruct the addresses with their previous respective offsets.
But here’s the addresses I’ve initially came up with for Yakuza 7 (AppID: 1235140) version (BuildID: 7362542): 

{"cheat_list": 
    [
        ["=", false, "Job XP", 140735646810020, "int32", "100000000", true], 
        ["=", false, "XP", 140735646810016, "int32", "10000", true], 
        ["=", false, "Yens", 140735477708648, "int64", "10017540", true], 
        ["=", false, "Ichiban Current HP", 140735477684656, "int64", "1945", true], 
        ["=", false, "Ichiban Max HP", 140735477684664, "int64", "2095", true], 
        ["=", false, "Ichiban Current MP", 140735477684680, "int32", "263", true], 
        ["=", false, "Ichiban Max MP", 140735477684684, "int64", "1263", true], 
        ["=", false, "Nanba Current HP", 140735477685856, "int64", "1620", true], 
        ["=", false, "Nanba Max HP", 140735477685864, "int64", "1620", true], 
        ["=", false, "Nanba Current MP", 140735477685880, "int32", "1395", true], 
        ["=", false, "Nanba Max MP", 140735477685884, "int64", "1425", true], 
        ["=", false, "Adachi Current HP", 140735477687056, "int64", "2150", true], 
        ["=", false, "Adachi Max HP", 140735477687064, "int64", "2150", true], 
        ["=", false, "Adachi Current MP", 140735477687080, "int32", "1072", true], 
        ["=", false, "Adachi Max MP", 140735477687084, "int64", "1102", true], 
        ["=", false, "Saeko Current HP", 140735477688256, "int64", "1650", true], 
        ["=", false, "Saeko Max HP", 140735477688264, "int64", "1700", true], 
        ["=", false, "Saeko Current MP", 140735477688280, "int32", "1269", true], 
        ["=", false, "Saeko Max MP", 140735477688284, "int64", "1359", true], 
        ["=", false, "Eri Current HP", 140735477691856, "int64", "1660", true], 
        ["=", false, "Eri Max HP", 140735477691864, "int64", "1660", true], 
        ["=", false, "Eri Current MP", 140735477691880, "int32", "859", true], 
        ["=", false, "Eri Max MP", 140735477691884, "int64", "1090", true], 
        ["=", false, "Joon-gi Han Current HP", 140735477690656, "int64", "1630", true], 
        ["=", false, "Joon-gi Han Max HP", 140735477690664, "int64", "1880", true], 
        ["=", false, "Joon-gi Han Current MP", 140735477690680, "int32", "1168", true], 
        ["=", false, "Joon-gi Han Max MP", 140735477690684, "int64", "1168", true], 
        ["=", false, "Zhao Current HP", 140735477689456, "int64", "1790", true], 
        ["=", false, "Zhao Max HP", 140735477689464, "int64", "1940", true], 
        ["=", false, "Zhao Current MP", 140735477689480, "int32", "1314", true], 
        ["=", false, "Zhao Max MP", 140735477689484, "int64", "1314", true]
    ]
}

Interestingly enough, current/max HP/MP addresses are close to each other and they appear to have the same offset from a character to the other. Additionally, each character stats are offset by 0x4B0 (1200 bytes).

#define FIRST_CHARACTER_CURRENT_HP_ADDRESS 0x7FFF882799B0
unsigned char* address_entry_point = FIRST_CHARACTER_CURRENT_HP_ADDRESS;

struct CharacterStatStruct {
    int64* hp_current; 
    int64* hp_max;
    int32* mp_current;
    int64* mp_max;
} CharacterStat;

CharacterStat Ichiban;
Ichiban.hp_current = <int64*> (address_entry_point);
Ichiban.hp_max     = <int64*> (address_entry_point + 0x8);
Ichiban.mp_current = <int32*> (address_entry_point + 0x18);
Ichiban.mp_max     = <int64*> (address_entry_point + 0x1C);

CharacterStat Nanba;
Nanba.hp_current = <int64*> (address_entry_point + 0x4B0);
Nanba.hp_max     = <int64*> (address_entry_point + 0x4B0 + 0x8);
Nanba.mp_current = <int32*> (address_entry_point + 0x4B0 + 0x18);
Nanba.mp_max     = <int64*> (address_entry_point + 0x4B0 + 0x1C);

CharacterStat Adachi;
Adachi.hp_current = <int64*> (address_entry_point + 2 * 0x4B0);
Adachi.hp_max     = <int64*> (address_entry_point + 2 * 0x4B0 + 0x8);
Adachi.mp_current = <int32*> (address_entry_point + 2 * 0x4B0 + 0x18);
Adachi.mp_max     = <int64*> (address_entry_point + 2 * 0x4B0 + 0x1C);

// And so on..

This hints that the player stats might be stored in a struct or class and one could probably deduce or stats or even character status such as alterations just by watching at the neighbouring addresses within the memory debugger live. 

Conclusion

I initially tried the True Final Millennium Tower dungeon after maxing out character and job level after farming the Kamurocho sewer’s Invested Vagabond with XP boost items.
I . got . one-shot .. on the reception hall first trash mob. Enemies have on average up to several dozen thousands of HP and deal single hits up to above a thousand HP. Tough shit.

So here I’ve gone with Hero Ichiban, Fortune Teller Nanba, Bodyguard Adachi and Idol Saeko with all jobs level maxed and a bunch of top tier weapons and gear and of course our gameconqueror runtime memory changes for all character max HP/MP set to 9999. This makes it easy and enjoyable but you could steal loose if not paying much attention.

Additional tips:

  • Stack up on all recovery items and Miracle Kimchi.
  • Fully heal all your party between each battles and have a second idol outside of party as a free MP pool.
  • Skip facultative battles, go straight to the elevator (misses on fine loot but what’s the point at this point ?)
  • Spam Saeko’s Lovedrunk Typhoon and Nanba’s Malodorous Stench to lower moderately both attack and defence of all enemies.
  • Ichiban’s Fearless Command and Nanba’s Auric Insight raises all allies attack.
  • Use Ichiban’s Peerless Resolve to withstand a hit which would have been fatal (useful for last boss). You can also find skills among your current party to boost Ichiban’s Evasion and lower enemies’ Accuracy for that matter.
  • Saeko’s Evil Begone cures all allies mental ailments, her Miraculous Voice cures all allies physical ailments and her Magical Concert fully heals everyone.
  • Spam Ichiban’s Essence of Orbital Laser to deal massive amount of damage to all enemies and can leave them paralysed.
  • Try dealing elemental critical damages (find weaknesses in the sujidex or the game wiki page about the ennemies or true final millennium tower). Kiryu and some other bosses hate ice.
  • Miracle Kimchi, which can be bought in Ijincho, Korea Town, is an item which boosts a character’s turns, so they attack more often, use on Ichiban all the time to spam his Essence of Orbital Laser even more often.

Yakuza 7 Amon Encounter

Yakuza 7 Amon Taunt

Yakuza 7 True Final Millennium Tower Achievement

Yakuza 7 True Hero

That should be enough to earn you over ¥120 000 000 (1億2000万 円), some fine loot and the Legendary Hero costume.

Yakuza 7 Legendary Hero Costume