amd-tools: consolidate patching and farc (un)packing into AFT Shader Patcher, and switch to xdelta vcdiff generation for use with new Novidia
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
# gane-specific settings for diva arcade future tone
|
||||
GAME_NAME = "Project DIVA Arcade: Future Tone"
|
||||
|
||||
from re import compile as recompile
|
||||
|
||||
# some skinning can't be done due to using NV parameter buffers, so use an otherwise unused texture instead.
|
||||
VP_SKINNING_REGEX = recompile(r"INT TEMP _adr;\s*?(BUFFER4[^;]*?;)([\s\S]*?_mtxpal)")
|
||||
VP_SKINNING_SUB = "TEMP _adr;\n#\\1 \nTEMP mtx_tmp, mtx_tmp1, mtx_tmp2;\\2"
|
||||
|
||||
# load the address temp with a texture coordinate instead
|
||||
# hardcoded to assume a 768*1 pixel texture
|
||||
VP_SKINNING_REGEX_2 = recompile(r"(BUFFER4[\s\S]*?)CVT(?:.S32.F32)?\s*?(.*?),\s*?(.*?);")
|
||||
VP_SKINNING_SUB_2 = "\\1MUL \\2, \\3, 0.0013037809647979;" #1/767
|
||||
|
||||
VP_SKINNING_REGEX_3 = recompile(r"(BUFFER4[\s\S]*?)(DP4 ([^;]*?), _mtxpal\[([^\]]*?\.x) \])")
|
||||
VP_SKINNING_REGEX_3b = recompile(r"(BUFFER4[\s\S]*?)(DP4 ([^;]*?), _mtxpal\[([^\]]*?\.y) \])")
|
||||
VP_SKINNING_REGEX_3c = recompile(r"(BUFFER4[\s\S]*?)(DP4 ([^;]*?), _mtxpal\[([^\]]*?\.z) \])")
|
||||
VP_SKINNING_REGEX_3d = recompile(r"(BUFFER4[\s\S]*?)(DP4 ([^;]*?), _mtxpal\[([^\]]*?\.w) \])")
|
||||
VP_SKINNING_SUB_3 = "\\1TEX mtx_tmp, \\4, texture[8], 1D; ADD \\4, \\4, 0.0013037809647979; TEX mtx_tmp1, \\4, texture[8], 1D; ADD \\4, \\4, 0.0013037809647979; TEX mtx_tmp2, \\4, texture[8], 1D; DP4 \\3, _mtxpal[\\4+ ]"
|
||||
|
||||
VP_SKINNING_REGEX_4 = recompile(r"(BUFFER4[\s\S]*?)_mtxpal\[([^\]]*?\.\S)\+?(\s|\d)\]")
|
||||
VP_SKINNING_SUB_4 = "\\1mtx_tmp\\3"
|
||||
|
||||
|
||||
# some kind of effect (scrunching up or stretching?) in tights vp shaders starting with 11 that partially uses skinning of normals...
|
||||
# works much the same as the regular vp skinning
|
||||
# make these run before VP_SKINNING_REGEX_4
|
||||
#TIGHTS_SKINNING_EFFECT_REGEX = recompile(r"(BUFFER4[\s\S]*?)SUBC .*?, vertex.attrib\[15\], .*?;\s*?SUBC1 tmp, vertex.attrib\[15\], .*?;[\s\S]*?XPD .*?, .*?, .*?;\s*?DP3_SAT (.*?), .*?, .*?;")
|
||||
#TIGHTS_SKINNING_EFFECT_SUB = "\\1MOV \\2, 0;"
|
||||
TIGHTS_SKINNING_EFFECT_REGEX = recompile(r"(BUFFER4[\s\S]*?)CVT(?:.S32.F32)?\s*?(.*?),\s*?(.*?);")
|
||||
TIGHTS_SKINNING_EFFECT_SUB = "\\1MUL \\2, \\3, 0.0013037809647979;"
|
||||
#TIGHTS_SKINNING_EFFECT_REGEX_2 = recompile(r"(BUFFER4[\s\S]*?)(IF GT1.y;[\s\S]*?|)(DP3 ([^;]*?), _mtxpal\[([^\]]*?\.x) \])(?![\s\S]*MUL _adr)")
|
||||
#TIGHTS_SKINNING_EFFECT_REGEX_2b = recompile(r"(BUFFER4[\s\S]*?)(IF GT1.y;[\s\S]*?|)(DP3 ([^;]*?), _mtxpal\[([^\]]*?\.y) \])(?![\s\S]*MUL _adr)")
|
||||
#TIGHTS_SKINNING_EFFECT_SUB_2 = "\\1TEX mtx_tmp, \\5, texture[8], 1D; ADD \\5, \\5, 0.0013037809647979; TEX mtx_tmp1, \\5, texture[8], 1D; ADD \\5, \\5, 0.0013037809647979; TEX mtx_tmp2, \\5, texture[8], 1D; \\2 DP3 \\4, _mtxpal[\\5+ ]"
|
||||
TIGHTS_SKINNING_EFFECT_REGEX_2 = recompile(r"(BUFFER4[\s\S]*?)(DP3 ([^;]*?), _mtxpal\[([^\]]*?\.x) \])(?![\s\S]*MUL _adr)")
|
||||
TIGHTS_SKINNING_EFFECT_SUB_2 = "\\1TEX mtx_tmp, \\4, texture[8], 1D; ADD \\4, \\4, 0.0013037809647979; TEX mtx_tmp1, \\4, texture[8], 1D; ADD \\4, \\4, 0.0013037809647979; TEX mtx_tmp2, \\4, texture[8], 1D; DP3 \\3, _mtxpal[\\4+ ]"
|
||||
TIGHTS_SKINNING_EFFECT_REGEX_3 = recompile(r"(BUFFER4[\s\S]*?)(DP3 ([^;]*?), _mtxpal\[([^\]]*?\.y) \])(?![\s\S]*MUL _adr)")
|
||||
TIGHTS_SKINNING_EFFECT_SUB_3 = "\\1TEX mtx_tmp, \\4, texture[8], 1D; DP3 \\3, _mtxpal[\\4+ ]"
|
||||
TIGHTS_SKINNING_EFFECT_REGEX_4 = recompile(r"(BUFFER4[\s\S]*?)(DP3 ([^;]*?), _mtxpal\[([^\]]*?\.y)\+1\])(?![\s\S]*MUL _adr)")
|
||||
TIGHTS_SKINNING_EFFECT_SUB_4 = "\\1ADD \\4, \\4, 0.0013037809647979; TEX mtx_tmp1, \\4, texture[8], 1D; ADD \\4, \\4, 0.0013037809647979; TEX mtx_tmp2, \\4, texture[8], 1D; DP3 \\3, _mtxpal[\\4+1]"
|
||||
|
||||
|
||||
|
||||
# some skinning just needs some branching fixup for AMD BRA working different (needs a target per branch instruction apparently)
|
||||
SKIN_BRA_REGEX = recompile(r"BRA skinning_(?:\S*)end (\(.*?\.y\))*?\s*?;")
|
||||
SKIN_BRA_SUB = "BRA sk_end1 \\1;"
|
||||
SKIN_BRA_REGEX_2 = recompile(r"BRA skinning_(?:\S*)end (\(.*?\.z\))*?\s*?;")
|
||||
SKIN_BRA_SUB_2 = "BRA sk_end2 \\1;"
|
||||
SKIN_BRA_REGEX_3 = recompile(r"BRA skinning_(?:\S*)end (\(.*?\.w\))*?\s*?;")
|
||||
SKIN_BRA_SUB_3 = "BRA sk_end3 \\1;"
|
||||
|
||||
SKIN_BRA_TGT_REGEX = recompile(r"skinning_(?:\S*)end:")
|
||||
SKIN_BRA_TGT_SUB = "\nsk_end1:\nsk_end2:\nsk_end3:\n"
|
||||
|
||||
|
||||
MATCH_FP_FNAME = recompile(r".*fp")
|
||||
MATCH_VP_FNAME = recompile(r".*vp")
|
||||
MATCH_ALL_FNAME = recompile(r".*")
|
||||
MATCH_FONT_FNAME = recompile(r"font.*fp")
|
||||
MATCH_TIGHTS_FNAME = recompile(r"tights.*vp")
|
||||
|
||||
# fourth param: apply continuously until no more matches are found
|
||||
fix_repls = [
|
||||
(MATCH_VP_FNAME, VP_SKINNING_REGEX, VP_SKINNING_SUB, False),
|
||||
(MATCH_VP_FNAME, VP_SKINNING_REGEX_2, VP_SKINNING_SUB_2, False),
|
||||
(MATCH_VP_FNAME, VP_SKINNING_REGEX_3, VP_SKINNING_SUB_3, False),
|
||||
(MATCH_VP_FNAME, VP_SKINNING_REGEX_3b, VP_SKINNING_SUB_3, False),
|
||||
(MATCH_VP_FNAME, VP_SKINNING_REGEX_3c, VP_SKINNING_SUB_3, False),
|
||||
(MATCH_VP_FNAME, VP_SKINNING_REGEX_3d, VP_SKINNING_SUB_3, False),
|
||||
(MATCH_TIGHTS_FNAME, TIGHTS_SKINNING_EFFECT_REGEX, TIGHTS_SKINNING_EFFECT_SUB, False),
|
||||
(MATCH_TIGHTS_FNAME, TIGHTS_SKINNING_EFFECT_REGEX_2, TIGHTS_SKINNING_EFFECT_SUB_2, False),
|
||||
(MATCH_TIGHTS_FNAME, TIGHTS_SKINNING_EFFECT_REGEX_3, TIGHTS_SKINNING_EFFECT_SUB_3, False),
|
||||
(MATCH_TIGHTS_FNAME, TIGHTS_SKINNING_EFFECT_REGEX_4, TIGHTS_SKINNING_EFFECT_SUB_4, False),
|
||||
(MATCH_VP_FNAME, VP_SKINNING_REGEX_4, VP_SKINNING_SUB_4, True),
|
||||
(MATCH_VP_FNAME, SKIN_BRA_REGEX, SKIN_BRA_SUB, False),
|
||||
(MATCH_VP_FNAME, SKIN_BRA_REGEX_2, SKIN_BRA_SUB_2, False),
|
||||
(MATCH_VP_FNAME, SKIN_BRA_REGEX_3, SKIN_BRA_SUB_3, False),
|
||||
(MATCH_VP_FNAME, SKIN_BRA_TGT_REGEX, SKIN_BRA_TGT_SUB, False),
|
||||
]
|
||||
|
||||
|
||||
# use to open a differnt file instead of the original shader (eg. to replace with an easier-to-patch variant
|
||||
def filename_filter(fname):
|
||||
if fname == 'sss_filter.130.fp':
|
||||
return 'sss_filter.120.fp'
|
||||
elif fname == 'sss_filter.131.fp':
|
||||
return 'sss_filter.121.fp'
|
||||
else:
|
||||
return fname
|
||||
|
||||
|
||||
# use to tweak reasults after patching
|
||||
def post_filter(fname, f_full):
|
||||
f_full = f_full.replace('\nBUFFER4', '\n#BUFFER4')
|
||||
|
||||
# fix tex sampler offsets
|
||||
# diva has something we can use for pixel size (program.local[0]), but it isn't in fonts so they get an approximation instead
|
||||
if MATCH_FONT_FNAME.match(fname):
|
||||
f_full = f_full.replace('{ 0.00078, 0.0014 }; ADD tex_offset_coord', '0.00035; ADD tex_offset_coord')
|
||||
else:
|
||||
f_full = f_full.replace('{ 0.00078, 0.0014 }; ADD tex_offset_coord', 'program.local[0]; ADD tex_offset_coord')
|
||||
|
||||
return f_full
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
# gane-specific settings for diva arcade future tone
|
||||
GAME_NAME = "Project DIVA Arcade: Future Tone"
|
||||
|
||||
from re import compile as recompile
|
||||
|
||||
# some skinning can't be done due to using NV parameter buffers, so just disable it.
|
||||
VP_SKINNING_REGEX = recompile(r"(BUFFER4[\s\S]*?)MOV (.*?).w, 1; SUBC _tmp1, vertex.attrib\[15\], (-1|255); (.*?) IF NE.y; (.*?) MAD \2.xyz, _tmp0, vertex.attrib\[1\].y, \2; (.*?) MAD (.*?).xyz, _tmp0, vertex.attrib\[1\].y, \7; (.*?) MAD (.*?).xyz, _tmp0, vertex.attrib\[1\].y, \9; IF NE.z; (.*?) MAD \2.xyz, _tmp0, vertex.attrib\[1\].z, \2; (.*?) MAD \7.xyz, _tmp0, vertex.attrib\[1\].z, \7; (.*?) MAD \9.xyz, _tmp0, vertex.attrib\[1\].z, \9; IF NE.w; (.*?) MAD \2.xyz, _tmp0, vertex.attrib\[1\].w, \2; (.*?) MAD \7.xyz, _tmp0, vertex.attrib\[1\].w, \7; (.*?) MAD \9.xyz, _tmp0, vertex.attrib\[1\].w, \9; (?:ENDIF; ENDIF; ENDIF;|.*:)")
|
||||
VP_SKINNING_SUB = "\\1MOV \\2.w, 1; MOV \\2.xyz, vertex.position; MOV \\7, vertex.normal; MOV \\9, vertex.attrib[6];"
|
||||
|
||||
VP_SKINNING_REGEX_2 = recompile(r"(BUFFER4[\s\S]*?)MOV (.*?).w, 1; SUBC _tmp1, vertex.attrib\[15\], (-1|255); (.*?) IF NE.y; (.*?) MAD \2.xyz, _tmp0, vertex.attrib\[1\].y, \2; (.*?) MAD (.*?).xyz, _tmp0, vertex.attrib\[1\].y, \7; IF NE.z; (.*?) MAD \2.xyz, _tmp0, vertex.attrib\[1\].z, \2; (.*?) MAD \7.xyz, _tmp0, vertex.attrib\[1\].z, \7; IF NE.w; (.*?) MAD \2.xyz, _tmp0, vertex.attrib\[1\].w, \2; (.*?) MAD \7.xyz, _tmp0, vertex.attrib\[1\].w, \7; (?:ENDIF; ENDIF; ENDIF;|.*:)")
|
||||
VP_SKINNING_SUB_2 = "\\1MOV \\2.w, 1; MOV \\2.xyz, vertex.position; MOV \\7, vertex.normal;"
|
||||
|
||||
VP_SKINNING_REGEX_3 = recompile(r"(BUFFER4[\s\S]*?)MOV (.*?).w, 1; SUBC _tmp1, vertex.attrib\[15\], (-1|255); (.*?) IF NE.y; (.*?) MAD \2.xyz, _tmp0, vertex.attrib\[1\].y, \2; IF NE.z; (.*?) MAD \2.xyz, _tmp0, vertex.attrib\[1\].z, \2; IF NE.w; (.*?) MAD \2.xyz, _tmp0, vertex.attrib\[1\].w, \2; (?:ENDIF; ENDIF; ENDIF;|.*:)")
|
||||
VP_SKINNING_SUB_3 = "\\1MOV \\2.w, 1; MOV \\2.xyz, vertex.position;"
|
||||
|
||||
|
||||
# some kind of effect (scrunching up or stretching?) in tights shaders starting with 11 that partially uses skinning of normals... let's just pretend it always results in 0
|
||||
TIGHTS_SKINNING_EFFECT_REGEX = recompile(r"(BUFFER4[\s\S]*?)SUBC .*?, vertex.attrib\[15\], .*?;\s*?SUBC1 tmp, vertex.attrib\[15\], .*?;[\s\S]*?XPD .*?, .*?, .*?;\s*?DP3_SAT (.*?), .*?, .*?;")
|
||||
TIGHTS_SKINNING_EFFECT_SUB = "\\1MOV \\2, 0;"
|
||||
|
||||
|
||||
# some skinning just needs some branching fixup for AMD BRA working different (needs a target per branch instruction apparently)
|
||||
SKIN_BRA_REGEX = recompile(r"BRA skinning_(?:\S*)end (\(.*?\.y\))*?\s*?;")
|
||||
SKIN_BRA_SUB = "BRA sk_end1 \\1;"
|
||||
SKIN_BRA_REGEX_2 = recompile(r"BRA skinning_(?:\S*)end (\(.*?\.z\))*?\s*?;")
|
||||
SKIN_BRA_SUB_2 = "BRA sk_end2 \\1;"
|
||||
SKIN_BRA_REGEX_3 = recompile(r"BRA skinning_(?:\S*)end (\(.*?\.w\))*?\s*?;")
|
||||
SKIN_BRA_SUB_3 = "BRA sk_end3 \\1;"
|
||||
|
||||
SKIN_BRA_TGT_REGEX = recompile(r"skinning_(?:\S*)end:")
|
||||
SKIN_BRA_TGT_SUB = "\nsk_end1:\nsk_end2:\nsk_end3:\n"
|
||||
|
||||
|
||||
MATCH_FP_FNAME = recompile(r".*fp")
|
||||
MATCH_VP_FNAME = recompile(r".*vp")
|
||||
MATCH_ALL_FNAME = recompile(r".*")
|
||||
MATCH_FONT_FNAME = recompile(r"font.*fp")
|
||||
MATCH_TIGHTS_FNAME = recompile(r"tights.*vp")
|
||||
|
||||
# fourth param: apply continuously until no more matches are found
|
||||
fix_repls = [
|
||||
(MATCH_VP_FNAME, VP_SKINNING_REGEX, VP_SKINNING_SUB, False),
|
||||
(MATCH_VP_FNAME, VP_SKINNING_REGEX_2, VP_SKINNING_SUB_2, False),
|
||||
(MATCH_VP_FNAME, VP_SKINNING_REGEX_3, VP_SKINNING_SUB_3, False),
|
||||
(MATCH_TIGHTS_FNAME, TIGHTS_SKINNING_EFFECT_REGEX, TIGHTS_SKINNING_EFFECT_SUB, False),
|
||||
(MATCH_VP_FNAME, SKIN_BRA_REGEX, SKIN_BRA_SUB, False),
|
||||
(MATCH_VP_FNAME, SKIN_BRA_REGEX_2, SKIN_BRA_SUB_2, False),
|
||||
(MATCH_VP_FNAME, SKIN_BRA_REGEX_3, SKIN_BRA_SUB_3, False),
|
||||
(MATCH_VP_FNAME, SKIN_BRA_TGT_REGEX, SKIN_BRA_TGT_SUB, False),
|
||||
]
|
||||
|
||||
|
||||
# use to open a differnt file instead of the original shader (eg. to replace with an easier-to-patch variant
|
||||
def filename_filter(fname):
|
||||
if fname == 'sss_filter.130.fp':
|
||||
return 'sss_filter.120.fp'
|
||||
elif fname == 'sss_filter.131.fp':
|
||||
return 'sss_filter.121.fp'
|
||||
else:
|
||||
return fname
|
||||
|
||||
|
||||
# use to tweak reasults after patching
|
||||
def post_filter(fname, f_full):
|
||||
f_full = f_full.replace('\nBUFFER4', '\n#BUFFER4')
|
||||
|
||||
# fix tex sampler offsets
|
||||
# diva has something we can use for pixel size (program.local[0]), but it isn't in fonts so they get an approximation instead
|
||||
if MATCH_FONT_FNAME.match(fname):
|
||||
f_full = f_full.replace('{ 0.00078, 0.0014 }; ADD tex_offset_coord', '0.00035; ADD tex_offset_coord')
|
||||
else:
|
||||
f_full = f_full.replace('{ 0.00078, 0.0014 }; ADD tex_offset_coord', 'program.local[0]; ADD tex_offset_coord')
|
||||
|
||||
return f_full
|
||||
@@ -0,0 +1,240 @@
|
||||
from rules import *
|
||||
|
||||
def patch_shader(fname, f_lines, game_settings_module=None, enable_print_warnings=True):
|
||||
f_lines[0] += "#" + fname +"\n"
|
||||
|
||||
|
||||
# do attrib, output, param simplification and replace header tags
|
||||
attribs = {}
|
||||
outputs = {}
|
||||
params = {}
|
||||
|
||||
for i in range(0, len(f_lines)):
|
||||
# this inlining stuff is only really needed for vp, but :shrug:
|
||||
no_repl = False
|
||||
|
||||
m = ATTRIB_REGEX.match(f_lines[i])
|
||||
while m:
|
||||
attribs[m.group(3)] = m.group(4)
|
||||
f_lines[i] = ATTRIB_REGEX.sub('#\\1 \n', f_lines[i], count=1)
|
||||
no_repl = True
|
||||
m = ATTRIB_REGEX.match(f_lines[i])
|
||||
|
||||
m = OUTPUT_REGEX.match(f_lines[i])
|
||||
while m:
|
||||
outputs[m.group(3)] = m.group(4)
|
||||
f_lines[i] = OUTPUT_REGEX.sub('#\\1 \n', f_lines[i], count=1)
|
||||
no_repl = True
|
||||
m = OUTPUT_REGEX.match(f_lines[i])
|
||||
|
||||
# and not needed at all for PARAMs in the end, but :shrug: again
|
||||
m = PARAM_REGEX.match(f_lines[i])
|
||||
while m:
|
||||
params[m.group(3)] = m.group(4)
|
||||
f_lines[i] = PARAM_REGEX.sub('#\\1 \n', f_lines[i], count=1)
|
||||
no_repl = True
|
||||
m = PARAM_REGEX.match(f_lines[i])
|
||||
|
||||
if no_repl:
|
||||
continue
|
||||
|
||||
replace_tokens = attribs.copy()
|
||||
replace_tokens.update(outputs)
|
||||
replace_tokens.update(params)
|
||||
|
||||
# sort from longest to shortest to minimize risk of names that are substrings of others causing issues
|
||||
for k in sorted(replace_tokens.keys(), reverse=True, key=lambda s: len(s)):
|
||||
# look for specific preceding characters to not replace in the middle of a name
|
||||
f_lines[i] = f_lines[i].replace(' ' + k, ' ' + replace_tokens[k])
|
||||
f_lines[i] = f_lines[i].replace('-' + k, '-' + replace_tokens[k])
|
||||
f_lines[i] = f_lines[i].replace('{' + k, '{' + replace_tokens[k])
|
||||
|
||||
# options are required for conditional stuff...
|
||||
# tysm nezarn for teaching me this because otherwise the patch would never be anywhere near as complete
|
||||
f_lines[i] = NVFP_HEADER_REGEX.sub("!!ARBfp1.0\nOPTION NV_fragment_program2;", f_lines[i])
|
||||
f_lines[i] = NVVP_HEADER_REGEX.sub("!!ARBvp1.0\nOPTION NV_vertex_program3;", f_lines[i])
|
||||
|
||||
f_full = ''.join(f_lines)
|
||||
|
||||
f_full = UNUSED_PARAM_REGEX.sub("#\\1 \n", f_full)
|
||||
|
||||
|
||||
# add a temp vars for NRM and SSG replacements before their first use
|
||||
f_full = f_full.replace('NRM ', 'TEMP nrm_tmp; NRM ', 1)
|
||||
f_full = f_full.replace('SSG ', 'TEMP ssg_tmp1, ssg_tmp2; SSG ', 1)
|
||||
# this needs to use the regex to match because it isn't just a single instruction
|
||||
f_full = SAMPLER_OFFSET_REGEX.sub('TEMP tex_offset_coord; \\g<0>', f_full, count=1)
|
||||
|
||||
for r in fix_repls:
|
||||
if r[0].match(fname):
|
||||
if r[3]: # r[3]: apply until no matches
|
||||
while r[1].search(f_full):
|
||||
f_full = r[1].sub(r[2], f_full)
|
||||
else:
|
||||
f_full = r[1].sub(r[2], f_full)
|
||||
|
||||
if game_settings_module and game_settings_module.fix_repls:
|
||||
for r in game_settings_module.fix_repls:
|
||||
if r[0].match(fname):
|
||||
if r[3]: # r[3]: apply until no matches
|
||||
while r[1].search(f_full):
|
||||
f_full = r[1].sub(r[2], f_full)
|
||||
else:
|
||||
f_full = r[1].sub(r[2], f_full)
|
||||
|
||||
|
||||
f_full = f_full.replace('INT TEMP ', 'TEMP ')
|
||||
|
||||
if game_settings_module and game_settings_module.post_filter:
|
||||
f_full = game_settings_module.post_filter(fname, f_full)
|
||||
|
||||
|
||||
bad_instr = BAD_INSTR_REGEX.search(f_full)
|
||||
if bad_instr:
|
||||
if enable_print_warnings:
|
||||
print ('\rWarning -- unpatched known bad instruction(s) in {} -- first: {}'.format(fname, bad_instr.group(1)))
|
||||
f_full = BAD_INSTR_REGEX.sub("#\\1 \n", f_full)
|
||||
|
||||
f_full = f_full.replace('\r', '')
|
||||
|
||||
return f_full
|
||||
|
||||
if __name__ == '__main__':
|
||||
from os import listdir, makedirs, get_terminal_size
|
||||
from os.path import join as joinpath, splitext, isfile, exists, dirname
|
||||
from re import compile as recompile
|
||||
import sys
|
||||
import importlib, importlib.util
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
datadir = dirname(sys.executable)
|
||||
else:
|
||||
datadir = dirname(__file__)
|
||||
|
||||
gamesettings_dir = joinpath(datadir, 'gamesettings')
|
||||
|
||||
def get_args():
|
||||
# make a list of game settings modules
|
||||
game_modules_list = [f[:-3] for f in listdir(gamesettings_dir) if isfile(joinpath(gamesettings_dir, f)) and splitext(f)[1] == '.py']
|
||||
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description='AMD ARB Patcher: An attempt at converting Nvidia-only ARB shaders to work on AMD. Patching techniques from Nezarn; implementation by somewhatlurker.')
|
||||
parser.add_argument('-i', '--in_dir', default='shader in', help='input directory containing .vp and .fp files (default: "shader in")')
|
||||
parser.add_argument('-o', '--out_dir', default='shader patched', help='output directory (default: "shader patched")')
|
||||
parser.add_argument('-f', '--file_filter', default=None, help='only process files that match this filter (regex)')
|
||||
parser.add_argument('-g', '--game_settings', default=None, choices=game_modules_list, help='game-specific settings to load')
|
||||
|
||||
parser.add_argument('--list_games', action='store_true', help='list game names for game-specific settings options instead of patching shaders')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
def print_game_modules():
|
||||
# make a list of game settings modules
|
||||
game_modules_list = [f[:-3] for f in listdir(gamesettings_dir) if isfile(joinpath(gamesettings_dir, f)) and splitext(f)[1] == '.py']
|
||||
|
||||
for game in game_modules_list:
|
||||
game_settings_spec = importlib.util.spec_from_file_location(game, joinpath(gamesettings_dir, game + '.py'))
|
||||
game_settings_module = importlib.util.module_from_spec(game_settings_spec)
|
||||
game_settings_spec.loader.exec_module(game_settings_module)
|
||||
|
||||
if game_settings_module.GAME_NAME:
|
||||
full_name = game_settings_module.GAME_NAME
|
||||
else:
|
||||
full_name = 'Unknown'
|
||||
|
||||
print ('{}: {}'.format(game, full_name))
|
||||
|
||||
|
||||
args = get_args()
|
||||
|
||||
if args.list_games:
|
||||
print_game_modules()
|
||||
sys.exit()
|
||||
|
||||
print("AMD ARB Patcher")
|
||||
print("===============")
|
||||
print("Patching techniques from Nezarn; implementation by somewhatlurker")
|
||||
print("=================================================================")
|
||||
print("Input directory: '{}'".format(args.in_dir))
|
||||
print("Output directory: '{}'".format(args.out_dir))
|
||||
|
||||
if args.game_settings:
|
||||
game_settings_spec = importlib.util.spec_from_file_location(args.game_settings, joinpath(gamesettings_dir, args.game_settings + '.py'))
|
||||
game_settings_module = importlib.util.module_from_spec(game_settings_spec)
|
||||
game_settings_spec.loader.exec_module(game_settings_module)
|
||||
if game_settings_module.GAME_NAME:
|
||||
print("Using game-specific settings for {}".format(game_settings_module.GAME_NAME))
|
||||
else:
|
||||
print("Using game-specific settings for {}".format(args.game_settings))
|
||||
else:
|
||||
game_settings_module = None
|
||||
|
||||
IN_DIR = args.in_dir
|
||||
OUT_DIR = args.out_dir
|
||||
|
||||
if not exists(IN_DIR):
|
||||
print ("Directory '{}' does not exist. Aborting".format(IN_DIR))
|
||||
sys.exit()
|
||||
elif isfile(IN_DIR):
|
||||
print ("'{}' is not a directory. Aborting".format(IN_DIR))
|
||||
sys.exit()
|
||||
|
||||
if exists(OUT_DIR):
|
||||
if isfile(OUT_DIR):
|
||||
print ("'{}' already exists but is not a directory. Aborting".format(OUT_DIR))
|
||||
sys.exit()
|
||||
else:
|
||||
makedirs(OUT_DIR)
|
||||
|
||||
if args.file_filter:
|
||||
file_filter = recompile(args.file_filter)
|
||||
else:
|
||||
file_filter = None
|
||||
|
||||
indir_list = listdir(IN_DIR)
|
||||
|
||||
if file_filter:
|
||||
indir_list = [f for f in indir_list if file_filter.match(f)]
|
||||
|
||||
proc_count = 0
|
||||
|
||||
last_status_len = 0
|
||||
|
||||
for fname in indir_list:
|
||||
proc_count += 1
|
||||
progress_val = proc_count / len(indir_list)
|
||||
progress_cnt_X = int(progress_val * 20)
|
||||
|
||||
status_str = '\r[{e:{s1}<{n1}}{e:{s2}<{n2}}]'.format(e='', s1='X', s2='-', n1=progress_cnt_X, n2=20-progress_cnt_X)
|
||||
status_str += ' {:.2%}'.format(progress_val)
|
||||
status_str += ' ' + fname
|
||||
|
||||
try:
|
||||
terminal_width = get_terminal_size()[0]
|
||||
except:
|
||||
terminal_width = 120
|
||||
|
||||
if len(status_str) > terminal_width:
|
||||
status_str = status_str[:terminal_width-3] + '...'
|
||||
|
||||
# fix characters left on screen from a previous longer line
|
||||
# (without cursor staying off to the side)
|
||||
this_status_len = len(status_str)
|
||||
if this_status_len < last_status_len:
|
||||
status_str = '{: <{l}}'.format(status_str, l=last_status_len)
|
||||
last_status_len = this_status_len
|
||||
|
||||
print (status_str, end='')
|
||||
|
||||
if game_settings_module and game_settings_module.filename_filter:
|
||||
openname = game_settings_module.filename_filter(fname)
|
||||
else:
|
||||
openname = fname
|
||||
|
||||
with open(joinpath(IN_DIR, openname), 'r') as f:
|
||||
f_lines = f.readlines()
|
||||
|
||||
f_full = patch_shader(fname, f_lines, game_settings_module, True)
|
||||
|
||||
with open(joinpath(OUT_DIR, fname), 'wb') as f:
|
||||
f.write(f_full.encode('utf-8'))
|
||||
@@ -0,0 +1,80 @@
|
||||
# regexes and config info is stored here to reduce clutter in main script
|
||||
from re import compile as recompile
|
||||
|
||||
# inline these because... idk
|
||||
# ATTRIB and OUTPUT must be inlined for vp tho
|
||||
ATTRIB_REGEX = recompile(r"(?<!#)(?<!#\s)((SHORT\s*?|LONG\s*?)?ATTRIB\s*?([^\[\]\s]*?)\s*?=\s*?([^\s].*?);)\s*")
|
||||
OUTPUT_REGEX = recompile(r"(?<!#)(?<!#\s)((SHORT\s*?|LONG\s*?)?OUTPUT\s*?([^\[\]\s]*?)\s*?=\s*?([^\s].*?);)\s*")
|
||||
PARAM_REGEX = recompile(r"(?<!#)(?<!#\s)((SHORT\s*?|LONG\s*?)?PARAM\s*?([^\[\]\s]*?)\s*?=\s*?([^\s].*?);)\s*")
|
||||
|
||||
# help reduce param count more by commenting out unused ones that weren't inlined (array params can't be inlined)
|
||||
UNUSED_PARAM_REGEX = recompile(r"(?<!#)(?<!#\s)((SHORT\s*?|LONG\s*?)?PARAM\s*?([^\[\]\s]*?)(\s*?\[.*?\])?\s*?=\s*?([^\s].*?);)\s*(?![\s\S]*[\s\-\+{]\3[\s,;\.\-\+\[])")
|
||||
|
||||
# I think some of these instructions might still be left after other patches.. they will be commented out
|
||||
BAD_INSTR_REGEX = recompile(r"((?<!#)(?<!#\s)(CVT|BUFFER4|POW|NRM|REP)\s+?.*?;|ENDREP;|[^;\n]*\s*?(p_coef2d\[idx.x\]).*?;)\s*")
|
||||
|
||||
# find the header tag to replace with !!ARBxx1.0 and an NV OPTION
|
||||
NVFP_HEADER_REGEX = recompile(r"!!NVfp\d\.\d")
|
||||
NVVP_HEADER_REGEX = recompile(r"!!NVvp\d\.\d")
|
||||
|
||||
|
||||
# POW, NRM, and SSG sometimes(?) don't work (depending on inputs?), so just reimplement with other instructions
|
||||
POW_REGEX = recompile(r"POW(\s+?|_SAT\s+?)(.*?),\s+?(.*?),\s+?(.*?);")
|
||||
POW_SUB = "LG2 \\2, \\3; MUL \\2, \\2, \\4; EX2\\1\\2, \\2;"
|
||||
|
||||
NRM_REGEX = recompile(r"NRM\s+?(.*?),\s+?(.*?);")
|
||||
NRM_SUB = "DP3 nrm_tmp.w, \\2, \\2; RSQ nrm_tmp.w, nrm_tmp.w; MUL \\1, nrm_tmp.w, \\2;"
|
||||
|
||||
SSG_REGEX = recompile(r"SSG\s+?(\S.*?\.\S*?),\s+?(.*?);")
|
||||
SSG_SUB = "SGE ssg_tmp1, \\2, 0; SUB ssg_tmp1, ssg_tmp1, 1; SGT ssg_tmp2, \\2, 0; ADD \\1, ssg_tmp1, ssg_tmp2;"
|
||||
|
||||
|
||||
# seems like using a TEX with pixel offsets is pretty common, but I can't make that work
|
||||
# afaik there's no general way to get texture resolution so I'll approximate one here that can be refined later per-shader
|
||||
# ({ 0.00078, 0.0014 } is approximately one pixel of a 720p texture/framebuffer)
|
||||
SAMPLER_OFFSET_REGEX = recompile(r"TEX\s+?([^;,]*?),\s+?([^;,]*?),\s+?([^;,]*?),\s+?2D,\s+?\((.*?)\);")
|
||||
SAMPLER_OFFSET_SUB = "MUL tex_offset_coord, {\\4}, { 0.00078, 0.0014 }; ADD tex_offset_coord, \\2, tex_offset_coord; TEX \\1, tex_offset_coord, \\3, 2D;"
|
||||
|
||||
|
||||
# ret at the end of an IF statement is moved to outside of it (or won't compile)
|
||||
# not a perfect solution, but works well enough
|
||||
RET_IF_REGEX = recompile(r"(IF\s+?(.*?);[\s\S]*?)RET;\s*?ENDIF;")
|
||||
RET_IF_SUB = "\\1ENDIF;\nRET (\\2);"
|
||||
|
||||
|
||||
# RET behaves differently on AMD, so convert conditional rets into if/else
|
||||
# (may not work properly with CAL)
|
||||
CONDITIONAL_RET_REGEX = recompile(r"RET\s+?\((.*?)\);([\s\S]*?)(END\s*?$|RET;)")
|
||||
CONDITIONAL_RET_SUB = "IF \\1; ELSE; \\2 ENDIF; \\3"
|
||||
|
||||
|
||||
# CAL behaves differently on AMD, so convert conditional cals into if
|
||||
CONDITIONAL_CAL_REGEX = recompile(r"CAL\s+?(.*?)\s+?\((.*?)\)\s*?;")
|
||||
CONDITIONAL_CAL_SUB = "IF \\2; CAL \\1; ENDIF;"
|
||||
|
||||
|
||||
# seems like some suffixes (eg .F, .CC1) don't work
|
||||
# first regex: drop everything before the CC suffix, and convert CC suffix to appended C0/C1
|
||||
INSTR_SUFFIX_REGEX = recompile(r"(;\s*?)([A-Z\d]+)[A-Z\d\.]*?\.CC(\d)")
|
||||
INSTR_SUFFIX_SUB = "\\1\\2C\\3"
|
||||
# second regex: remove all remaining suffixes (or all suffixes for instructions without CC)
|
||||
INSTR_SUFFIX_REGEX_2 = recompile(r"(;\s*?)([A-Z\d]+)\.[A-Z\.\d]+")
|
||||
INSTR_SUFFIX_SUB_2 = "\\1\\2"
|
||||
|
||||
|
||||
MATCH_FP_FNAME = recompile(r".*fp")
|
||||
MATCH_VP_FNAME = recompile(r".*vp")
|
||||
MATCH_ALL_FNAME = recompile(r".*")
|
||||
|
||||
# fourth param: apply continuously until no more matches are found
|
||||
fix_repls = [
|
||||
(MATCH_ALL_FNAME, POW_REGEX, POW_SUB, False),
|
||||
(MATCH_ALL_FNAME, NRM_REGEX, NRM_SUB, False),
|
||||
(MATCH_ALL_FNAME, SSG_REGEX, SSG_SUB, False),
|
||||
(MATCH_ALL_FNAME, SAMPLER_OFFSET_REGEX, SAMPLER_OFFSET_SUB, False),
|
||||
(MATCH_ALL_FNAME, RET_IF_REGEX, RET_IF_SUB, False),
|
||||
(MATCH_ALL_FNAME, CONDITIONAL_RET_REGEX, CONDITIONAL_RET_SUB, True),
|
||||
(MATCH_ALL_FNAME, CONDITIONAL_CAL_REGEX, CONDITIONAL_CAL_SUB, False),
|
||||
(MATCH_ALL_FNAME, INSTR_SUFFIX_REGEX, INSTR_SUFFIX_SUB, False),
|
||||
(MATCH_ALL_FNAME, INSTR_SUFFIX_REGEX_2, INSTR_SUFFIX_SUB_2, False),
|
||||
]
|
||||
@@ -0,0 +1,117 @@
|
||||
import sys
|
||||
from os import chdir, makedirs, get_terminal_size
|
||||
from os.path import join as joinpath, splitext, isfile, exists, dirname, abspath
|
||||
import pyfarc
|
||||
import importlib, importlib.util
|
||||
|
||||
if getattr(sys, 'frozen', False):
|
||||
datadir = dirname(sys.executable)
|
||||
else:
|
||||
datadir = dirname(__file__)
|
||||
|
||||
arbpatcher_dir = joinpath(datadir, 'ARB Patcher')
|
||||
arbpatcher_gamesettings_dir = joinpath(arbpatcher_dir, 'gamesettings')
|
||||
|
||||
sys.path = [arbpatcher_dir] + sys.path
|
||||
import main as ArbPatcher
|
||||
sys.path = sys.path[1:]
|
||||
|
||||
game_settings_spec = importlib.util.spec_from_file_location('divaaft', joinpath(arbpatcher_gamesettings_dir, 'divaaft.py'))
|
||||
game_settings_module = importlib.util.module_from_spec(game_settings_spec)
|
||||
game_settings_spec.loader.exec_module(game_settings_module)
|
||||
|
||||
|
||||
# not bothering to check for main here because it's just a glue script anyway
|
||||
def get_args():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(description='AFT Shader Patcher: Patches Nvidia-only ARB shaders from PDAFT to work on AMD. Patching techniques from Nezarn; implementation by somewhatlurker.')
|
||||
parser.add_argument('-i', '--in_farc', default='shader.farc', help='input shader farc file (default: "shader.farc")')
|
||||
parser.add_argument('-o', '--out_farc', default='shader_patched.farc', help='output shader farc file (default: "shader_patched.farc")')
|
||||
parser.add_argument('-c', '--compress', action='store_true', help='force use of farc compression (this mey produce larger vcdiff files)')
|
||||
parser.add_argument('-x', '--xdelta', action='store_true', help='generate a vcdiff patch file')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
args = get_args()
|
||||
|
||||
print("AFT Shader Patcher")
|
||||
print("==================")
|
||||
print("Patching techniques from Nezarn; implementation by somewhatlurker")
|
||||
print("=================================================================")
|
||||
print("Input file: '{}'".format(args.in_farc))
|
||||
print("Output file: '{}'".format(args.out_farc))
|
||||
|
||||
if not exists(args.in_farc):
|
||||
print ("'{}' does not exist. Aborting".format(args.in_farc))
|
||||
sys.exit()
|
||||
elif not isfile(args.in_farc):
|
||||
print ("'{}' is not a file. Aborting".format(args.in_farc))
|
||||
sys.exit()
|
||||
|
||||
if exists(args.out_farc):
|
||||
if not isfile(args.out_farc):
|
||||
print ("'{}' already exists but is a directory. Aborting".format(args.out_farc))
|
||||
sys.exit()
|
||||
|
||||
|
||||
with open(args.in_farc, 'rb') as f:
|
||||
farcdata = pyfarc.from_stream(f)
|
||||
if args.xdelta:
|
||||
f.seek(0)
|
||||
from binascii import crc32
|
||||
in_farc_crc_str = '{:08x}'.format(crc32(f.read()))
|
||||
|
||||
proc_count = 0
|
||||
|
||||
last_status_len = 0
|
||||
|
||||
for fname in farcdata['files']:
|
||||
proc_count += 1
|
||||
progress_val = proc_count / len(farcdata['files'])
|
||||
progress_cnt_X = int(progress_val * 20)
|
||||
|
||||
status_str = '\r[{e:{s1}<{n1}}{e:{s2}<{n2}}]'.format(e='', s1='X', s2='-', n1=progress_cnt_X, n2=20-progress_cnt_X)
|
||||
status_str += ' {:.2%}'.format(progress_val)
|
||||
status_str += ' ' + fname
|
||||
|
||||
try:
|
||||
terminal_width = get_terminal_size()[0]
|
||||
except:
|
||||
terminal_width = 120
|
||||
|
||||
if len(status_str) > terminal_width:
|
||||
status_str = status_str[:terminal_width-3] + '...'
|
||||
|
||||
# fix characters left on screen from a previous longer line
|
||||
# (without cursor staying off to the side)
|
||||
this_status_len = len(status_str)
|
||||
if this_status_len < last_status_len:
|
||||
status_str = '{: <{l}}'.format(status_str, l=last_status_len)
|
||||
last_status_len = this_status_len
|
||||
|
||||
print (status_str, end='')
|
||||
|
||||
if game_settings_module and game_settings_module.filename_filter:
|
||||
openname = game_settings_module.filename_filter(fname)
|
||||
else:
|
||||
openname = fname
|
||||
|
||||
f_lines = farcdata['files'][openname]['data'].decode('utf-8').splitlines(keepends=True)
|
||||
|
||||
f_full = ArbPatcher.patch_shader(fname, f_lines, game_settings_module, True)
|
||||
|
||||
farcdata['files'][fname]['data'] = f_full.encode('utf-8')
|
||||
|
||||
if args.compress:
|
||||
farcdata['farc_type'] = 'FArC'
|
||||
|
||||
with open(args.out_farc, 'wb') as f:
|
||||
pyfarc.to_stream(farcdata, f, no_copy=True)
|
||||
|
||||
if args.xdelta:
|
||||
import subprocess
|
||||
|
||||
# make paths absolute before running this
|
||||
#args.in_farc = abspath(args.in_farc)
|
||||
#args.out_farc = abspath(args.out_farc)
|
||||
subprocess.run([joinpath(datadir, 'xdelta3.exe'), '-e', '-f', '-S', 'none', '-s', args.in_farc, args.out_farc, joinpath(dirname(args.out_farc), in_farc_crc_str + '.vcdiff')], )
|
||||
@@ -0,0 +1,252 @@
|
||||
"""
|
||||
pyfarc reader and writer for farc archives
|
||||
supports Farc and FarC only
|
||||
"""
|
||||
|
||||
from construct import Struct, Const, Int32ub, Int32sb, RepeatUntil, CString, Pointer, Bytes, Padding
|
||||
from copy import deepcopy
|
||||
import gzip
|
||||
|
||||
_FArc_format = Struct(
|
||||
"signature" / Const(b'FArc'),
|
||||
"header_size" / Int32ub, # doesn't include signature or header_size
|
||||
"alignment" / Int32sb,
|
||||
"files" / RepeatUntil(lambda obj,lst,ctx: ctx._io.tell() - 7 > ctx.header_size, Struct(
|
||||
"name" / CString("utf8"),
|
||||
"pointer" / Int32ub,
|
||||
"size" / Int32ub,
|
||||
"data" / Pointer(lambda this: this.pointer, Bytes(lambda this: this.size))
|
||||
)),
|
||||
#Padding(lambda this: this.alignment - (this._io.tell() % this.alignment) if this._io.tell() % this.alignment else 0)
|
||||
)
|
||||
|
||||
_FArC_format = Struct(
|
||||
"signature" / Const(b'FArC'),
|
||||
"header_size" / Int32ub, # doesn't include signature or header_size
|
||||
"alignment" / Int32sb,
|
||||
"files" / RepeatUntil(lambda obj,lst,ctx: ctx._io.tell() - 7 > ctx.header_size, Struct(
|
||||
"name" / CString("utf8"),
|
||||
"pointer" / Int32ub,
|
||||
"compressed_size" / Int32ub,
|
||||
"uncompressed_size" / Int32ub,
|
||||
"data" / Pointer(lambda this: this.pointer, Bytes(lambda this: this.compressed_size))
|
||||
)),
|
||||
#Padding(lambda this: this.alignment - (this._io.tell() % this.alignment) if this._io.tell() % this.alignment else 0)
|
||||
)
|
||||
|
||||
_farc_types = {
|
||||
'FArc': {
|
||||
'remarks': 'basic farc format',
|
||||
'struct': _FArc_format,
|
||||
'compression_support': False,
|
||||
'compression_forced': False,
|
||||
'fixed_header_size': 4,
|
||||
'files_header_fields_size': 8,
|
||||
},
|
||||
'FArC': {
|
||||
'remarks': 'farc with compression support',
|
||||
'struct': _FArC_format,
|
||||
'compression_support': True,
|
||||
'compression_forced': True,
|
||||
'fixed_header_size': 4,
|
||||
'files_header_fields_size': 12,
|
||||
},
|
||||
}
|
||||
|
||||
class UnsupportedFarcTypeException(Exception):
|
||||
pass
|
||||
|
||||
def check_farc_type(t):
|
||||
"""Checks if a farc type is supported and returns a remarks string. Raises UnsupportedFarcTypeException if not supported."""
|
||||
|
||||
if not t in _farc_types:
|
||||
raise UnsupportedFarcTypeException("{} type not supported".format(t))
|
||||
|
||||
return _farc_types[t]['remarks']
|
||||
|
||||
|
||||
def _files_header_size_calc(files, farc_type):
|
||||
"""Sums the size of the files header section for the given files and farc_type data."""
|
||||
|
||||
size = 0
|
||||
for fname, info in files.items():
|
||||
size += len(fname) + 1
|
||||
size += farc_type['files_header_fields_size']
|
||||
return size
|
||||
|
||||
def _prep_files(files, alignment, farc_type):
|
||||
"""Gets files ready for writing by compressing them and calculating pointers."""
|
||||
|
||||
def _compress_files(files, farc_type):
|
||||
for fname, info in files.items():
|
||||
info['data_compressed'] = gzip.compress(info['data'], mtime=39) # set mtime for reproducible output
|
||||
if (not farc_type['compression_forced']) and (len(info['data_compressed']) >= len(info['data'])):
|
||||
info['data_compressed'] = info['data']
|
||||
|
||||
def _set_files_pointers(files, alignment, farc_type):
|
||||
pos = 8 + farc_type['fixed_header_size'] + _files_header_size_calc(files, farc_type)
|
||||
|
||||
for fname, info in files.items():
|
||||
if pos % alignment: pos += alignment - (pos % alignment)
|
||||
info['pointer'] = pos
|
||||
if 'data_compressed' in info:
|
||||
pos += len(info['data_compressed'])
|
||||
else:
|
||||
pos += len(info['data'])
|
||||
|
||||
if farc_type['compression_support']:
|
||||
_compress_files(files, farc_type)
|
||||
_set_files_pointers(files, alignment, farc_type)
|
||||
|
||||
def to_bytes(data, alignment=1, no_copy=False):
|
||||
"""
|
||||
Converts a farc dictionary (formatted like the dictionary returned by from_bytes) to an in-memory bytes object containing farc data.
|
||||
|
||||
Set no_copy to True for a speedup and memory usage reduction if you don't mind your input data being contaminated.
|
||||
"""
|
||||
|
||||
magic_str = data['farc_type']
|
||||
check_farc_type(magic_str)
|
||||
farc_type = _farc_types[magic_str]
|
||||
|
||||
if no_copy:
|
||||
files = data['files']
|
||||
else:
|
||||
files = deepcopy(data['files'])
|
||||
_prep_files(files, alignment, farc_type)
|
||||
|
||||
if farc_type['compression_support']:
|
||||
return farc_type['struct'].build(dict(
|
||||
header_size=farc_type['fixed_header_size'] + _files_header_size_calc(files, farc_type),
|
||||
alignment=alignment,
|
||||
files=[dict(
|
||||
name=fname,
|
||||
pointer=info['pointer'],
|
||||
compressed_size=len(info['data_compressed']),
|
||||
uncompressed_size=len(info['data']),
|
||||
data=info['data_compressed']
|
||||
) for fname, info in files.items()]
|
||||
))
|
||||
else:
|
||||
return farc_type['struct'].build(dict(
|
||||
header_size=farc_type['fixed_header_size'] + _files_header_size_calc(files, farc_type),
|
||||
alignment=alignment,
|
||||
files=[dict(
|
||||
name=fname,
|
||||
pointer=info['pointer'],
|
||||
size=len(info['data']),
|
||||
data=info['data']
|
||||
) for fname, info in files.items()]
|
||||
))
|
||||
|
||||
def to_stream(data, stream, alignment=1, no_copy=False):
|
||||
"""
|
||||
Converts a farc dictionary (formatted like the dictionary returned by from_stream) to farc data and writes it to a stream.
|
||||
|
||||
Set no_copy to True for a speedup and memory usage reduction if you don't mind your input data being contaminated.
|
||||
"""
|
||||
|
||||
magic_str = data['farc_type']
|
||||
check_farc_type(magic_str)
|
||||
farc_type = _farc_types[magic_str]
|
||||
|
||||
if no_copy:
|
||||
files = data['files']
|
||||
else:
|
||||
files = deepcopy(data['files'])
|
||||
_prep_files(files, alignment, farc_type)
|
||||
|
||||
if farc_type['compression_support']:
|
||||
return farc_type['struct'].build_stream(dict(
|
||||
header_size=farc_type['fixed_header_size'] + _files_header_size_calc(files, farc_type),
|
||||
alignment=alignment,
|
||||
files=[dict(
|
||||
name=fname,
|
||||
pointer=info['pointer'],
|
||||
compressed_size=len(info['data_compressed']),
|
||||
uncompressed_size=len(info['data']),
|
||||
data=info['data_compressed']
|
||||
) for fname, info in files.items()]
|
||||
), stream)
|
||||
else:
|
||||
return farc_type['struct'].build_stream(dict(
|
||||
header_size=farc_type['fixed_header_size'] + _files_header_size_calc(files, farc_type),
|
||||
alignment=alignment,
|
||||
files=[dict(
|
||||
name=fname,
|
||||
pointer=info['pointer'],
|
||||
size=len(info['data']),
|
||||
data=info['data']
|
||||
) for fname, info in files.items()]
|
||||
), stream)
|
||||
|
||||
|
||||
def _parsed_to_dict(farcdata, farc_type):
|
||||
"""Converts the raw construct data to our standard dictionary format."""
|
||||
|
||||
files = {}
|
||||
|
||||
if farc_type['compression_support']:
|
||||
for f in farcdata['files']:
|
||||
if farc_type['compression_forced'] or (f['uncompressed_size'] != f['compressed_size']):
|
||||
data = gzip.decompress(f['data'])
|
||||
else:
|
||||
data = f['data']
|
||||
files[f['name']] = {'data': data}
|
||||
else:
|
||||
for f in farcdata['files']:
|
||||
data = f['data']
|
||||
files[f['name']] = {'data': data}
|
||||
|
||||
return {'farc_type': farcdata['signature'].decode('ascii'), 'files': files}
|
||||
|
||||
def from_bytes(b):
|
||||
"""Converts farc data from bytes to a dictionary."""
|
||||
|
||||
magic_str = b[:4].decode('ascii')
|
||||
check_farc_type(magic_str)
|
||||
farc_type = _farc_types[magic_str]
|
||||
|
||||
farcdata = farc_type['struct'].parse(b)
|
||||
return _parsed_to_dict(farcdata, farc_type)
|
||||
|
||||
def from_stream(s):
|
||||
"""Converts farc data from a stream to a dictionary."""
|
||||
|
||||
pos = s.tell()
|
||||
magic_str = s.read(4).decode('ascii')
|
||||
check_farc_type(magic_str)
|
||||
farc_type = _farc_types[magic_str]
|
||||
s.seek(pos)
|
||||
|
||||
farcdata = farc_type['struct'].parse_stream(s)
|
||||
return _parsed_to_dict(farcdata, farc_type)
|
||||
|
||||
|
||||
#test_farc = {'farc_type': 'FArc', 'files': {'aaa': {'data': b'test1'}, 'bbb': {'data': b'test2'}, 'ccc': {'data': b'aaaaaaaaaaaaaaaaaaaaaaaa'}}}
|
||||
test_farc = {'farc_type': 'FArC', 'files': {'aaa': {'data': b'test1'}, 'bbb': {'data': b'test2'}, 'ccc': {'data': b'aaaaaaaaaaaaaaaaaaaaaaaa'}}}
|
||||
#print (test_farc)
|
||||
|
||||
#test_bytes = to_bytes(test_farc, alignment=16)
|
||||
#print (test_bytes)
|
||||
#print (from_bytes(test_bytes))
|
||||
|
||||
#with open('test.farc', 'wb') as f:
|
||||
# to_stream(test_farc, f, alignment=16)
|
||||
#with open('test.farc', 'rb') as f:
|
||||
# print (from_stream(f))
|
||||
|
||||
#with open('shader_amd.farc', 'rb') as f:
|
||||
# shaderfarc = from_stream(f)
|
||||
#with open('shader_amd_out.farc', 'wb') as f:
|
||||
# to_stream(shaderfarc, f, alignment=16, no_copy=True)
|
||||
|
||||
#with open('shader_amd_compressed.farc', 'rb') as f:
|
||||
# shaderfarc = from_stream(f)
|
||||
#with open('shader_amd_out_compressed.farc', 'wb') as f:
|
||||
# to_stream(shaderfarc, f, alignment=1, no_copy=True)
|
||||
|
||||
#with open('fontmap.farc', 'rb') as f:
|
||||
# fontmapfarc = from_stream(f)
|
||||
#with open('fontmap_out.farc', 'wb') as f:
|
||||
# to_stream(fontmapfarc, f, alignment=1, no_copy=True)
|
||||
Binary file not shown.
Reference in New Issue
Block a user