#!/usr/bin/env python3 import os import sys try: assert(sys.version_info.major == 3) if sys.version_info.minor >= 9: # Python 3.9+ from typing import Generic, NewType, Optional, TypeVar, Union, final from collections.abc import Iterable Dict = dict List = list Tuple = tuple elif sys.version_info.minor >= 8: # Python [3.8, 3.9) from typing import Dict, List, Tuple, Generic, Iterable, NewType, Optional, TypeVar, Union, final elif (sys.version_info.minor >= 5) and (sys.version_info.micro >= 2): # Python [3.5.2, 3.8) from typing import Dict, List, Tuple, Generic, Iterable, NewType, Optional, TypeVar, Union final = lambda fun: fun # type: ignore elif sys.version_info.minor >= 5: # Python [3.5, 3.5.2) from typing import Dict, List, Tuple, Generic, Iterable, Optional, TypeVar, Union class GTDummy: def __getitem__(self, _): return self final = lambda fun: fun # type: ignore def NewType(_, b): return b # type: ignore else: # Python < 3.5 #print("Your Python version does not have the typing module, fallback to empty 'types'") # Dummies class GTDummy: def __getitem__(self, _): return self Dict = GTDummy() # type: ignore List = GTDummy() # type: ignore Generic = GTDummy() # type: ignore Iterable = GTDummy() # type: ignore Optional = GTDummy() # type: ignore def NewType(_, b): return b # type: ignore Tuple = GTDummy() # type: ignore def TypeVar(T): return object # type: ignore Union = GTDummy() # type: ignore except ImportError: print("It seems your Python version is quite broken...") assert(False) """ Generates all files in src/wrapped/generated === TL;DR: Automagically creates type definitions (/.F.+/ functions/typedefs...). All '//%' in the headers are used by the script. Reads each lines of each "_private.h" headers. For each of them: - If if starts with a #ifdef, #else, #ifndef, #endif, it memorizes which definition is required - If it starts with a "GO", it will do multiple things: - It memorizes the type used by the function (second macro argument) - It memorizes the type it is mapped to, if needed (eg, iFEvp is mapped to iFEp: the first "real" argument is dropped) - It checks if the type given (both original and mapped to) are valid - If the signature contains a 'E' but it is not a "GOM" command, it will throw an error - If the line starts with a '//%S', it will memorize a structure declaration. The structure of it is: "//%S " NOTE: Those structure letters are "fake types" that are accepted in the macros. After sorting the data, it generates: wrapper.c --------- (Private) type definitions (/.F.+_t/) Function definitions (/.F.+/ functions, that actually execute the function given as argument) isSimpleWrapper definition wrapper.h --------- Generic "wrapper_t" type definition Function declarations (/.F.+/ functions) *types.h -------- Local types definition, for the original signatures The SUPER() macro definition, used to generate and initialize the `*_my_t` library structure (TODO: also automate this declaration/definition? It would require more metadata, and may break sometime in the future due to the system changing...) *defs.h ------- Local `#define`s, for signature mapping *undefs.h --------- Local `#undefine`s, for signature mapping Example: ======== In wrappedtest_private.h: ---------------------- //%S X TestLibStructure ppu GO(superfunction, pFX) GOM(superFunction2, pFpX) Generated files: wrapper.c: [snippet] ---------- typedef void *(*pFppu_t)(void*, void*, uint32_t); typedef void *(*pFpppu_t)(void*, void*, void*, uint32_t); void pFppu(x64emu_t *emu, uintptr_t fcn) { pFppu_t *fn = (pFppu_t)fn; R_RAX=...; } void pFpppu(x64emu_t *emu, uintptr_t fcn) { pFpppu_t *fn = (pFpppu_t)fn; R_RAX=...; } int isSimpleWrapper(wrapper_t fun) { if (fcn == pFppu) return 1; if (fcn == pFpppu) return 1; return 0; } wrapper.h: [snippet] ---------- void pFppu(x64emu_t *emu, uintptr_t fcn); void pFpppu(x64emu_t *emu, uintptr_t fcn); int isSimpleWrapper(wrapper_t fun); wrappedtesttypes.h: ------------------- typedef void *(*pFpX_t)(void*, TestLibStructure); #define SUPER() \\ GO(superFunction2, pFpX) wrappedtestdefs.h: ------------------ #define pFX pFppu #define pFpX pFpppu wrappedtestundefs.h: -------------------- #undef pFX #undef pFpX """ # TODO: Add /.F.*A/ automatic generation (and suppression) class FunctionConvention(object): def __init__(self, ident: str, convname: str, valid_chars: List[str]) -> None: self.ident = ident self.name = convname self.values = valid_chars # Free letters: B FG J QR T XYZab e gh jk mno qrst xyz conventions = { 'F': FunctionConvention('F', "System V", ['E', 'v', 'c', 'w', 'i', 'I', 'C', 'W', 'u', 'U', 'f', 'd', 'D', 'K', 'l', 'L', 'p', 'V', 'O', 'S', 'N', 'M', 'H', 'P', 'A', 'x', 'X']), 'W': FunctionConvention('W', "Windows", ['E', 'v', 'c', 'w', 'i', 'I', 'C', 'W', 'u', 'U', 'f', 'd', 'K', 'l', 'L', 'p', 'V', 'O', 'S', 'N', 'M', 'P', 'A']) } sortedvalues = ['E', 'v', 'c', 'w', 'i', 'I', 'C', 'W', 'u', 'U', 'f', 'd', 'D', 'K', 'l', 'L', 'p', 'V', 'O', 'S', 'N', 'M', 'H', 'P', 'A', 'x', 'X', '0', '1'] assert(all(all(c not in conv.values[:i] and c in sortedvalues for i, c in enumerate(conv.values)) for conv in conventions.values())) class FunctionType(str): @staticmethod def validate(s: str, post: str) -> bool: if len(s) < 3: raise NotImplementedError("Type {0} too short{1}".format(s, post)) chk_type = s[0] + s[2:] if "E" in s: if ("E" in s[:2]) or ("E" in s[3:]): raise NotImplementedError( "emu64_t* not as the first parameter{0}".format(post)) if len(s) < 4: raise NotImplementedError("Type {0} too short{1}".format(s, post)) # TODO: change *FEv into true functions (right now they are redirected to *FE) #chk_type = s[0] + s[3:] if s[1] not in conventions: raise NotImplementedError("Bad middle letter {0}{1}".format(s[1], post)) return all(c in conventions[s[1]].values for c in chk_type) and (('v' not in chk_type[1:]) or (len(chk_type) == 2)) def get_convention(self) -> FunctionConvention: return conventions[self[1]] def splitchar(self) -> List[int]: """ splitchar -- Sorting key function for function signatures The longest strings are at the end, and for identical length, the string are sorted using a pseudo-lexicographic sort, where characters have a value of `values.index`. """ try: ret = [len(self), ord(self.get_convention().ident), self.get_convention().values.index(self[0])] for c in self[2:]: ret.append(self.get_convention().values.index(c)) return ret except ValueError as e: raise ValueError("Value is " + self) from e def __getitem__(self, i: Union[int, slice]) -> 'FunctionType': return FunctionType(super().__getitem__(i)) RedirectType = NewType('RedirectType', FunctionType) DefineType = NewType('DefineType', str) StructType = NewType('StructType', str) T = TypeVar('T') U = TypeVar('U') # TODO: simplify construction of this (add an 'insert' method?...) class CustOrderedDict(Generic[T, U], Iterable[T]): def __init__(self, dict: Dict[T, U], keys: List[T]): self.__indict__ = dict self.__inkeys__ = keys def __iter__(self): return iter(self.__inkeys__) def __getitem__(self, k: T) -> U: return self.__indict__[k] Filename = str ClausesStr = str @final class Define: name: DefineType inverted_: bool defines: List[DefineType] = [] def __init__(self, name: str, inverted_: bool): # All values of "name" are included in defines (throw otherwise) if DefineType(name) not in self.defines: raise KeyError(name) self.name = DefineType(name) self.inverted_ = inverted_ def copy(self) -> "Define": return Define(self.name, self.inverted_) def value(self) -> int: return self.defines.index(self.name)*2 + (1 if self.inverted_ else 0) def invert(self) -> None: """ invert -- Transform a `defined()` into a `!defined()` and vice-versa, in place. """ self.inverted_ = not self.inverted_ def inverted(self) -> "Define": """ inverted -- Transform a `defined()` into a `!defined()` and vice-versa, out-of-place. """ return Define(self.name, not self.inverted_) def __str__(self) -> str: if self.inverted_: return "!defined(" + self.name + ")" else: return "defined(" + self.name + ")" @final class Clause: defines: List[Define] def __init__(self, defines: Union[List[Define], str] = []): if isinstance(defines, str): if defines == "": self.defines = [] else: self.defines = list( map( lambda x: Define(x[9:-1] if x[0] == '!' else x[8:-1], x[0] == '!') , defines.split(" && ") ) ) else: self.defines = [d.copy() for d in defines] def copy(self) -> "Clause": return Clause(self.defines) def append(self, define: Define) -> None: self.defines.append(define) def invert_last(self) -> None: self.defines[-1].invert() def pop_last(self) -> None: if len(self.defines) > 0: self.defines.pop() def __str__(self) -> str: return " && ".join(map(str, self.defines)) @final class Clauses: """ Represent a list of clauses, aka a list of or-ed together and-ed "defined" conditions """ definess: List[Clause] def __init__(self, definess: Union[List[Clause], str] = []): if isinstance(definess, str): if definess == "()": self.definess = [] elif ") || (" in definess: self.definess = list(map(Clause, definess[1:-1].split(") || ("))) else: self.definess = [Clause(definess)] else: self.definess = definess[:] def copy(self) -> "Clauses": return Clauses(self.definess[:]) def add(self, defines: Clause) -> None: self.definess.append(defines) def splitdef(self) -> List[int]: """ splitdef -- Sorting key function for #ifdefs All #if defined(...) are sorted first by the number of clauses, then by the number of '&&' in each clause and then by the "key" of the tested names (left to right, inverted placed after non-inverted). """ ret = [len(self.definess)] for cunj in self.definess: ret.append(len(cunj.defines)) for cunj in self.definess: for d in cunj.defines: ret.append(d.value()) return ret def __str__(self) -> ClausesStr: if len(self.definess) == 1: return str(self.definess[0]) else: return "(" + ") || (".join(map(str, self.definess)) + ")" JumbledGlobals = Dict[ClausesStr, List[FunctionType]] JumbledRedirects = Dict[ClausesStr, Dict[RedirectType, FunctionType]] JumbledTypedefs = Dict[RedirectType, List[str]] JumbledStructures = Dict[str, Tuple[StructType, str]] JumbledStructUses = Dict[RedirectType, FunctionType] JumbledFilesSpecific = Dict[Filename, Tuple[JumbledTypedefs, JumbledStructures, JumbledStructUses]] SortedGlobals = CustOrderedDict[ClausesStr, List[FunctionType]] SortedRedirects = CustOrderedDict[ClausesStr, List[Tuple[RedirectType, FunctionType]]] SortedTypedefs = CustOrderedDict[RedirectType, List[str]] SortedStructUses = CustOrderedDict[RedirectType, FunctionType] SortedFilesSpecific = Dict[Filename, Tuple[SortedTypedefs, SortedStructUses]] def readFiles(files: Iterable[Filename]) -> Tuple[JumbledGlobals, JumbledRedirects, JumbledFilesSpecific]: """ readFiles This function is the one that parses the files. It returns the jumbled (gbl, redirects, {file: (typedefs, mystructs)}) tuple. """ # Initialize variables: gbl for all values, redirects for redirections gbl : JumbledGlobals = {} redirects: JumbledRedirects = {} filespec : JumbledFilesSpecific = {} functions: Dict[str, Filename] = {} halt_required = False # Is there a GO(*, .FE*) or similar in-batch error(s)? # First read the files inside the headers for filepath in files: filename: Filename = filepath.split("/")[-1] dependants: Clause = Clause() # typedefs is a list of all "*FE*" types for the current file # mystructs is a map of all char -> (structure C name, replacement) for structures typedefs : JumbledTypedefs = {} mystructs : JumbledStructures = {} mystructuses: JumbledStructUses = {} filespec[filename[:-10]] = (typedefs, mystructs, mystructuses) def add_symbol_name(funname: Union[str, None], funsname: Dict[ClausesStr, List[str]] = {"": []}): # Optional arguments are evaluated only once! nonlocal halt_required if funname is None: for k in funsname: if (k != "") and (len(funsname[k]) != 0): # Note: if this condition ever raises, check the wrapper pointed by it. # If you find no problem, comment the error below, add a "pass" line (so python is happy) # and open a ticket so I can fix this. raise NotImplementedError("Some functions are only implemented under one condition (probably) ({0}:{1})" .format(k, filename) + " [extra note in the script]\nProblematic function{}: {}".format(("" if len(funsname[k]) == 1 else "s"), funsname[k])) for f in funsname[k]: if f in ['_fini', '_init', '__bss_start', '__data_start', '_edata', '_end']: continue # Always allow those symbols [TODO: check if OK] if f in functions: # Check for resemblances between functions[f] and filename if filename.startswith(functions[f][:-12]) or functions[f].startswith(filename[:-12]): # Probably OK continue # Manual compatible libs detection match = lambda l, r: (filename[7:-10], functions[f][7:-10]) in [(l, r), (r, l)] if match("sdl1image", "sdl2image") \ or match("sdl1mixer", "sdl2mixer") \ or match("sdl1net", "sdl2net") \ or match("sdl1ttf", "sdl2ttf") \ or match("libGL", "libEGL") \ or match("libc", "tcmallocminimal") \ or match("libc", "tbbmallocproxy") \ or match("libc", "androidshmem") \ or match("tcmallocminimal", "tbbmallocproxy"): continue # Note: this test is very (too) simple. If it ever raises, comment # `halt_required = True` and open an issue. print("The function or data {0} is declared in multiple files ({1}/{2})" .format(f, functions[f], filename) + " [extra note in the script]") halt_required = True functions[f] = filename else: if funname == "": raise NotImplementedError("This function name (\"\") is suspicious... ({0})".format(filename)) l = len(dependants.defines) already_pst = funname in funsname[""] if l > 1: return elif l == 1: funsname.setdefault(str(dependants), []) already_pst = already_pst or (funname in funsname[str(dependants)]) if already_pst: print("Function or data {0} is duplicated! ({1})".format(funname, filename)) halt_required = True return if l == 1: s = str(dependants.defines[0].inverted()) if (s in funsname) and (funname in funsname[s]): funsname[s].remove(funname) funsname[""].append(funname) else: funsname[str(dependants)].append(funname) else: funsname[""].append(funname) with open(filepath, 'r') as file: line: str # Because VSCode really struggles with files for line in file: ln = line.strip() # If the line is a `#' line (#ifdef LD80BITS/#ifndef LD80BITS/header) if ln.startswith("#"): preproc_cmd = ln[1:].strip() try: if preproc_cmd.startswith("if defined(GO)"): continue #if defined(GO) && defined(GOM)... elif preproc_cmd.startswith("if !(defined(GO)"): continue #if !(defined(GO) && defined(GOM)...) elif preproc_cmd.startswith("error"): continue #error meh! elif preproc_cmd.startswith("endif"): dependants.pop_last() elif preproc_cmd.startswith("ifdef"): dependants.append(Define(preproc_cmd[5:].strip(), False)) elif preproc_cmd.startswith("ifndef"): dependants.append(Define(preproc_cmd[6:].strip(), True)) elif preproc_cmd.startswith("else"): dependants.invert_last() else: raise NotImplementedError("Unknown preprocessor directive: {0} ({1}:{2})".format( preproc_cmd.split(" ")[0], filename, line[:-1] )) except KeyError as k: raise NotImplementedError("Unknown key: {0} ({1}:{2})".format( k.args[0], filename, line[:-1] )) from k # If the line is a `GO...' line (GO/GOM/GO2/...)... elif ln.startswith("GO"): # ... then look at the second parameter of the line try: gotype = ln.split("(")[0].strip() funname = ln.split(",")[0].split("(")[1].strip() ln = ln.split(",")[1].split(")")[0].strip() if not filename.endswith("_genvate.h"): add_symbol_name(funname) except IndexError: raise NotImplementedError("Invalid GO command: {0}:{1}".format( filename, line[:-1] )) hasFlatStructure = False origLine = ln if not FunctionType.validate(ln, " ({0}:{1})".format(filename, line[:-1])): # This needs more work old = RedirectType(FunctionType(ln)) if (ln[0] in old.get_convention().values) \ and ('v' not in ln[2:]) \ and all((c in old.get_convention().values) or (c in mystructs) for c in ln[2:]): hasFlatStructure = True for sn in mystructs: ln = ln.replace(sn, mystructs[sn][1]) ln = ln[0] + 'F' + ln[2:] # In case a structure named 'F' is used mystructuses[RedirectType(FunctionType(origLine))] = FunctionType(ln) else: if old.get_convention().name == "System V": acceptables = ['0', '1'] + old.get_convention().values if any(c not in acceptables for c in ln[2:]): raise NotImplementedError("{0} ({1}:{2})".format(ln[2:], filename, line[:-1])) # Ok, this is acceptable: there is 0, 1 and/or void ln = ln[:2] + (ln[2:] .replace("v", "") # void -> nothing .replace("0", "i") # 0 -> integer .replace("1", "i")) # 1 -> integer assert(len(ln) >= 3) else: acceptables = ['0', '1', 'D', 'H'] + old.get_convention().values if any(c not in acceptables for c in ln[2:]): raise NotImplementedError("{0} ({1}:{2})".format(ln[2:], filename, line[:-1])) # Ok, this is acceptable: there is 0, 1 and/or void ln = ln[:2] + (ln[2:] .replace("v", "") # void -> nothing .replace("D", "p") # long double -> pointer .replace("H", "p") # unsigned __int128 -> pointer .replace("0", "i") # 0 -> integer .replace("1", "i")) # 1 -> integer assert(len(ln) >= 3) redirects.setdefault(str(dependants), {}) redirects[str(dependants)][old] = FunctionType(ln) origLine = ln # Simply append the function type if it's not yet existing gbl.setdefault(str(dependants), []) if ln not in gbl[str(dependants)]: gbl[str(dependants)].append(FunctionType(ln)) if origLine[2] == "E": if (gotype != "GOM") and (gotype != "GOWM"): if (gotype != "GO2") or not (line.split(',')[2].split(')')[0].strip().startswith('my_')): print("\033[91mThis is probably not what you meant!\033[m ({0}:{1})".format(filename, line[:-1])) halt_required = True if len(origLine) > 3: funtype = RedirectType(FunctionType(origLine[:2] + origLine[3:])) else: funtype = RedirectType(FunctionType(origLine[:2] + "v")) # filename isn't stored with the '_private.h' part typedefs.setdefault(funtype, []) typedefs[funtype].append(funname) elif (gotype == "GOM") or (gotype == "GOWM"): # OK on box64 for a GOM to not have emu... funtype = RedirectType(FunctionType(origLine)) typedefs.setdefault(funtype, []) typedefs[funtype].append(funname) # print("\033[94mAre you sure of this?\033[m ({0}:{1})".format(filename, line[:-1])) # halt_required = True elif hasFlatStructure: # Still put the type in typedefs, but don't add the function name typedefs.setdefault(RedirectType(FunctionType(origLine)), []) # If the line is a structure metadata information... # FIXME: what happens with e.g. a Windows function? elif ln.startswith("//%S"): metadata = [e for e in ln.split() if e] if len(metadata) != 4: raise NotImplementedError("Invalid structure metadata supply (too many fields) ({0}:{1})".format(filename, line[:-1])) if metadata[0] != "//%S": raise NotImplementedError("Invalid structure metadata supply (invalid signature) ({0}:{1})".format(filename, line[:-1])) if len(metadata[1]) != 1: # If you REALLY need it, consider opening a ticket # Before you do, consider that everything that is a valid in a C token is valid here too raise NotImplementedError("Structure names cannot be of length greater than 1 ({0}:{1})".format(filename, line[:-1])) if metadata[3] == "": # If you need this, please open an issue (this is never actually called, empty strings are removed) raise NotImplementedError("Invalid structure metadata supply (empty replacement) ({0}:{1})".format(filename, line[:-1])) if any(c not in conventions['F'].values for c in metadata[3]): # Note that replacement cannot be another structure type raise NotImplementedError("Invalid structure metadata supply (invalid replacement) ({0}:{1})".format(filename, line[:-1])) if metadata[1] in mystructs: raise NotImplementedError("Invalid structure nickname {0} (duplicate) ({1}/{2})".format(metadata[1], filename, line[:-1])) if (metadata[1] in conventions['F'].values) or (metadata[1] in ['0', '1']): raise NotImplementedError("Invalid structure nickname {0} (reserved) ({1}/{2})".format(metadata[1], filename, line[:-1])) # OK, add into the database mystructs[metadata[1]] = (StructType(metadata[2]), metadata[3]) # If the line contains any symbol name... elif ("GO" in ln) or ("DATA" in ln): # Probably "//GO(..., " or "DATA(...," at least try: funname = ln.split('(')[1].split(',')[0].strip() add_symbol_name(funname) except IndexError: # Oops, it wasn't... pass add_symbol_name(None) if halt_required: raise ValueError("Fix all previous errors before proceeding") if ("" not in gbl) or ("" not in redirects): print("\033[1;31mThere is suspiciously not many types...\033[m") print("Check the CMakeLists.txt file. If you are SURE there is nothing wrong" " (as a random example, `set()` resets the variable...), then comment out the following exit.") print("(Also, the program WILL crash later if you proceed.)") sys.exit(2) # Check what you did, not proceeding return gbl, redirects, filespec def sortArrays(gbl_tmp : JumbledGlobals, red_tmp : JumbledRedirects, filespec: JumbledFilesSpecific) \ -> Tuple[SortedGlobals, SortedRedirects, SortedFilesSpecific]: # Now, take all function types, and make a new table gbl_vals # This table contains all #if conditions for when a function type needs to # be generated. There is also a filter to avoid duplicate/opposite clauses. gbl_vals: Dict[FunctionType, Clauses] = {} for k1 in gbl_tmp: ks = Clause(k1) for v in gbl_tmp[k1]: if k1 == "": # Unconditionally define v gbl_vals[v] = Clauses() elif v in gbl_vals: if gbl_vals[v].definess == []: # v already unconditionally defined continue for other_key in gbl_vals[v].definess: for other_key_val in other_key.defines: if other_key_val not in ks.defines: # Not a duplicate or more specific case # (could be a less specific one though) break else: break else: gbl_vals[v].add(ks) else: gbl_vals[v] = Clauses([Clause(k1)]) for v in gbl_vals: strdefines = list(map(str, gbl_vals[v].definess)) for k2 in gbl_vals[v].definess: for i in range(len(k2.defines)): if " && ".join(map(str, k2.defines[:i] + [k2.defines[i].inverted()] + k2.defines[i+1:])) in strdefines: # Opposite clauses detected gbl_vals[v] = Clauses() break else: continue break # Now create a new gbl and gbl_idxs # gbl will contain the final version of gbl (without duplicates, based on # gbl_vals) # gbl_idxs will contain all #if clauses gbl: Dict[ClausesStr, List[FunctionType]] = {} gbl_idxs: List[ClausesStr] = [] for k1 in gbl_vals: clauses = gbl_vals[k1] key = str(clauses) gbl.setdefault(key, []) gbl[key].append(k1) if key not in gbl_idxs: gbl_idxs.append(key) # Sort the #if clauses as defined in `splitdef` gbl_idxs.sort(key=lambda c: Clauses(c).splitdef()) # This map will contain all additional function types that are "redirected" # to an already defined type (with some remapping). redirects_vals: Dict[Tuple[RedirectType, FunctionType], Clauses] = {} for k1 in red_tmp: ks = Clause(k1) for v in red_tmp[k1]: if k1 == "": # Unconditionally define v redirects_vals[(v, red_tmp[k1][v])] = Clauses() elif (v, red_tmp[k1][v]) in redirects_vals: if redirects_vals[(v, red_tmp[k1][v])].definess == []: # v already unconditionally defined continue for other_key in redirects_vals[(v, red_tmp[k1][v])].definess: for other_key_val in other_key.defines: if other_key_val not in ks.defines: # Not a duplicate or more specific case # (could be a less specific one though) break else: break else: redirects_vals[(v, red_tmp[k1][v])].add(ks) else: redirects_vals[(v, red_tmp[k1][v])] = Clauses([Clause(k1)]) # Also do the same trick as before (it also helps keep the order # in the file deterministic) redirects: Dict[ClausesStr, List[Tuple[RedirectType, FunctionType]]] = {} redirects_idxs: List[ClausesStr] = [] for k1, v in redirects_vals: clauses = redirects_vals[(k1, v)] key = str(clauses) redirects.setdefault(key, []) redirects[key].append((k1, v)) if key not in redirects_idxs: redirects_idxs.append(key) redirects_idxs.sort(key=lambda c: Clauses(c).splitdef()) # Sort the function types as defined in `splitchar` for k3 in gbl: gbl[k3].sort(key=FunctionType.splitchar) oldvals = { k: conventions[k].values for k in conventions } for k in conventions: conventions[k].values = sortedvalues for k3 in redirects: redirects[k3].sort(key=lambda v: v[0].splitchar() + v[1].splitchar()) for k in conventions: conventions[k].values = oldvals[k] sortedfilespec: SortedFilesSpecific = {} for fn in filespec: # Maybe do better? mystructs_vals: List[str] = sorted(filespec[fn][1].keys()) if mystructs_vals != []: for k in conventions: conventions[k].values = conventions[k].values + list(mystructs_vals) mytypedefs_vals: List[RedirectType] = sorted(filespec[fn][0].keys(), key=FunctionType.splitchar) sortedfilespec[fn] = ( CustOrderedDict(dict((v, sorted(filespec[fn][0][v])) for v in mytypedefs_vals), mytypedefs_vals), CustOrderedDict(filespec[fn][2], sorted(filespec[fn][2], key=FunctionType.splitchar)) ) if mystructs_vals != []: for k in conventions: conventions[k].values = conventions[k].values[:-len(mystructs_vals)] return CustOrderedDict(gbl, gbl_idxs), CustOrderedDict(redirects, redirects_idxs), sortedfilespec def checkRun(root: str, jumbled: JumbledFilesSpecific, \ gbls: SortedGlobals, redirects: SortedRedirects, filesspec: SortedFilesSpecific) -> Optional[str]: # Check if there was any new functions compared to last run functions_list: str = "" for k in gbls: for v in gbls[k]: functions_list = functions_list + "#" + k + " " + v + "\n" for k in redirects: for vr, vf in redirects[k]: functions_list = functions_list + "#" + k + " " + vr + " -> " + vf + "\n" for filename in sorted(filesspec.keys()): functions_list = functions_list + filename + ":\n" for st in sorted(jumbled[filename][1].keys()): functions_list = functions_list + \ "% " + st + " " + jumbled[filename][1][st][0] + " " + jumbled[filename][1][st][1] + "\n" for vr in filesspec[filename][0]: functions_list = functions_list + "- " + vr + ":\n" for fn in filesspec[filename][0][vr]: functions_list = functions_list + " - " + fn + "\n" for defined in filesspec[filename][1]: functions_list = functions_list + "% " + defined + "\n" # functions_list is a unique string, compare it with the last run try: last_run = "" with open(os.path.join(root, "src", "wrapped", "generated", "functions_list.txt"), 'r') as file: last_run = file.read() if last_run == functions_list: # Mark as OK for CMake with open(os.path.join(root, "src", "wrapped", "generated", "functions_list.txt"), 'w') as file: file.write(functions_list) return None except IOError: # The file does not exist yet, first run pass return functions_list def main(root: str, files: Iterable[Filename], ver: str): """ main -- The main function root: the root path (where the CMakeLists.txt is located) files: a list of files to parse (wrapped*.h) ver: version number """ # gbl_tmp: # "defined() && ..." -> [vFv, ...] # red_tmp: # "defined() && ..." -> [vFEv -> vFv, ...] # fsp_tmp: # "filename" -> ( # [vFEv -> fopen, ...], # [G -> ("SDL_J...", UU), ...], # [vFGppp -> vFUUppp, ...] # ) gbl_tmp: JumbledGlobals red_tmp: JumbledRedirects fsp_tmp: JumbledFilesSpecific gbl_tmp, red_tmp, fsp_tmp = readFiles(files) # gbls: sorted gbl_tmp # redirects: sorted red_tmp # filesspec: # "filename" -> ( # sorted [vFEv -> fopen, ...], # sorted [vFGppp -> vFUUppp, ...] # ) gbls : SortedGlobals redirects: SortedRedirects filesspec: SortedFilesSpecific gbls, redirects, filesspec = sortArrays(gbl_tmp, red_tmp, fsp_tmp) functions_list = checkRun(root, fsp_tmp, gbls, redirects, filesspec) if functions_list is None: print("Detected same build as last run, skipping") return 0 # Detect simple wrappings simple_wraps: Dict[ClausesStr, List[Tuple[FunctionType, int]]] = {} allowed_conv_ident = "F" allowed_conv = conventions[allowed_conv_ident] # H could be allowed maybe? allowed_simply: str = "v" allowed_regs : str = "cCwWiuIUlLp" allowed_fpr : str = "fd" # Detect functions which return in an x87 register retx87_wraps: Dict[ClausesStr, List[FunctionType]] = {} return_x87: str = "DK" # Sanity checks forbidden_simple: str = "EDKVOSNMHPAxX" assert(len(allowed_simply) + len(allowed_regs) + len(allowed_fpr) + len(forbidden_simple) == len(allowed_conv.values)) assert(all(c not in allowed_regs for c in allowed_simply)) assert(all(c not in allowed_simply + allowed_regs for c in allowed_fpr)) assert(all(c not in allowed_simply + allowed_regs + allowed_fpr for c in forbidden_simple)) assert(all(c in allowed_simply + allowed_regs + allowed_fpr + forbidden_simple for c in allowed_conv.values)) assert(all(c in allowed_conv.values for c in return_x87)) def check_simple(v: FunctionType) -> Optional[int]: regs_count: int = 0 fpr_count : int = 0 if v.get_convention() is not allowed_conv: return None if v[0] in forbidden_simple: return None for c in v[2:]: if c in allowed_regs: regs_count = regs_count + 1 elif c in allowed_fpr: fpr_count = fpr_count + 1 elif c in allowed_simply: continue else: return None # No character in forbidden_simply if (regs_count <= 6) and (fpr_count <= 8): # All checks passed! ret_val = 1 + fpr_count if v[0] in allowed_fpr: ret_val = -ret_val return ret_val else: # Too many arguments... return None # Only search in real wrappers (mapped ones are nearly always not simple) for k in gbls: tmp = [ (v, ret_val) for v, ret_val in map(lambda v: (v, check_simple(v)), gbls[k]) if ret_val is not None ] if tmp: simple_wraps[k] = tmp simple_idxs = sorted(simple_wraps.keys(), key=lambda x: Clauses(x).splitdef()) def check_return_x87(v: FunctionType) -> bool: return v[0] in return_x87 for k in gbls: tmp = [v for v in gbls[k] if check_return_x87(v)] if tmp: retx87_wraps[k] = tmp retx87_idxs = sorted(retx87_wraps.keys(), key=lambda x: Clauses(x).splitdef()) # Now the files rebuilding part # File headers and guards files_header = { "wrapper.c": """ #include #include #include #include "wrapper.h" #include "emu/x64emu_private.h" #include "emu/x87emu_private.h" #include "regs.h" #include "x64emu.h" #define COMPLEX_IMPL #include "complext.h" extern void* my__IO_2_1_stdin_ ; extern void* my__IO_2_1_stdout_; extern void* my__IO_2_1_stderr_; static void* io_convert(void* v) {lbr} if(!v) return v; if(v == my__IO_2_1_stdin_) return stdin; if(v == my__IO_2_1_stdout_) return stdout; if(v == my__IO_2_1_stderr_) return stderr; return v; {rbr} #define ST0val ST0.d int of_convert(int); """, "wrapper.h": """ #ifndef __WRAPPER_H_ #define __WRAPPER_H_ #include #include #include "complext.h" typedef struct x64emu_s x64emu_t; // the generic wrapper pointer functions typedef void (*wrapper_t)(x64emu_t* emu, uintptr_t fnc); // list of defined wrapper // E = current x86emu struct // v = void // C = unsigned byte c = char // W = unsigned short w = short // u = uint32, i = int32 // U = uint64, I = int64 // L = unsigned long, l = signed long (long is an int with the size of a pointer) // H = Huge 128bits value/struct // p = pointer, P = void* on the stack // f = float, d = double, D = long double, K = fake long double // V = vaargs // O = libc O_ flags bitfield // o = stdout // S = _IO_2_1_stdXXX_ pointer (or FILE*) // N = ... automatically sending 1 arg // M = ... automatically sending 2 args // A = va_list // 0 = constant 0, 1 = constant 1 // x = float complex // X = double complex """, "fntypes.h": """ #ifndef __{filename}TYPES_H_ #define __{filename}TYPES_H_ #ifndef LIBNAME #error You should only #include this file inside a wrapped*.c file #endif #ifndef ADDED_FUNCTIONS #define ADDED_FUNCTIONS() #endif """, "fndefs.h": """ #ifndef __{filename}DEFS_H_ #define __{filename}DEFS_H_ """, "fnundefs.h": """ #ifndef __{filename}UNDEFS_H_ #define __{filename}UNDEFS_H_ """ } files_guard = { "wrapper.c": """ """, "wrapper.h": """ int isSimpleWrapper(wrapper_t fun); #endif // __WRAPPER_H_ """, "fntypes.h": """ #endif // __{filename}TYPES_H_ """, "fndefs.h": """ #endif // __{filename}DEFS_H_ """, "fnundefs.h": """ #endif // __{filename}UNDEFS_H_ """ } banner = "/********************************************************" + ('*'*len(ver)) + "***\n" \ " * File automatically generated by rebuild_wrappers.py (v" + ver + ") *\n" \ " ********************************************************" + ('*'*len(ver)) + "***/\n" trim = lambda string: '\n'.join(line[2:] for line in string.splitlines())[1:] # Yes, the for loops are inversed. This is because both dicts should have the same keys. for fhdr in files_guard: files_header[fhdr] = banner + trim(files_header[fhdr]) for fhdr in files_header: files_guard[fhdr] = trim(files_guard[fhdr]) # Rewrite the wrapper.c file: # i and u should only be 32 bits td_types = { # E v c w i I C W u U f d D K l L p V O S N M H P A x X 'F': ["x64emu_t*", "void", "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t", "float", "double", "long double", "double", "intptr_t", "uintptr_t", "void*", "void*", "int32_t", "void*", "...", "...", "unsigned __int128", "void*", "void*", "complexf_t", "complex_t"], # E v c w i I C W u U f d K l L p V O S N M P A 'W': ["x64emu_t*", "void", "int8_t", "int16_t", "int32_t", "int64_t", "uint8_t", "uint16_t", "uint32_t", "uint64_t", "float", "double", "double", "intptr_t", "uintptr_t", "void*", "void*", "int32_t", "void*", "...", "...", "void*", "void*"] } assert(all(k in conventions for k in td_types)) for k in conventions: if len(conventions[k].values) != len(td_types[k]): raise NotImplementedError("len(values) = {lenval} != len(td_types) = {lentypes}".format(lenval=len(conventions[k].values), lentypes=len(td_types[k]))) def generate_typedefs(arr: Iterable[FunctionType], file) -> None: for v in arr: file.write("typedef " + td_types[v.get_convention().ident][v.get_convention().values.index(v[0])] + " (*" + v + "_t)" + "(" + ', '.join(td_types[v.get_convention().ident][v.get_convention().values.index(t)] for t in v[2:]) + ");\n") with open(os.path.join(root, "src", "wrapped", "generated", "wrapper.c"), 'w') as file: file.write(files_header["wrapper.c"].format(lbr="{", rbr="}", version=ver)) # First part: typedefs for k in gbls: if k != str(Clauses()): file.write("\n#if " + k + "\n") generate_typedefs(gbls[k], file) if k != str(Clauses()): file.write("#endif\n") file.write("\n") # Next part: function definitions # Helper variables # Return type template vals = { conventions['F']: [ "\n#error Invalid return type: emulator\n", # E "fn({0});", # v "R_RAX=fn({0});", # c "R_RAX=fn({0});", # w "R_RAX=(int32_t)fn({0});", # i "R_RAX=(int64_t)fn({0});", # I "R_RAX=(unsigned char)fn({0});", # C "R_RAX=(unsigned short)fn({0});", # W "R_RAX=(uint32_t)fn({0});", # u "R_RAX=fn({0});", # U "emu->xmm[0].f[0]=fn({0});", # f "emu->xmm[0].d[0]=fn({0});", # d "long double ld=fn({0}); fpu_do_push(emu); ST0val = ld;", # D "double db=fn({0}); fpu_do_push(emu); ST0val = db;", # K "R_RAX=(intptr_t)fn({0});", # l "R_RAX=(uintptr_t)fn({0});", # L "R_RAX=(uintptr_t)fn({0});", # p "\n#error Invalid return type: va_list\n", # V "\n#error Invalid return type: at_flags\n", # O "\n#error Invalid return type: _io_file*\n", # S "\n#error Invalid return type: ... with 1 arg\n", # N "\n#error Invalid return type: ... with 2 args\n", # M "unsigned __int128 u128 = fn({0}); R_RAX=(u128&0xFFFFFFFFFFFFFFFFL); R_RDX=(u128>>64)&0xFFFFFFFFFFFFFFFFL;", # H "\n#error Invalid return type: pointer in the stack\n", # P "\n#error Invalid return type: va_list\n", # A 'from_complexf(emu, fn({0})));', # x 'from_complex(emu, fn({0})));' # X ], conventions['W']: [ "\n#error Invalid return type: emulator\n", # E "fn({0});", # v "R_RAX=fn({0});", # c "R_RAX=fn({0});", # w "R_RAX=(int32_t)fn({0});", # i "R_RAX=(int64_t)fn({0});", # I "R_RAX=(unsigned char)fn({0});", # C "R_RAX=(unsigned short)fn({0});", # W "R_RAX=(uint32_t)fn({0});", # u "R_RAX=fn({0});", # U "emu->xmm[0].f[0]=fn({0});", # f "emu->xmm[0].d[0]=fn({0});", # d "double db=fn({0}); fpu_do_push(emu); ST0val = db;", # K "R_RAX=(intptr_t)fn({0});", # l "R_RAX=(uintptr_t)fn({0});", # L "R_RAX=(uintptr_t)fn({0});", # p "\n#error Invalid return type: va_list\n", # V "\n#error Invalid return type: at_flags\n", # O "\n#error Invalid return type: _io_file*\n", # S "\n#error Invalid return type: ... with 1 arg\n", # N "\n#error Invalid return type: ... with 2 args\n", # M "\n#error Invalid return type: pointer in the stack\n", # P "\n#error Invalid return type: va_list\n", # A ] } # vreg: value is in a general register # E v c w i I C W u U f d D K l L p V O S N M H P A x X vreg = [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 2, 2, 0, 1, 0, 0] # vxmm: value is in a XMM register # E v c w i I C W u U f d D K l L p V O S N M H P A x X vxmm = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2] # vother: value is elsewere # E v c w i I C W u U f d D K l L p V O S N M H P A x X vother = [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0] # vstack: value is on the stack (or out of register) # E v c w i I C W u U f d D K l L p V O S N M H P A x X vstack = [0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 0, 1, 1, 1, 2, 2, 1, 1, 1, 2] arg_r = [ "", # E "", # v "(int8_t){p}, ", # c "(int16_t){p}, ", # w "(int32_t){p}, ", # i "(int64_t){p}, ", # I "(uint8_t){p}, ", # C "(uint16_t){p}, ", # W "(uint32_t){p}, ", # u "(uint64_t){p}, ", # U "", # f "", # d "", # D "", # K "(intptr_t){p}, ", # l "(uintptr_t){p}, ", # L "(void*){p}, ", # p "", # V "of_convert((int32_t){p}), ", # O "io_convert((void*){p}), ", # S "(void*){p}, ", # N "(void*){p}, ", # M "\n#error Use pp instead\n", # H "", # P "(void*){p}, ", # A "", # x "", # X ] arg_x = [ "", # E "", # v "", # c "", # w "", # i "", # I "", # C "", # W "", # u "", # U "emu->xmm[{p}].f[0], ", # f "emu->xmm[{p}].d[0], ", # d "", # D "", # K "", # l "", # L "", # p "", # V "", # O "", # S "", # N "", # M "", # H "", # P "", # A "to_complexf(emu, {p}),",# x "to_complex(emu, {p})," # X ] arg_o = [ "emu, ", # E "", # v "", # c "", # w "", # i "", # I "", # C "", # W "", # u "", # U "", # f "", # d "", # D "", # K "", # l "", # L "", # p "(void*)(R_RSP + {p}), ", # V "", # O "", # S "", # N "", # M "", # H "", # P "", # A "", # x "", # X ] arg_s = [ "", # E "", # v "*(int8_t*)(R_RSP + {p}), ", # c "*(int16_t*)(R_RSP + {p}), ", # w "*(int32_t*)(R_RSP + {p}), ", # i "*(int64_t*)(R_RSP + {p}), ", # I "*(uint8_t*)(R_RSP + {p}), ", # C "*(uint16_t*)(R_RSP + {p}), ", # W "*(uint32_t*)(R_RSP + {p}), ", # u "*(uint64_t*)(R_RSP + {p}), ", # U "*(float*)(R_RSP + {p}), ", # f "*(double*)(R_RSP + {p}), ", # d "LD2localLD((void*)(R_RSP + {p})), ", # D "FromLD((void*)(R_RSP + {p})), ", # K "*(intptr_t*)(R_RSP + {p}), ", # l "*(uintptr_t*)(R_RSP + {p}), ", # L "*(void**)(R_RSP + {p}), ", # p "", # V "of_convert(*(int32_t*)(R_RSP + {p})), ", # O "io_convert(*(void**)(R_RSP + {p})), ", # S "*(void**)(R_RSP + {p}), ", # N "*(void**)(R_RSP + {p}),*(void**)(R_RSP + {p} + 8), ", # M "*(unsigned __int128)(R_RSP + {p}), ", # H "*(void**)(R_RSP + {p}), ", # P "*(void**)(R_RSP + {p}), ", # A "*(complexf_t*)(R_RSP + {p}), ", # x "*(complex_t*)(R_RSP + {p}), ", # X ] # Asserts for k in conventions: assert all(v in conventions['F'].values for v in conventions[k].values), "a convention is not a subset of System V" assert all(vr == vs for (vr, vs) in zip(vreg, vstack) if vr != 0), "vreg and vstack are inconsistent" assert all(vx == vs for (vx, vs) in zip(vxmm, vstack) if vx != 0), "vxmm and vstack are inconsistent" assert all((vo == 0) == (vs != 0) for (vo, vs) in zip(vother, vstack)), "vother and vstack are inconsistent" if len(conventions['F'].values) != len(vstack): raise NotImplementedError("len(values) = {lenval} != len(vstack) = {lenvstack}".format(lenval=len(conventions['F'].values), lenvstack=len(vstack))) if len(conventions['F'].values) != len(vreg): raise NotImplementedError("len(values) = {lenval} != len(vreg) = {lenvreg}".format(lenval=len(conventions['F'].values), lenvreg=len(vreg))) if len(conventions['F'].values) != len(vxmm): raise NotImplementedError("len(values) = {lenval} != len(vxmm) = {lenvxmm}".format(lenval=len(conventions['F'].values), lenvxmm=len(vxmm))) if len(conventions['F'].values) != len(vother): raise NotImplementedError("len(values) = {lenval} != len(vother) = {lenvother}".format(lenval=len(conventions['F'].values), lenvother=len(vother))) if len(conventions['F'].values) != len(arg_s): raise NotImplementedError("len(values) = {lenval} != len(arg_s) = {lenargs}".format(lenval=len(conventions['F'].values), lenargs=len(arg_s))) if len(conventions['F'].values) != len(arg_r): raise NotImplementedError("len(values) = {lenval} != len(arg_r) = {lenargr}".format(lenval=len(conventions['F'].values), lenargr=len(arg_r))) if len(conventions['F'].values) != len(arg_x): raise NotImplementedError("len(values) = {lenval} != len(arg_x) = {lenargx}".format(lenval=len(conventions['F'].values), lenargx=len(arg_x))) if len(conventions['F'].values) != len(arg_o): raise NotImplementedError("len(values) = {lenval} != len(arg_o) = {lenargo}".format(lenval=len(conventions['F'].values), lenargo=len(arg_o))) for k in conventions: c = conventions[k] if c not in vals: raise NotImplementedError("[{k}]values not in vals".format(k=k, lenval=len(c.values), lenvals=len(vals[c]))) if len(c.values) != len(vals[c]): raise NotImplementedError("len([{k}]values) = {lenval} != len(vals[...]) = {lenvals}".format(k=k, lenval=len(c.values), lenvals=len(vals[c]))) # When arg_* is not empty, v* should not be 0 if any(map(lambda v, a: (a != "") and (v == 0), vstack, arg_s)): raise NotImplementedError("Something in the stack has a null offset and a non-empty arg string") if any(map(lambda v, a: (a != "") and (v == 0), vreg, arg_r)): raise NotImplementedError("Something in the stack has a null offset and a non-empty arg string") if any(map(lambda v, a: (a != "") and (v == 0), vxmm, arg_x)): raise NotImplementedError("Something in the stack has a null offset and a non-empty arg string") if any(map(lambda v, a: (a != "") and (v == 0), vother, arg_o)): raise NotImplementedError("Something in the stack has a null offset and a non-empty arg string") # Everything is either in the stack or somewhere else, it cannot be in a GPr and in an XMMr, etc if any(map(lambda o, s: (o == 0) == (s == 0), vother, vstack)): raise NotImplementedError("Something cannot be in exactly one of the stack and somewhere else") if any(map(lambda r, x: (r > 0) and (x > 0), vreg, vxmm)): raise NotImplementedError("Something can be in both a general purpose register and in an XMM register") if any(map(lambda r, s: (r > 0) and (s == 0), vreg, vstack)): raise NotImplementedError("Something can be in a general purpose register but not in the stack") if any(map(lambda x, s: (x > 0) and (s == 0), vxmm, vstack)): raise NotImplementedError("Something can be in an XMM register but not in the stack") # Helper functions to write the function definitions systemVconv = conventions['F'] def function_args_systemV(args: FunctionType, d: int = 8, r: int = 0, x: int = 0) -> str: # args: string of argument types # d: delta (in the stack) # r: general register no # x: XMM register no if len(args) == 0: return "" # Redirections if args[0] == "0": return "0, " + function_args_systemV(args[1:], d, r, x) elif args[0] == "1": return "1, " + function_args_systemV(args[1:], d, r, x) idx = systemVconv.values.index(args[0]) # Name of the registers reg_arg = ["R_RDI", "R_RSI", "R_RDX", "R_RCX", "R_R8", "R_R9"] if (r < len(reg_arg)) and (vreg[idx] > 0): ret = "" for _ in range(vreg[idx]): # There may be values in multiple registers if r < len(reg_arg): # Value is in a general register ret = ret + arg_r[idx].format(p=reg_arg[r]) r = r + 1 else: # Remaining is in the stack ret = ret + arg_s[idx].format(p=d) d = d + 8 return ret + function_args_systemV(args[1:], d, r, x) elif (x < 8) and (vxmm[idx] > 0): # Value is in an XMM register return arg_x[idx].format(p=x) + function_args_systemV(args[1:], d, r, x+1) elif vstack[idx] > 0: # Value is in the stack return arg_s[idx].format(p=d) + function_args_systemV(args[1:], d+8*vstack[idx], r, x) else: # Value is somewhere else return arg_o[idx].format(p=d) + function_args_systemV(args[1:], d, r, x) # windowsconv = conventions['W'] def function_args_windows(args: FunctionType, d: int = 40, r: int = 0) -> str: # args: string of argument types # d: delta (in the stack) # r: general register no # We can re-use vstack to know if we need to put a pointer or the value if len(args) == 0: return "" # Redirections if args[0] == "0": return "0, " + function_args_windows(args[1:], d, r) elif args[0] == "1": return "1, " + function_args_windows(args[1:], d, r) idx = systemVconv.values.index(args[0]) # Little hack to be able to re-use # Name of the registers reg_arg = ["R_RCX", "R_RDX", "R_R8", "R_R9"] if (r < len(reg_arg)) and (vstack[idx] == 1): # We use a register if vreg[idx] == 1: # Value is in a general register return arg_r[idx].format(p=reg_arg[r]) + function_args_windows(args[1:], d, r+1) else: # Remaining is in an XMM register return arg_x[idx].format(p=r) + function_args_windows(args[1:], d, r+1) elif vstack[idx] > 0: # Value is in the stack return arg_s[idx].format(p=d) + function_args_windows(args[1:], d+8*vstack[idx], r) else: # Value is somewhere else return arg_o[idx].format(p=d) + function_args_windows(args[1:], d, r) def function_writer(f, N: FunctionType, W: str) -> None: # Write to f the function type N (real type W) f.write("void {0}(x64emu_t *emu, uintptr_t fcn) {2} {1} fn = ({1})fcn; ".format(N, W, "{")) # Generic function conv = N.get_convention() if conv is systemVconv: f.write(vals[conv][conv.values.index(N[0])].format(function_args_systemV(N[2:])[:-2]) + " }\n") else: f.write(vals[conv][conv.values.index(N[0])].format(function_args_windows(N[2:])[:-2]) + " }\n") for k in gbls: if k != str(Clauses()): file.write("\n#if " + k + "\n") for v in gbls[k]: if v == FunctionType("vFv"): # Suppress all warnings... file.write("void vFv(x64emu_t *emu, uintptr_t fcn) { vFv_t fn = (vFv_t)fcn; fn(); (void)emu; }\n") else: function_writer(file, v, v + "_t") if k != str(Clauses()): file.write("#endif\n") file.write("\n") for k in redirects: if k != str(Clauses()): file.write("\n#if " + k + "\n") for vr, vf in redirects[k]: function_writer(file, vr, vf + "_t") if k != str(Clauses()): file.write("#endif\n") # Write the isSimpleWrapper function file.write("\nint isSimpleWrapper(wrapper_t fun) {\n") for k in simple_idxs: if k != str(Clauses()): file.write("#if " + k + "\n") for vf, val in simple_wraps[k]: file.write("\tif (fun == &" + vf + ") return " + str(val) + ";\n") if k != str(Clauses()): file.write("#endif\n") file.write("\treturn 0;\n}\n") # Write the isRetX87Wrapper function file.write("\nint isRetX87Wrapper(wrapper_t fun) {\n") for k in retx87_idxs: if k != str(Clauses()): file.write("#if " + k + "\n") for vf in retx87_wraps[k]: file.write("\tif (fun == &" + vf + ") return 1;\n") if k != str(Clauses()): file.write("#endif\n") file.write("\treturn 0;\n}\n") file.write(files_guard["wrapper.c"].format(lbr="{", rbr="}", version=ver)) # Rewrite the wrapper.h file: with open(os.path.join(root, "src", "wrapped", "generated", "wrapper.h"), 'w') as file: file.write(files_header["wrapper.h"].format(lbr="{", rbr="}", version=ver)) # Normal function types for k in gbls: if k != str(Clauses()): file.write("\n#if " + k + "\n") for v in gbls[k]: file.write("void " + v + "(x64emu_t *emu, uintptr_t fnc);\n") if k != str(Clauses()): file.write("#endif\n") file.write("\n") # Redirects for k in redirects: if k != str(Clauses()): file.write("\n#if " + k + "\n") for vr, _ in redirects[k]: file.write("void " + vr + "(x64emu_t *emu, uintptr_t fnc);\n") if k != str(Clauses()): file.write("#endif\n") file.write(files_guard["wrapper.h"].format(lbr="{", rbr="}", version=ver)) # Rewrite the *types.h files: for k in conventions: td_types[k][conventions[k].values.index('A')] = "va_list" td_types[k][conventions[k].values.index('V')] = "..." orig_val_len = {k: len(conventions[k].values) for k in conventions} for fn in filesspec: for strc in fsp_tmp[fn][1]: for k in conventions: conventions[k].values.append(strc) td_types[k].append(fsp_tmp[fn][1][strc][0]) with open(os.path.join(root, "src", "wrapped", "generated", fn + "types.h"), 'w') as file: file.write(files_header["fntypes.h"].format(lbr="{", rbr="}", version=ver, filename=fn)) generate_typedefs(filesspec[fn][0], file) file.write("\n#define SUPER() ADDED_FUNCTIONS()") for r in filesspec[fn][0]: for f in filesspec[fn][0][r]: file.write(" \\\n\tGO({0}, {1}_t)".format(f, r)) file.write("\n") file.write(files_guard["fntypes.h"].format(lbr="{", rbr="}", version=ver, filename=fn)) with open(os.path.join(root, "src", "wrapped", "generated", fn + "defs.h"), 'w') as file: file.write(files_header["fndefs.h"].format(lbr="{", rbr="}", version=ver, filename=fn)) for defined in filesspec[fn][1]: file.write("#define {defined} {define}\n".format(defined=defined, define=filesspec[fn][1][defined])) file.write(files_guard["fndefs.h"].format(lbr="{", rbr="}", version=ver, filename=fn)) with open(os.path.join(root, "src", "wrapped", "generated", fn + "undefs.h"), 'w') as file: file.write(files_header["fnundefs.h"].format(lbr="{", rbr="}", version=ver, filename=fn)) for defined in filesspec[fn][1]: file.write("#undef {defined}\n".format(defined=defined)) file.write(files_guard["fnundefs.h"].format(lbr="{", rbr="}", version=ver, filename=fn)) for k in conventions: conventions[k].values = conventions[k].values[:orig_val_len[k]] td_types[k] = td_types[k][:orig_val_len[k]] # Save the string for the next iteration, writing was successful with open(os.path.join(root, "src", "wrapped", "generated", "functions_list.txt"), 'w') as file: file.write(functions_list) return 0 if __name__ == '__main__': limit = [] for i, v in enumerate(sys.argv): if v == "--": limit.append(i) Define.defines = list(map(DefineType, sys.argv[2:limit[0]])) if main(sys.argv[1], sys.argv[limit[0]+1:], "2.2.0.18") != 0: exit(2) exit(0)