feat: File* save/load actions, hashlib/uuid, dict spread and small renpy helpers in the player
Games with their own save screens work: FileAction/FileSave/FileLoad/FileDelete/FilePage*, FileLoadable,
FileTime, FileSaveName, FileJson and friends use browser slots (a slot has no screenshot). Added
renpy.has_image/has_screen/list_files/get_mouse_pos/log/jump_out_of_context/random.seed,
renpy.translation.translate_string, type/id/vars/Flatten/LiveComposite/Composite, bare defaultdict/
deepcopy, md5/sha256 (checked against test vectors), uuid4, time.monotonic, re.compile().fullmatch,
math.isfinite and `{**a, "k": v}`. The converter whitelists them and accepts the hashlib/uuid imports.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5c1e1aee01
commit
16ba20816c
8 files changed
+366
-20
No files matched your search
@@ -120,6 +120,10 @@ CALLS |= {"renpy.start_predict", "renpy.stop_predict", "renpy.start_predict_scre
|
||||
"renpy.ui.imagebutton", "renpy.return_statement"}
|
||||
from app.games.renpy_native import NATIVE_CLASSES, NATIVE_METHODS # noqa: E402
|
||||
CALLS |= set(NATIVE_CLASSES)
|
||||
CALLS |= {"type", "id", "vars", "Flatten", "LiveComposite", "Composite", "defaultdict", "OrderedDict", "deepcopy",
|
||||
"hashlib.md5", "hashlib.sha256", "uuid.uuid4", "time.monotonic", "re.compile", "math.isfinite", "renpy.log",
|
||||
"renpy.write_log", "renpy.get_filename_line", "renpy.has_image", "renpy.has_screen", "renpy.list_files",
|
||||
"renpy.get_mouse_pos", "renpy.jump_out_of_context", "renpy.random.seed", "random.seed"}
|
||||
CALLS |= {"dir", "datetime.date", "datetime.datetime", "datetime.timedelta", "date", "timedelta",
|
||||
"renpy.style.rebuild", "renpy.image_size"}
|
||||
CALLS |= {"map", "filter", "iter", "hash", "hasattr", "getattr", "setattr", "callable", "super"}
|
||||
@@ -134,6 +138,11 @@ TintMatrix BrightnessMatrix SaturationMatrix ContrastMatrix InvertMatrix Opacity
|
||||
ColorizeMatrix IdentityMatrix print Exception BaseException Character""".split())
|
||||
SCREEN_CALLS |= set(NATIVE_CLASSES)
|
||||
SCREEN_CALLS |= {"SetMixer", "ImageReference"}
|
||||
# Save/load screens of the game's own (slots are kept in the browser; a slot has no screenshot, see screens.ts)
|
||||
SCREEN_CALLS |= set("""FileAction FileSave FileLoad FileDelete FileTakeScreenshot FilePage FilePageNext FilePagePrevious
|
||||
FileLoadable FileUsedSlot FileNewest FileSaveName FileTime FileJson FileScreenshot FileCurrentScreenshot
|
||||
FileCurrentPage FilePageName FileSlotName""".split())
|
||||
SCREEN_CALLS |= {"renpy.translation.translate_string"}
|
||||
# Screen language helpers for audio, focus and pictures that play (see screens.ts)
|
||||
SCREEN_CALLS |= {"Movie", "MixerValue", "AudioPositionValue", "PauseAudio", "ToggleSetMembership",
|
||||
"GetFocusRect", "ToggleFocus", "XScrollValue", "YScrollValue", "Preference", "Start"}
|
||||
@@ -158,7 +167,7 @@ METHODS = set("append extend insert pop remove index count reverse sort clear co
|
||||
|
||||
METHODS |= NATIVE_METHODS
|
||||
# Methods of generators made by `renpy.random.Random()` / `random.Random()`
|
||||
METHODS |= {"randint", "randrange", "random", "choice", "choices", "sample", "shuffle", "uniform"}
|
||||
METHODS |= {"fullmatch", "hexdigest", "randint", "randrange", "random", "choice", "choices", "sample", "shuffle", "uniform"}
|
||||
|
||||
|
||||
def _bound_names(root) -> tuple[set[str], set[str]]:
|
||||
|
||||
@@ -25,7 +25,7 @@ UNARY_OPS = {ast.Not: "not", ast.USub: "-", ast.UAdd: "+", ast.Invert: "~"}
|
||||
|
||||
# Modules whose import is harmless: the player provides the functions it supports.
|
||||
KNOWN_MODULES = {"random", "math", "renpy", "store", "time", "datetime", "re", "copy", "collections",
|
||||
"itertools", "string", "functools", "operator", "json", "gc"}
|
||||
"itertools", "string", "functools", "operator", "json", "gc", "hashlib", "uuid"}
|
||||
|
||||
|
||||
class Unsupported(Exception):
|
||||
@@ -146,9 +146,9 @@ def expr(node: ast.AST) -> Expr:
|
||||
case ast.Tuple(elts=elts):
|
||||
return {"k": "tuple", "v": [expr(e) for e in elts]}
|
||||
case ast.Dict(keys=keys, values=values):
|
||||
if any(k is None for k in keys):
|
||||
raise Unsupported("распаковка ** в словаре")
|
||||
return {"k": "dict", "v": [[expr(k), expr(v)] for k, v in zip(keys, values)]}
|
||||
# `{**other, "k": v}`: a spread entry is a star key with an empty value
|
||||
return {"k": "dict", "v": [[{"k": "star", "v": expr(v)}, {"k": "const", "v": None}] if k is None
|
||||
else [expr(k), expr(v)] for k, v in zip(keys, values)]}
|
||||
case ast.Call(func=func, args=args, keywords=kws):
|
||||
call = {"k": "call", "f": expr(func),
|
||||
"a": [{"k": "star", "v": expr(a.value)} if isinstance(a, ast.Starred) else expr(a)
|
||||
|
||||
Reference in new issue
Block a user