import argparse
import shutil
import os
always_on = set()
mods = {
'positive_tk': [{ # We change the TK points to +2, and change tank enemy tank kill to -2
'file': 'ingame.lvl',
'offset': 0x000000b9,
'orig': [0x00, 0xc0],
'mod': [0x40, 0x40]
}, {
'file': 'ingame.lvl',
'offset': 0x000000be,
'orig': [0x40, 0x40],
'mod': [0x00, 0xc0]
}, {
'file': 'ingame.lvl',
'offset': 0x0000013b,
'orig': [0x89],
'mod': [0x09]
}, {
'file': 'ingame.lvl',
'offset': 0x00000143,
'orig': [0xc9],
'mod': [0x89]
}, {
'file': 'ingame.lvl',
'offset': 0x0000014b,
'orig': [0x09],
'mod': [0xc9]
}],
'droid_invincible': [{
'file': 'ingame.lvl',
'offset': 0x002e7614, # Health droid
'orig': [
0x50, 0x52, 0x4F, 0x50, 0x08, 0x00, 0x00, 0x00, # These bytes correspond to the MaxHealth=200 part of the ODF
0x1B, 0x1F, 0x97, 0x19, 0x32, 0x30, 0x30, 0x00
],
'mod': [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # 0ing them gives it no health, and therefore makes it invincible
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
}, {
'file': 'ingame.lvl',
'offset': 0x002a9644, # Ammo droid built
'orig': [
0x50, 0x52, 0x4F, 0x50, 0x07, 0x00, 0x00, 0x00, # These bytes correspond to the MaxHealth=0 part of the ODF
0x1B, 0x1F, 0x97, 0x19, 0x38, 0x30, 0x00, 0x00
],
'mod': [
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, # 0ing them gives it no health, and therefore makes it invincible
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
]
}],
'droid_fast_heal': [{
'file': 'ingame.lvl',
'offset': 0x002e7630, # speed
'orig': '1.0',
'mod': '0.1'
}, {
'file': 'ingame.lvl',
'offset': 0x002e7640, # heal power
'orig': '25.000',
'mod': '1000.0'
}],
'no_chewie': [{
'file': 'SIDE\\all.lvl',
'offset': 0x0011CABC8,
'orig': '24',
'mod': '00'
}],
'no_gunners': [{
'file': 'SIDE\\all.lvl',
'offset': 0x0017D3DD8, # han 1
'orig': '15',
'mod': '00'
}, {
'file': 'SIDE\\all.lvl',
'offset': 0x0029784C8, # han 2
'orig': '15',
'mod': '00'
}, {
'file': 'SIDE\\all.lvl',
'offset': 0x0019D9EA0, # lei
'orig': '12',
'mod': '00'
}, {
'file': 'SIDE\\imp.lvl',
'offset': 0x002B3FAC, # boba
'orig': '12',
'mod': '00'
}, {
'file': 'SIDE\\cis.lvl',
'offset': 0x007994A8, # jango
'orig': '12',
'mod': '00'
}],
'no_force': [{
'file': 'SIDE\\cis.lvl',
'offset': 0x0052471C, # choke
'orig': '1',
'mod': '0'
}, {
'file': 'SIDE\\imp.lvl',
'offset': 0x003012E0, # choke 1
'orig': '1',
'mod': '0'
}, {
'file': 'SIDE\\imp.lvl',
'offset': 0x003A07E4, # choke 2
'orig': '1',
'mod': '0'
}, {
'file': 'SIDE\\imp.lvl',
'offset': 0x003A00E4, # lightning ord 1
'orig': '1',
'mod': '0'
}, {
'file': 'SIDE\\cis.lvl',
'offset': 0x0052401C, # lightning ord 2
'orig': '1',
'mod': '0'
}, {
'file': 'SIDE\\rep.lvl',
'offset': 0x00581CF4, # pull ord 1
'orig': '1',
'mod': '0'
}, {
'file': 'SIDE\\rep.lvl',
'offset': 0x0072A1D8, # pull ord 2
'orig': '1',
'mod': '0'
}, {
'file': 'SIDE\\rep.lvl',
'offset': 0x00958FF4, # pull ord 3
'orig': '1',
'mod': '0'
}, {
'file': 'SIDE\\rep.lvl',
'offset': 0x007B75B0, # push ord 1
'orig': '4',
'mod': '0'
}, {
'file': 'SIDE\\rep.lvl',
'offset': 0x00843518, # push ord 2
'orig': '4',
'mod': '0'
}, {
'file': 'SIDE\\rep.lvl',
'offset': 0x008CAC88, # push ord 3
'orig': '4',
'mod': '0'
}, {
'file': 'SIDE\\rep.lvl',
'offset': 0x00959548, # push ord 4
'orig': '4',
'mod': '0'
}, {
'file': 'SIDE\\cis.lvl',
'offset': 0x005B8128, # push ord 5
'orig': '4',
'mod': '0'
}, {
'file': 'SIDE\\all.lvl',
'offset': 0x001BF2D88, # push ord 6
'orig': '4',
'mod': '0'
}, {
'file': 'SIDE\\all.lvl',
'offset': 0x002020570, # push ord 7
'orig': '4',
'mod': '0'
}],
'no_border': [{
'file': 'TAT\\tat2.lvl',
'orig': 'TAT\\tat2.lvl.orig',
'mod': 'TAT\\tat2.lvl.mod'
}],
'enemy_tk': [{
'file': 'mission.lvl',
'offset': 0x0000C2BB,
'orig': [0x00],
'mod': [0x80]
}]
}
def write_bytes_to_file_at_offset(val, file, offset, encoding='ascii'):
with open(file, 'r+b') as f:
f.seek(offset)
if type(val) == str:
f.write(bytes(val, encoding))
else:
f.write(bytes(val))
def apply_mods(revert=False, mods_to_apply=set(mods.keys())):
if type(mods_to_apply) != set:
mods_to_apply = set(mods_to_apply)
for mod_name in mods:
if mod_name not in mods_to_apply or ((mod_name in always_on) and revert):
continue
mod_arr = mods[mod_name]
for mod in mod_arr:
if revert and mod_name:
val = mod['orig']
else:
val = mod['mod']
if mod.get('offset'):
write_bytes_to_file_at_offset(
val, mod['file'], mod['offset']
)
else:
os.remove(mod['file'])
shutil.copy(val, mod['file'])
return mods_to_apply
parser = argparse.ArgumentParser(description='Apply SWBF2 server mods.')
parser.add_argument(
'mods_to_apply', metavar='mod_name', type=str, nargs='*',
help='The mods you want to enable/disable. Defaults to all.', default=set(mods.keys())
)
parser.add_argument('--revert', help='Whether or not we are reverting changes. If not used, we are applying the mods.', action='store_true')
parser.add_argument('--list', help='To just list available mods.', action='store_true')
args = parser.parse_args()
if args.list:
print(', '.join(list(mods.keys())))
else:
print(', '.join(apply_mods(revert=args.revert, mods_to_apply=args.mods_to_apply)))