tp/tools/libdol2asm/util.py

277 lines
6.3 KiB
Python

import os
import re
import asyncio
import click
import sys
import numpy
from pathlib import Path
from functools import partial, wraps
from typing import Dict, Tuple
from decimal import getcontext, Decimal
def wrap(func):
@wraps(func)
async def run(*args, loop=None, executor=None, **kwargs):
if loop is None:
loop = asyncio.get_event_loop()
pfunc = partial(func, *args, **kwargs)
return await loop.run_in_executor(executor, pfunc)
return run
def chunks(lst, n):
for i in range(0, len(lst), n):
yield lst[i:i + n]
def mapOverlap(data, n):
r = [None] * n
for x in data:
r = r[1:] + [x]
yield r
for _ in range(n - 1):
r = r[1:] + [None]
yield r
def _itersplit(l, splitters):
current = []
for item in l:
if item in splitters:
yield current
current = []
else:
current.append(item)
yield current
def magicsplit(l, *splitters):
return [subl for subl in _itersplit(l, splitters)]
#
#
#
class PathPath(click.Path):
"""A Click path argument that returns a pathlib Path, not a string"""
def convert(self, value, param, ctx):
return Path(super().convert(value, param, ctx))
#
#
#
register_r_re = re.compile(r'r([0-9]+)')
register_f_re = re.compile(r'r([0-9]+)')
register_qr_re = re.compile(r'r([0-9]+)')
def is_register(name, regex, count):
match = regex.fullmatch(name)
if not match:
return False
try:
reg = int(match.group(1))
return reg < count
except:
return False
def is_weird(name):
return (is_register(name, register_r_re, 32) or
is_register(name, register_f_re, 32) or
is_register(name, register_qr_re, 8) or
"@" in name or "\\" in name or "." in name or "*" in name or "-" in name or "$" in name or "?" in name)
literal_re = re.compile(r'\@([0-9]+)')
def literal_name(name):
""" Convert symbols in the form of "@number" to "LIT_number" """
match = literal_re.fullmatch(name)
if not match:
return None
return "lit_" + match.group(1)
def escape_name(n):
"""
Determine if the name is "safe" (name does not use any weird characters).
Special names, such as, @stringBase0 or @NUMBER is converted to a more readable name.
"""
if not n.name:
return
if "@" in n.name:
if n.name.endswith("@stringBase0"):
rname = n.name.replace("@stringBase0", "stringBase0")
n.override_name = rname
return
lname = literal_name(n.name)
if lname:
n.override_name = lname
return
if is_weird(n.name):
return
if "<" in n.name or ">" in n.name or "," in n.name:
return
n.is_name_safe = True
#
#
#
def mkdir(filepath):
path = Path("/".join(filepath.split("/")[:-1]))
path.mkdir(parents=True, exist_ok=True)
def _create_dirs_for_file(filepath):
parent = filepath.parent
if parent.exists():
return
parent.mkdir(parents=True, exist_ok=True)
create_dirs_for_file = wrap(_create_dirs_for_file)
async def wait_all(tasks):
await asyncio.gather(*tasks)
class CheckPathException(Exception):
...
def check_file(base, name):
new_path = base.joinpath(name)
if not new_path.is_file() or not new_path.exists():
raise CheckPathException(
f"file '{name}' was not found in the game directory ('{base}')")
return new_path
def check_dir(base, name):
new_path = base.joinpath(name)
if new_path.is_file() or not new_path.exists():
raise CheckPathException(
f"path '{name}' was not found in the game directory ('{base}')")
return new_path
def get_files_with_ext(path, ext):
return [x for x in path.glob(f"*{ext}") if x.is_file()]
#
#
#
def bytes2float32(data):
if len(data) < 4:
return None
result = numpy.frombuffer(data[0:4][::-1], dtype='float32')
if len(result) > 0:
return result[0]
else:
return None
def bytes2float64(data):
if len(data) < 8:
return None
result = numpy.frombuffer(data[0:8][::-1], dtype='float64')
if len(result) > 0:
return result[0]
else:
return None
def is_nice_float(f):
try:
if int(f*1000) == f*1000 and abs(f) <= 10:
return True
if int(f*100) == f*100 and abs(f) <= 100000:
return True
if int(f*10) == f*10 and abs(f) <= 10000000:
return True
if int(f) == f and abs(f) <= 1000000000:
return True
except:
return False
return False
def is_nice_float32(f):
return is_nice_float(f)
def is_nice_float64(f):
return is_nice_float(f)
float32_exact: Dict[numpy.float32, Tuple[int, int]] = {}
float64_exact: Dict[numpy.float64, Tuple[int, int]] = {}
getcontext().prec = 64
for i in range(1, 128):
for j in range(1, 128):
if i % j == 0:
continue
d = Decimal(i)/Decimal(j)
f = numpy.float32(d)
if f"{f}" != f"{d}":
if not f in float32_exact:
float32_exact[f] = (i, j)
for i in range(1, 128):
for j in range(1, 128):
if i % j == 0:
continue
d = Decimal(i)/Decimal(j)
f = numpy.float64(d)
if f"{f}" != f"{d}":
if not f in float64_exact:
float64_exact[f] = (i, j)
for value, numbers in list(float32_exact.items()):
float32_exact[-value] = (-numbers[0], numbers[1])
for value, numbers in list(float64_exact.items()):
float64_exact[-value] = (-numbers[0], numbers[1])
def special_float32(value):
if numpy.isposinf(value):
return "FLOAT_INF"
if numpy.isneginf(value):
return "-FLOAT_INF"
if numpy.isnan(value):
return "FLOAT_NAN"
return None
def special_float64(value):
if numpy.isposinf(value):
return "DOUBLE_INF"
if numpy.isneginf(value):
return "-DOUBLE_INF"
if numpy.isnan(value):
return "DOUBLE_NAN"
return None