Dumping All Ini Settings Read By The Game
1. Foreword
Since there seems to be quite a bit of confusion on the internet about which ini keys the game reads, here's a method for dumping all sections, keys and default values straight from the exe. It uses an x64dbg script to dump everything from the winapi calls. Data is filtered in a separate Python script. The setup is quite barebones, but it does the job.
2. Disclaimer
The results have not been verified, but at the time of writing I have no reason to believe this is incorrect.
3. Notes
- A key read is not a key used. This is not a list of ini settings that change something in the game. If the key you're looking for is not dumped by this tool then it absolutely won't work: a key that is never read, can never be used.
- Default values are the values passed to the winapi function, not what is in the default ini files. stepmodifications.org most likely has a different definition of defaults.
- GetPrivateProfileIntA internally uses GetPrivateProfileStringA. The game does not use the wide char variants.
4. Permissions
You may use this without credit, but if you do create something useful with this, please post a link to it.
5. Summoning A Flame Atronach In The Real World
x64dbg script:
bp GetPrivateProfileStringA
SetBreakpointCommand GetPrivateProfileStringa, "scriptcmd call debug_getstring_a"
bp GetPrivateProfileStringW
SetBreakpointCommand GetPrivateProfileStringW, "scriptcmd call debug_getstring_w"
goto main
debug_getstring_a:
rtr
mem_file = ReadQword(rsp+30)
mem_section = ReadQword(rsp-38)
mem_key = ReadQword(rsp-30)
mem_value = ReadQword(rsp-48)
mem_default = ReadQword(rsp-28)
log "GetPrivateProfileStringA({s:mem_section}, {s:mem_key}, {s:mem_default}, {s:mem_value}, ?, {s:mem_file})"
goto main
debug_getstring_w:
rtr
mem_file = ReadQword(rsp+30)
mem_section = ReadQword(rsp-38)
mem_key = ReadQword(rsp-30)
mem_value = ReadQword(rsp-48)
mem_default = ReadQword(rsp-28)
log "GetPrivateProfileStringW({s:mem_section}, {s:mem_key}, {s:mem_default}, {s:mem_value}, ?, {s:mem_file})"
goto main
main:
run
Python x64dbg dump/log filter and data reformatter:
import sys, os
def main(argc, argv):
raw_data_file = 'data_raw.txt'
files = {}
with open(raw_data_file, 'r') as f:
for line in f:
line = line.strip()
if line.startswith('GetPrivateProfileString'):
is_wide = line[23] == 'W'
raw_args = line[25:-1]
args = parse_args(raw_args)
#args = [s.strip(' "') for s in args]
app_name, key_name, default, returned_string, unk1, file = args
if file == '???' or file == '':
continue
section = app_name # Translate Microslop to makes-sense.
section = section.strip() # NavMesh section is named " NavMesh"
file = file.replace('\\\\', '\\')
file = file.replace('/', '\\') # Normalize directory separator
file_id = get_file_id(file)
file_id = file_id.strip('\\') # Strip leading/trailing slash
file_id = file_id.replace('\\', '__') # Remove slashes
file_id = file_id.replace(' ', '_') # Remove spaces
if not file_id in files:
files[file_id] = {}
#print(f'{file} -> {file_id}')
if not section in files[file_id]:
files[file_id][section] = []
files[file_id][section].append({
'key_name': key_name,
'default': default,
'is_wide': is_wide
})
dump_files(files)
def dump_files(files):
if not os.path.isdir('output_inis'):
os.makedirs('output_inis')
for file_id in files:
#print(f'{file} {len(file)}')
with open('output_inis\\' + file_id, 'w') as f:
for section_id in files[file_id]:
section = files[file_id][section_id]
f.write(f'\n[{section_id}]\n')
for kv in section:
key_name = kv['key_name']
default = kv['default']
f.write(f'{key_name}={default}\n')
def parse_args(raw_args):
args = []
arg = ''
in_quotes = False
for i in range(len(raw_args)):
c = raw_args[i]
if c == '"':
if in_quotes:
in_quotes = False
else:
in_quotes = True
elif c == ',':
if in_quotes:
arg += c
else:
args.append(arg)
arg = ''
elif in_quotes or c == '?':
arg += c
#if len(arg) > 0:
args.append(arg)
return args
def get_file_id(file):
count = 0
for i in range(len(file) - 1, 0, -1):
c = file[i]
if c == '\\' or c == '/':
count += 1
if count == 2:
return file[i:]
return file
if __name__ == '__main__':
main(len(sys.argv), sys.argv)
All ini keys dumped from SkyrimSE.exe 1.6.1170.0 (7a44a52dfc92d78f934c4d12ed92f494) are in the attachment below.
Edited by traison
1 Comment
Recommended Comments