📅 Published on: June 25, 2025

🧐 Subject: One finger death punch recovery!


Now would you look at that we finally have blog posts! I don’t really play games lately despite having a massive collection, but one youtube clip somehow reminded me of an amazing little game I used to play more or less all the time, One Finger Death Punch. It’s one of those games that doesn’t support steam cloud for whatever reason. When I saw the game is not installed on my main pc and last time I played was in 2019, I was hoping I could do something.

Not for anything but I already lost that save file once, and I damn sure didn’t want to start it all over again for a third time after close to 70 hours put in. Lowkey also motivated me to do a savegames backuper for (mainly) the games that steam doesn’t support, but that’s for another day, already got too much on my backburner already. In the pile of ideas you go, shoo shoo (╯°□°)╯.

Started reverse engineering the savegame, and lo and behold, the gods of data were on my side. The game using plain XML to store info. Jᴀᴄᴋᴘᴏᴛ!

For some context, the game has a story mode you can complete in 3 difficulties, student, master, and grandmaster. In the savefile they are marked as 0, 1, and 2. Each of the 3 stages has 255 levels, and for each level you have data for the score, the number of misses, and also whether or not the level was unlocked, beat, or done perfectly. The first 2 are integers, and the later ones are booleans.

Very conveniently, the XML structure was incredibly simple, for instance this would be the scoring section for master levels:

  <Level_Score_1>
    <int>11201</int>
    <int>8455</int>
    <int>0</int>
        ...
  <Level_Score_1/>

Funny finding, a score of 0 would mark a level as not played in game, even if in the savefile it was marked as beat.

Thanks to the steam achievements, I could see the latest unlocks I did, and from memory I could recall I was somewhere in the beginning of the grandmaster playthrough. Unlocking the difficulties was a breeze thanks to the friendly xml, I just had to change the Master/Grandmaster_Unlocked, to true, and same with the 3 different survival modes, which were marked as A, B, and C. If I recall, I’ll post a picture here of the No Luca No Survival, which is hilarious, you have hoardes on ninjas coming to you left and right, while there’s a cat taking 70% of your screen every few seconds, and you gotta show him away if you wanna see something.

The real challenge was figuring out how many points to add to each level, and this ended up way more maths than I expected.

After a whole bunch of tests, it turned out that the number of misses and the score were the main factors. If I finished a level with 10,000 points, and I had 2 misses, I would be awarded 10,000 mastery points. Table bellow shows the effect of misses on points.

No. of Misses Effect on points
0 +50
1-3 none
4-6 -25
7-9 -40
10+ -50

A first assumption was to think that I’d have more on points student rather than on master, since the later one was harder and I may have missed more often. But that turned out to be false, since while indeed I may have messed up more, the sheer quantity of enemies in the master levels was way higher. Between 50 and 250 enemies for brawl rounds in student, meanwhile it was between 90 and 305 for master. An enemy would yield an average of 92 points, which would mean an average of 13,800 for the first case and around 18,000 for the second one.

As a baseline, I was supposed to have had somewhere between 5 and 6 million mastery points. Using the numbers above, I ended up going with ~2.5M for the student levels, and ~3.4M for the master.

But how do you generate 255 random numbers that sum up to 2.5M, while having an average of 13,800, and an upper/lower bound? 🤔🤔🤔

Well, apparently you can draw random samples from a normal (Gaussian) distribution, clamp them between limits, and finally normalize the whole thing. Or all put together:

import numpy as np

def generate_level_scores(total_points=2500000, num_levels=255, mean=13800, std_dev=7000, lower_bound=5980, upper_bound=19700, seed=None):
    if seed is not None:
        np.random.seed(seed)
    
    # Generate scores using normal distribution
    raw_scores = np.random.normal(loc=mean, scale=std_dev, size=num_levels)
    
    # Clamp to upper/lower bounds
    clamped_scores = np.clip(raw_scores, lower_bound, upper_bound)

    # Normalize so the sum is exactly total_points
    scale = total_points / clamped_scores.sum()
    final_scores = np.round(clamped_scores * scale).astype(int)

    # Checking the output!
    print(f"Generated {len(final_scores)} scores with total: {final_scores.sum()}")
    return final_scores.tolist()

Now that we had the scores too, all that was left were the misses and perfect levels. For perfects it was quite easy, I knew I had around 150 perfect levels, so I just had to distribute 80ish to student and the rest to master. This was achieved by making a list with the indices of the boolean tags, randomly shuffling it, and then picking the first 80 random indices to turn to true.

        all_tags = parent.findall("boolean")
        indices = list(range(len(all_tags)))
        random.shuffle(indices)

        perfect_indices = set(indices[:count])
        for i, tag in enumerate(all_tags):
            tag.text = "true" if i in perfect_indices else "false"

The misses was a tiiiny bit more complex, since I wanted to have a “special” distribution. I wanted to assign a weight to each number of misses so its more or less probable based on config.

miss_weights = [(0, 0.7), (1, 0.2), (2, 0.1), (3, 0.05), (4, 0.01)]
             ...
    values, weights = zip(*miss_weights)
    total = sum(weights)
    norm_weights = [w / total for w in weights]

    parent = root.find(f".//{tag_name}")
    if parent is not None:
        for i_tag in parent.findall("int"):
            i_tag.text = str(random.choices(values, weights=norm_weights)[0])

All in all, this worked great, and I was back in the game in no time as if I didn’t lose anything. Was it overengineered? Sure, but I wanted it to be as close to reality as possible ☝🤓!

Thank you for reading 😎👉


🔖 Tags: one finger death punch, savegame, python