Skip to content

Instantly share code, notes, and snippets.

@jh0ker
Last active January 6, 2024 14:35
Show Gist options
  • Star 16 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save jh0ker/1b8dd16f113b7ddc7b90e617ac0851c4 to your computer and use it in GitHub Desktop.
Save jh0ker/1b8dd16f113b7ddc7b90e617ac0851c4 to your computer and use it in GitHub Desktop.
Max attributes and skills in SCUM game single player

README

This script manipulates the SCUM database for the single player mode to increase the level of all skills and attributes to max (or the value of your choice) for a given prisoner. This script does not work for multiplayer characters.

You can edit the configuration settings SET_ATTRIBUTES and SET_SKILLS at the start of the script below to change the target values for the skills and attributes. Default is maxed out.

Tested with SCUM Build 0.9.101.72873

How do I use it?

You need to have Python installed on your computer. The script was tested with Python 3.11 - older versions may work, newer versions should be fine.

Download Python from python.org and install it. I recommend you tick the option "Add to PATH" during the install if there is one.

Now, download the max_prisoner.py script file by right-clicking on the Raw button in its title bar and selecting "Save link as...". Save it somewhere you can easily find it.

Make sure you are not currently in a single player game (being in the main menu should be fine). Now, you should be able to run the script by simply double-clicking it in Windows. If this does not work, right click the file and select "Open with" -> "Python".

The script will automatically create a backup of your current DB and ask you which prisoner/character to work on. If everything works out correctly, it will look something like this:

image

Inspired by https://gist.github.com/quonic/279da005187df6e611b1b31859d91eeb

"""
This script manipulates the SCUM database for the single player mode to
increase the level of all skills and attributes to max (or the value of
your choice).
Edit the constants below to change the target values for the skills and
attributes. Default is maxed out.
Tested with Python 3.11 on August 5th, 2023 with SCUM Build 0.9.101.72873
"""
from dataclasses import dataclass
import datetime as dt
import os
from pathlib import Path
import shutil
import sqlite3
import struct
import traceback
from typing import Literal
#### Configuration ####
## Main attributes ##
SET_ATTRIBUTES = {
"BaseStrength": 8.0, # 1.0 to 8.0
"BaseConstitution": 5.0, # 1.0 to 5.0
"BaseDexterity": 5.0, # 1.0 to 5.0
"BaseIntelligence": 5.0, # 1.0 to 5.0
}
## Skills ##
"""
You can remove skills from the list below and they will not be changed.
If a new skill is added to the game, you can add it to the list below.
The first number in each line is the skill level (0 - 3)
The second number is the skill experience (0 - 10000000)
"""
SET_SKILLS = {
"BoxingSkill": (3, 10000000),
"AwarenessSkill": (3, 10000000),
"RiflesSkill": (3, 10000000),
"SnipingSkill": (3, 10000000),
"CamouflageSkill": (3, 10000000),
"SurvivalSkill": (3, 10000000),
"MeleeWeaponsSkill": (3, 10000000),
"HandgunSkill": (3, 10000000),
"RunningSkill": (3, 10000000),
"EnduranceSkill": (3, 10000000),
"TacticsSkill": (3, 10000000),
"CookingSkill": (3, 10000000),
"ThieverySkill": (3, 10000000),
"ArcherySkill": (3, 10000000),
"DrivingSkill": (3, 10000000),
"EngineeringSkill": (3, 10000000),
"DemolitionSkill": (3, 10000000),
"MedicalSkill": (3, 10000000),
"MotorcycleSkill": (3, 10000000),
"StealthSkill": (3, 10000000),
"AviationSkill": (3, 10000000),
"ResistanceSkill": (3, 10000000),
"FarmingSkill": (3, 10000000),
}
# Other constants
USER = os.getlogin()
DB_PATH = Path(f"C:/Users/{USER}/AppData/Local/SCUM/Saved/SaveFiles/SCUM.db")
BODY_SIM_KEY_PADDING = 5
BODY_SIM_VALUE_PADDING = 10
@dataclass
class PropertyType:
"""Just a small class to define property types as they occur in the body simulation blob."""
name: bytes
width: int # in bytes
# Used for converting with Python types
struct_type: Literal["<d", "<f", "<?"]
DoubleProperty = PropertyType(name=b"DoubleProperty", width=8, struct_type="<d")
FloatProperty = PropertyType(name=b"FloatProperty", width=4, struct_type="<f")
BoolProperty = PropertyType(name=b"BoolProperty", width=1, struct_type="<?")
def load_prisoner(con: sqlite3.Connection, id: int):
"""Load prisoner from database."""
cur = con.execute("SELECT * FROM prisoner WHERE id = ?", (id,))
result = {desc[0]: val for desc, val in zip(cur.description, cur.fetchone())}
return result
def save_prisoner(con: sqlite3.Connection, prisoner: dict):
"""Updates prisoner in database. Currently only sets body_simulation."""
return con.execute(
"UPDATE prisoner SET body_simulation = ? WHERE id = ?",
(prisoner["body_simulation"], prisoner["id"]),
)
def update_body_sim(body_sim: bytearray, key: bytes, value: float, property_type: PropertyType):
# Find the key in the body simulation blob
key_offset = body_sim.index(key)
# Make sure we are using the correct property type
assert (
body_sim[
key_offset
+ len(key)
+ BODY_SIM_KEY_PADDING : key_offset
+ len(key)
+ BODY_SIM_KEY_PADDING
+ len(property_type.name)
]
== property_type.name
)
# Calculate offset of actual value
value_offset = (
key_offset
+ len(key)
+ BODY_SIM_KEY_PADDING
+ len(property_type.name)
+ BODY_SIM_VALUE_PADDING
)
# Convert value to bytes
value_bytes = struct.pack(property_type.struct_type, value)
# Update value in body sim blob
body_sim[value_offset : value_offset + property_type.width] = value_bytes
def update_skills(con: sqlite3.Connection, prisoner: dict):
"""Sets all skills to max level in the database."""
for (name,) in con.execute(
"SELECT name FROM prisoner_skill WHERE prisoner_id = ?", (prisoner["id"],)
):
if name not in SET_SKILLS:
continue
new_level, new_experience = SET_SKILLS[name]
# Finally, update the XML and other fields in the database
con.execute(
"UPDATE prisoner_skill SET level = ?, experience = ? WHERE prisoner_id = ? AND name = ?",
(new_level, new_experience, prisoner["id"], name),
)
def choose_prisoner(con: sqlite3.Connection):
"""Choose prisoner to update."""
cur = con.execute(
"SELECT prisoner.id, user_profile.name FROM prisoner LEFT JOIN user_profile ON prisoner.user_profile_id = user_profile.id WHERE user_profile.authority_name is ?",
(None,),
)
print("\nFound prisoners in local single player:\n")
for id, name in cur:
print(f'"{name}" with ID {id}')
return int(input("\nEnter prisoner ID: "))
def main():
print("Backing up database... ")
filename_safe_iso = dt.datetime.now().isoformat().replace(":", "-")
backup_path = DB_PATH.with_name(f"SCUM-bak-{filename_safe_iso}.db")
shutil.copy(DB_PATH, backup_path)
print(f"Backed up to: {backup_path}")
print("\nConnecting to database...")
con = sqlite3.connect(DB_PATH)
# Choose prisoner interactively
prisoner_id = choose_prisoner(con)
print(f"Loading prisoner with ID {prisoner_id}...")
prisoner = load_prisoner(con, prisoner_id)
print("\nUpdating attributes... ", end="")
body_sim = bytearray(prisoner["body_simulation"])
for attribute, value in SET_ATTRIBUTES.items():
update_body_sim(
body_sim,
attribute.encode("ascii"),
value,
DoubleProperty,
)
prisoner["body_simulation"] = bytes(body_sim)
save_prisoner(con, prisoner)
print("Success!")
print("Updating skills... ", end="")
update_skills(con, prisoner)
print("Success!")
con.commit()
input("\nAll done! Press enter to exit.")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nExiting...")
except Exception:
print("\n\nSomething went wrong...\n\n")
traceback.print_exc()
input("\n\nPress enter to exit.")
"""
This script manipulates the SCUM database for the single player mode to
repair placed items by the chosen player.
Tested with Python 3.10 on September 15, 2022 with SCUM Build 0.7.162.51364
"""
import datetime as dt
import os
import shutil
import sqlite3
import traceback
import xml.etree.ElementTree as ET
from pathlib import Path
from typing import Tuple
#### Configuration ####
# No configuration :)
# Other constants
USER = os.getlogin()
DB_PATH = Path(f"C:/Users/{USER}/AppData/Local/SCUM/Saved/SaveFiles/SCUM.db")
def repair_base_element(con: sqlite3.Connection, prisoner_id: int):
"""Reset health of base elements like walls, foundations and some other stuff."""
con.execute(
"UPDATE base_element SET element_health = ? WHERE creator_prisoner_id = ?",
(1.0, prisoner_id),
)
def repair_virtualized_item(con: sqlite3.Connection, user_profile_id: int):
"""Reset health of other buildings and dropped items."""
for item_id, xml in con.execute(
"SELECT id, item_xml FROM virtualized_item WHERE user_profile_id = ?",
(user_profile_id,),
):
# Parse XML, modify health and max health, and convert back to XML
string_terminator = "\x0a\x00"
root = ET.fromstring(xml.rstrip(string_terminator))
root.set("_health", "1")
root.set("_maxHealth", "1")
new_xml = ET.tostring(root, encoding="unicode") + string_terminator
# Finally, update the XML and other fields in the database
con.execute(
"UPDATE virtualized_item SET item_xml = ? WHERE id = ?",
(new_xml, item_id),
)
def choose_prisoner(con: sqlite3.Connection) -> Tuple[int, int]:
"""Choose prisoner to update. Returns a tuple (prisoner_id, user_profile_id)."""
cur = con.execute(
"SELECT prisoner.id, user_profile.name, user_profile.id FROM prisoner LEFT JOIN user_profile ON prisoner.user_profile_id = user_profile.id WHERE user_profile.authority_name is ?",
(None,),
)
prisoners = {
prisoner_id: {"name": name, "user_profile_id": user_profile_id}
for (prisoner_id, name, user_profile_id) in cur
}
print("\nFound prisoners in local single player:\n")
for prisoner_id, data in prisoners.items():
print(f'"{data["name"]}" with ID {prisoner_id}')
chosen_prisoner_id = int(input("\nEnter prisoner ID: "))
if chosen_prisoner_id not in prisoners:
raise ValueError(f"Prisoner ID {chosen_prisoner_id} not found!")
return (chosen_prisoner_id, prisoners[chosen_prisoner_id]["user_profile_id"])
def main():
print("Backing up database... ")
filename_safe_iso = dt.datetime.now().isoformat().replace(":", "-")
backup_path = DB_PATH.with_name(f"SCUM-bak-{filename_safe_iso}.db")
shutil.copy(DB_PATH, backup_path)
print(f"Backed up to: {backup_path}")
print("\nConnecting to database...")
con = sqlite3.connect(DB_PATH)
# Choose prisoner interactively
prisoner_id, user_profile_id = choose_prisoner(con)
print('Repairing items in table "base_element"... ', end="")
repair_base_element(con, prisoner_id)
print("Success!")
print('Repairing items in table "virtualized_item"... ', end="")
repair_virtualized_item(con, user_profile_id)
print("Success!")
con.commit()
input("\nAll done! Press enter to exit.")
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nExiting...")
except Exception:
print("\n\nSomething went wrong...\n\n")
traceback.print_exc()
input("\n\nPress enter to exit.")
@bakedbeangaming
Copy link

thank you ill have a go at it again and will pm you in discord

@bakedbeangaming
Copy link

@bakedbeangaming Hmm... if it did run successfully, that's strange. Have you tried shutting off your game completely before running the script? Maybe the changes got overwritten by something in the game. Did you make any modifications to the script? If yes, try re-downloading the original. If it still doesn't work, maybe you could send me your save game and I'll take a look if there's anything different with that. You can send it to me on Discord if you want, my username there is the same as here, jh0ker

hi iv sent you a friend request on discord as it would not let me inbox you or send save file

@jh0ker
Copy link
Author

jh0ker commented Dec 23, 2023

Hey @bakedbeangaming I don't see any friend requests on my end, not sure why

@bakedbeangaming
Copy link

@jh0ker just like to say a big thank you to jh0ker for all your time in helping me much love and again thank you all sorted

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment