1
0
mirror of synced 2024-11-30 16:54:30 +01:00

Implement the first bits of bytecode processing, including the ability to go to an animation frame.

This commit is contained in:
Jennifer Taylor 2021-05-23 20:32:21 +00:00
parent 0a6635d993
commit 14c8ac9347
4 changed files with 329 additions and 53 deletions

View File

@ -1,9 +1,10 @@
from typing import Dict, List, Tuple, Optional, Union from typing import Any, Dict, List, Tuple, Optional, Union
from PIL import Image # type: ignore from PIL import Image # type: ignore
from .blend import affine_composite from .blend import affine_composite
from .swf import SWF, Frame, Tag, AP2ShapeTag, AP2DefineSpriteTag, AP2PlaceObjectTag, AP2RemoveObjectTag, AP2DoActionTag, AP2DefineFontTag, AP2DefineEditTextTag, AP2PlaceCameraTag from .swf import SWF, Frame, Tag, AP2ShapeTag, AP2DefineSpriteTag, AP2PlaceObjectTag, AP2RemoveObjectTag, AP2DoActionTag, AP2DefineFontTag, AP2DefineEditTextTag, AP2PlaceCameraTag
from .types import Color, Matrix, Point, Rectangle from .decompile import ByteCode
from .types import Color, Matrix, Point, Rectangle, AP2Trigger, AP2Action, PushAction, StoreRegisterAction, StringConstant, Register, THIS, UNDEFINED
from .geo import Shape, DrawParams from .geo import Shape, DrawParams
from .util import VerboseOutput from .util import VerboseOutput
@ -47,7 +48,7 @@ class RegisteredDummy:
class PlacedObject: class PlacedObject:
# An object that occupies the screen at some depth. # An object that occupies the screen at some depth.
def __init__(self, object_id: int, depth: int, rotation_offset: Point, transform: Matrix, mult_color: Color, add_color: Color, blend: int) -> None: def __init__(self, object_id: int, depth: int, rotation_offset: Point, transform: Matrix, mult_color: Color, add_color: Color, blend: int, mask: Optional[Rectangle]) -> None:
self.__object_id = object_id self.__object_id = object_id
self.__depth = depth self.__depth = depth
self.rotation_offset = rotation_offset self.rotation_offset = rotation_offset
@ -55,6 +56,7 @@ class PlacedObject:
self.mult_color = mult_color self.mult_color = mult_color
self.add_color = add_color self.add_color = add_color
self.blend = blend self.blend = blend
self.mask = mask
@property @property
def source(self) -> Union[RegisteredClip, RegisteredShape, RegisteredDummy]: def source(self) -> Union[RegisteredClip, RegisteredShape, RegisteredDummy]:
@ -75,8 +77,19 @@ class PlacedObject:
class PlacedShape(PlacedObject): class PlacedShape(PlacedObject):
# A shape that occupies its parent clip at some depth. Placed by an AP2PlaceObjectTag # A shape that occupies its parent clip at some depth. Placed by an AP2PlaceObjectTag
# referencing an AP2ShapeTag. # referencing an AP2ShapeTag.
def __init__(self, object_id: int, depth: int, rotation_offset: Point, transform: Matrix, mult_color: Color, add_color: Color, blend: int, source: RegisteredShape) -> None: def __init__(
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend) self,
object_id: int,
depth: int,
rotation_offset: Point,
transform: Matrix,
mult_color: Color,
add_color: Color,
blend: int,
mask: Optional[Rectangle],
source: RegisteredShape,
) -> None:
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend, mask)
self.__source = source self.__source = source
@property @property
@ -90,11 +103,24 @@ class PlacedShape(PlacedObject):
class PlacedClip(PlacedObject): class PlacedClip(PlacedObject):
# A movieclip that occupies its parent clip at some depth. Placed by an AP2PlaceObjectTag # A movieclip that occupies its parent clip at some depth. Placed by an AP2PlaceObjectTag
# referencing an AP2DefineSpriteTag. Essentially an embedded movie clip. # referencing an AP2DefineSpriteTag. Essentially an embedded movie clip.
def __init__(self, object_id: int, depth: int, rotation_offset: Point, transform: Matrix, mult_color: Color, add_color: Color, blend: int, source: RegisteredClip) -> None: def __init__(
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend) self,
object_id: int,
depth: int,
rotation_offset: Point,
transform: Matrix,
mult_color: Color,
add_color: Color,
blend: int,
mask: Optional[Rectangle],
source: RegisteredClip,
) -> None:
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend, mask)
self.placed_objects: List[PlacedObject] = [] self.placed_objects: List[PlacedObject] = []
self.frame: int = 0 self.frame: int = 0
self.__source = source self.__source = source
self.playing: bool = True
self.requested_frame: Optional[int] = None
@property @property
def source(self) -> RegisteredClip: def source(self) -> RegisteredClip:
@ -111,11 +137,49 @@ class PlacedClip(PlacedObject):
def __repr__(self) -> str: def __repr__(self) -> str:
return f"PlacedClip(object_id={self.object_id}, depth={self.depth}, source={self.source}, frame={self.frame}, total_frames={len(self.source.frames)}, finished={self.finished})" return f"PlacedClip(object_id={self.object_id}, depth={self.depth}, source={self.source}, frame={self.frame}, total_frames={len(self.source.frames)}, finished={self.finished})"
# The following are attributes and functions necessary to support some simple bytecode.
def gotoAndPlay(self, frame: Any) -> None:
if not isinstance(frame, int):
# TODO: Technically this should also allow string labels to frames as identified in the
# SWF specification, but we don't support that here.
print(f"WARNING: Non-integer frame {frame} to gotoAndPlay function!")
return
if frame <= 0 or frame > len(self.source.frames):
return
self.requested_frame = frame
self.playing = True
@property
def frameOffset(self) -> int:
return self.requested_frame or self.frame
@frameOffset.setter
def frameOffset(self, val: Any) -> None:
if not isinstance(val, int):
# TODO: Technically this should also allow string labels to frames as identified in the
# SWF specification, but we don't support that here.
print(f"WARNING: Non-integer frameOffset {val} to frameOffset attribute!")
return
if val < 0 or val >= len(self.source.frames):
return
self.requested_frame = val + 1
class PlacedDummy(PlacedObject): class PlacedDummy(PlacedObject):
# A reference to an object we can't find because we're missing the import. # A reference to an object we can't find because we're missing the import.
def __init__(self, object_id: int, depth: int, rotation_offset: Point, transform: Matrix, mult_color: Color, add_color: Color, blend: int, source: RegisteredDummy) -> None: def __init__(
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend) self,
object_id: int,
depth: int,
rotation_offset: Point,
transform: Matrix,
mult_color: Color,
add_color: Color,
blend: int,
mask: Optional[Rectangle],
source: RegisteredDummy,
) -> None:
super().__init__(object_id, depth, rotation_offset, transform, mult_color, add_color, blend, mask)
self.__source = source self.__source = source
@property @property
@ -123,6 +187,50 @@ class PlacedDummy(PlacedObject):
return self.__source return self.__source
class Movie:
def __init__(self, root: PlacedClip) -> None:
self.__root = root
def getInstanceAtDepth(self, depth: Any) -> Any:
if not isinstance(depth, int):
return UNDEFINED
# For some reason, it looks like internally the depth of all objects is
# stored added to -0x4000, so let's reverse that.
depth = depth + 0x4000
for obj in self.__root.placed_objects:
if obj.depth == depth:
return obj
print(f"WARNING: Could not find object at depth {depth}!")
return UNDEFINED
class AEPLib:
def __init__(self, this: PlacedObject, movie: Movie) -> None:
self.__this = this
self.__movie = movie
def aep_set_rect_mask(self, thisptr: Any, left: Any, right: Any, top: Any, bottom: Any) -> None:
if not isinstance(left, (int, float)) or not isinstance(right, (int, float)) or not isinstance(top, (int, float)) or not isinstance(bottom, (int, float)):
print("WARNING: Ignoring aeplib.aep_set_rect_mask call with invalid parameters!")
return
if thisptr is THIS:
self.__this.mask = Rectangle(
left=float(left),
right=float(right),
top=float(top),
bottom=float(bottom),
)
else:
print("WARNING: Ignoring aeplib.aep_set_rect_mask call with unrecognized target!")
def aep_set_set_frame(self, thisptr: Any, frame: Any) -> None:
# I have no idea what this should do, so let's ignore it.
pass
class AFPRenderer(VerboseOutput): class AFPRenderer(VerboseOutput):
def __init__(self, shapes: Dict[str, Shape] = {}, textures: Dict[str, Image.Image] = {}, swfs: Dict[str, SWF] = {}, single_threaded: bool = False) -> None: def __init__(self, shapes: Dict[str, Shape] = {}, textures: Dict[str, Image.Image] = {}, swfs: Dict[str, SWF] = {}, single_threaded: bool = False) -> None:
super().__init__() super().__init__()
@ -137,6 +245,7 @@ class AFPRenderer(VerboseOutput):
# Internal render parameters. # Internal render parameters.
self.__registered_objects: Dict[int, Union[RegisteredShape, RegisteredClip, RegisteredDummy]] = {} self.__registered_objects: Dict[int, Union[RegisteredShape, RegisteredClip, RegisteredDummy]] = {}
self.__movie: Optional[Movie] = None
def add_shape(self, name: str, data: Shape) -> None: def add_shape(self, name: str, data: Shape) -> None:
# Register a named shape with the renderer. # Register a named shape with the renderer.
@ -193,6 +302,119 @@ class AFPRenderer(VerboseOutput):
return paths return paths
def __execute_bytecode(self, bytecode: ByteCode, clip: PlacedClip) -> None:
if self.__movie is None:
raise Exception("Logic error, executing bytecode outside of a rendering movie clip!")
location: int = 0
stack: List[Any] = []
variables: Dict[str, Any] = {
'aeplib': AEPLib(clip, self.__movie),
'GLOBAL': self.__movie,
}
registers: List[Any] = [UNDEFINED] * 256
while location < len(bytecode.actions):
action = bytecode.actions[location]
if action.opcode == AP2Action.END:
# End the execution.
self.vprint("Ending bytecode execution.")
break
elif action.opcode == AP2Action.GET_VARIABLE:
varname = stack.pop()
# Look up the variable, put it on the stack.
if varname in variables:
stack.append(variables[varname])
else:
stack.append(UNDEFINED)
elif action.opcode == AP2Action.SET_MEMBER:
# Grab what we're about to do.
set_value = stack.pop()
attribute = stack.pop()
obj = stack.pop()
if not hasattr(obj, attribute):
print(f"WARNING: Tried to set attribute {attribute} on {obj} but that attribute doesn't exist!")
else:
setattr(obj, attribute, set_value)
elif action.opcode == AP2Action.CALL_METHOD:
# Grab the method name.
methname = stack.pop()
# Grab the object to perform the call on.
obj = stack.pop()
# Grab the parameters to pass to the function.
num_params = stack.pop()
if not isinstance(num_params, int):
raise Exception("Logic error, cannot get number of parameters to method call!")
params = []
for _ in range(num_params):
params.append(stack.pop())
# Look up the python function we're calling.
try:
meth = getattr(obj, methname)
# Call it, set the return on the stack.
stack.append(meth(*params))
except AttributeError:
# Function does not exist!
print(f"WARNING: Tried to call {methname}({', '.join(repr(s) for s in params)}) on {obj} but that method doesn't exist!")
stack.append(UNDEFINED)
elif action.opcode == AP2Action.CALL_FUNCTION:
# Grab the method name.
funcname = stack.pop()
# Grab the object to perform the call on.
obj = variables['GLOBAL']
# Grab the parameters to pass to the function.
num_params = stack.pop()
if not isinstance(num_params, int):
raise Exception("Logic error, cannot get number of parameters to function call!")
params = []
for _ in range(num_params):
params.append(stack.pop())
# Look up the python function we're calling.
try:
func = getattr(obj, funcname)
# Call it, set the return on the stack.
stack.append(func(*params))
except AttributeError:
# Function does not exist!
print(f"WARNING: Tried to call {funcname}({', '.join(repr(s) for s in params)}) on {obj} but that function doesn't exist!")
stack.append(UNDEFINED)
elif isinstance(action, PushAction):
for obj in action.objects:
if isinstance(obj, Register):
stack.append(registers[obj.no])
elif isinstance(obj, StringConstant):
if obj.alias:
stack.append(obj.alias)
else:
stack.append(StringConstant.property_to_name(obj.const))
else:
stack.append(obj)
elif isinstance(action, StoreRegisterAction):
set_value = stack.pop()
if action.preserve_stack:
stack.append(set_value)
for reg in action.registers:
registers[reg.no] = set_value
elif action.opcode == AP2Action.POP:
stack.pop()
else:
print(f"WARNING: Unhandled opcode {action} with stack {stack}")
# Next opcode!
location += 1
def __place(self, tag: Tag, operating_clip: PlacedClip, prefix: str = "") -> Tuple[Optional[PlacedClip], bool]: def __place(self, tag: Tag, operating_clip: PlacedClip, prefix: str = "") -> Tuple[Optional[PlacedClip], bool]:
# "Place" a tag on the screen. Most of the time, this means performing the action of the tag, # "Place" a tag on the screen. Most of the time, this means performing the action of the tag,
# such as defining a shape (registering it with our shape list) or adding/removing an object. # such as defining a shape (registering it with our shape list) or adding/removing an object.
@ -253,6 +475,7 @@ class AFPRenderer(VerboseOutput):
new_mult_color, new_mult_color,
new_add_color, new_add_color,
new_blend, new_blend,
obj.mask,
newobj, newobj,
) )
@ -267,6 +490,7 @@ class AFPRenderer(VerboseOutput):
new_mult_color, new_mult_color,
new_add_color, new_add_color,
new_blend, new_blend,
obj.mask,
newobj, newobj,
) )
operating_clip.placed_objects[i] = new_clip operating_clip.placed_objects[i] = new_clip
@ -282,6 +506,7 @@ class AFPRenderer(VerboseOutput):
new_mult_color, new_mult_color,
new_add_color, new_add_color,
new_blend, new_blend,
obj.mask,
newobj, newobj,
) )
@ -320,6 +545,7 @@ class AFPRenderer(VerboseOutput):
tag.mult_color or Color(1.0, 1.0, 1.0, 1.0), tag.mult_color or Color(1.0, 1.0, 1.0, 1.0),
tag.add_color or Color(0.0, 0.0, 0.0, 0.0), tag.add_color or Color(0.0, 0.0, 0.0, 0.0),
tag.blend or 0, tag.blend or 0,
None,
newobj, newobj,
) )
) )
@ -327,14 +553,6 @@ class AFPRenderer(VerboseOutput):
# Didn't place a new clip, changed the parent clip. # Didn't place a new clip, changed the parent clip.
return None, True return None, True
elif isinstance(newobj, RegisteredClip): elif isinstance(newobj, RegisteredClip):
# TODO: Handle ON_LOAD triggers for this object. Many of these are just calls into
# the game to set the current frame that we're on, but sometimes its important.
for flags, code in tag.triggers.items():
for bytecode in code:
print("WARNING: Unhandled PLACE_OBJECT trigger!")
if self.verbose:
print(bytecode.decompile())
placed_clip = PlacedClip( placed_clip = PlacedClip(
tag.object_id, tag.object_id,
tag.depth, tag.depth,
@ -343,10 +561,18 @@ class AFPRenderer(VerboseOutput):
tag.mult_color or Color(1.0, 1.0, 1.0, 1.0), tag.mult_color or Color(1.0, 1.0, 1.0, 1.0),
tag.add_color or Color(0.0, 0.0, 0.0, 0.0), tag.add_color or Color(0.0, 0.0, 0.0, 0.0),
tag.blend or 0, tag.blend or 0,
None,
newobj, newobj,
) )
operating_clip.placed_objects.append(placed_clip) operating_clip.placed_objects.append(placed_clip)
for flags, code in tag.triggers.items():
if flags & AP2Trigger.ON_LOAD:
for bytecode in code:
self.__execute_bytecode(bytecode, placed_clip)
else:
print("WARNING: Unhandled PLACE_OBJECT trigger with flags {flags}!")
# Placed a new clip, changed the parent. # Placed a new clip, changed the parent.
return placed_clip, True return placed_clip, True
elif isinstance(newobj, RegisteredDummy): elif isinstance(newobj, RegisteredDummy):
@ -359,6 +585,7 @@ class AFPRenderer(VerboseOutput):
tag.mult_color or Color(1.0, 1.0, 1.0, 1.0), tag.mult_color or Color(1.0, 1.0, 1.0, 1.0),
tag.add_color or Color(0.0, 0.0, 0.0, 0.0), tag.add_color or Color(0.0, 0.0, 0.0, 0.0),
tag.blend or 0, tag.blend or 0,
None,
newobj, newobj,
) )
) )
@ -402,13 +629,14 @@ class AFPRenderer(VerboseOutput):
if not removed_objects: if not removed_objects:
print(f"WARNING: Couldn't find object to remove by ID {tag.object_id} and depth {tag.depth}!") print(f"WARNING: Couldn't find object to remove by ID {tag.object_id} and depth {tag.depth}!")
# TODO: Handle ON_UNLOAD triggers for this object. I don't think I've ever seen one
# on any object so this might be a pedantic request.
# Didn't place a new clip, changed parent clip. # Didn't place a new clip, changed parent clip.
return None, True return None, True
elif isinstance(tag, AP2DoActionTag): elif isinstance(tag, AP2DoActionTag):
print("WARNING: Unhandled DO_ACTION tag!") self.__execute_bytecode(tag.bytecode, operating_clip)
if self.verbose:
print(tag.bytecode.decompile())
# Didn't place a new clip. # Didn't place a new clip.
return None, False return None, False
@ -457,6 +685,9 @@ class AFPRenderer(VerboseOutput):
if parent_blend not in {0, 1, 2} and blend in {0, 1, 2}: if parent_blend not in {0, 1, 2} and blend in {0, 1, 2}:
blend = parent_blend blend = parent_blend
if renderable.mask:
print(f"WARNING: Unsupported mask Rectangle({renderable.mask})!")
# Render individual shapes if this is a sprite. # Render individual shapes if this is a sprite.
if isinstance(renderable, PlacedClip): if isinstance(renderable, PlacedClip):
if only_depths is not None: if only_depths is not None:
@ -553,30 +784,50 @@ class AFPRenderer(VerboseOutput):
# Track whether anything in ourselves or our children changes during this processing. # Track whether anything in ourselves or our children changes during this processing.
changed = False changed = False
# Clips that are part of our own placed objects which we should handle. while True:
child_clips = [c for c in clip.placed_objects if isinstance(c, PlacedClip)] # See if this clip should actually be played.
if clip.requested_frame is None and not clip.playing:
break
# Execute each tag in the frame. # See if we need to fast forward to a frame.
if not clip.finished: if clip.requested_frame is not None:
frame = clip.source.frames[clip.frame] if clip.frame > clip.requested_frame:
tags = clip.source.tags[frame.start_tag_offset:(frame.start_tag_offset + frame.num_tags)] # Rewind this clip to the beginning so we can replay until the requested frame.
clip.placed_objects = []
clip.frame = 0
elif clip.frame == clip.requested_frame:
# We played up through the requested frame, we're done!
clip.requested_frame = None
break
for tagno, tag in enumerate(tags): # Clips that are part of our own placed objects which we should handle.
# Perform the action of this tag. child_clips = [c for c in clip.placed_objects if isinstance(c, PlacedClip)]
self.vprint(f"{prefix} Sprite Tag ID: {clip.source.tag_id}, Current Tag: {frame.start_tag_offset + tagno}, Num Tags: {frame.num_tags}")
new_clip, clip_changed = self.__place(tag, clip, prefix=prefix)
changed = changed or clip_changed
# If we create a new movie clip, process it as well for this frame. # Execute each tag in the frame.
if new_clip: if not clip.finished:
changed = self.__process_tags(new_clip, prefix=prefix + " ") or changed frame = clip.source.frames[clip.frame]
tags = clip.source.tags[frame.start_tag_offset:(frame.start_tag_offset + frame.num_tags)]
# Now, handle each of the existing clips. for tagno, tag in enumerate(tags):
for child in child_clips: # Perform the action of this tag.
changed = self.__process_tags(child, prefix=prefix + " ") or changed self.vprint(f"{prefix} Sprite Tag ID: {clip.source.tag_id}, Current Tag: {frame.start_tag_offset + tagno}, Num Tags: {frame.num_tags}")
new_clip, clip_changed = self.__place(tag, clip, prefix=prefix)
changed = changed or clip_changed
# Now, advance the frame for this clip. # If we create a new movie clip, process it as well for this frame.
clip.advance() if new_clip:
changed = self.__process_tags(new_clip, prefix=prefix + " ") or changed
# Now, handle each of the existing clips.
for child in child_clips:
changed = self.__process_tags(child, prefix=prefix + " ") or changed
# Now, advance the frame for this clip.
clip.advance()
# See if we should bail.
if clip.requested_frame is None:
break
self.vprint(f"{prefix}Finished handling placed clip {clip.object_id} at depth {clip.depth}") self.vprint(f"{prefix}Finished handling placed clip {clip.object_id} at depth {clip.depth}")
@ -687,12 +938,14 @@ class AFPRenderer(VerboseOutput):
Color(1.0, 1.0, 1.0, 1.0), Color(1.0, 1.0, 1.0, 1.0),
Color(0.0, 0.0, 0.0, 0.0), Color(0.0, 0.0, 0.0, 0.0),
0, 0,
None,
RegisteredClip( RegisteredClip(
None, None,
swf.frames, swf.frames,
swf.tags, swf.tags,
), ),
) )
self.__movie = Movie(root_clip)
# These could possibly be overwritten from an external source of we wanted. # These could possibly be overwritten from an external source of we wanted.
actual_mult_color = Color(1.0, 1.0, 1.0, 1.0) actual_mult_color = Color(1.0, 1.0, 1.0, 1.0)
@ -726,4 +979,7 @@ class AFPRenderer(VerboseOutput):
# Allow ctrl-c to end early and render a partial animation. # Allow ctrl-c to end early and render a partial animation.
print(f"WARNING: Interrupted early, will render only {len(frames)}/{len(root_clip.source.frames)} frames of animation!") print(f"WARNING: Interrupted early, will render only {len(frames)}/{len(root_clip.source.frames)} frames of animation!")
# Clean up
self.movie = None
return int(spf * 1000.0), frames return int(spf * 1000.0), frames

View File

@ -8,6 +8,7 @@ from .types import Matrix, Color, Point, Rectangle
from .types import ( from .types import (
AP2Action, AP2Action,
AP2Tag, AP2Tag,
AP2Trigger,
DefineFunction2Action, DefineFunction2Action,
InitRegisterAction, InitRegisterAction,
StoreRegisterAction, StoreRegisterAction,
@ -1235,33 +1236,33 @@ class SWF(TrackedCoverage, VerboseOutput):
self.add_coverage(dataoffset + evt_offset, 8) self.add_coverage(dataoffset + evt_offset, 8)
events: List[str] = [] events: List[str] = []
if evt_flags & 0x1: if evt_flags & AP2Trigger.ON_LOAD:
events.append("ON_LOAD") events.append("ON_LOAD")
if evt_flags & 0x2: if evt_flags & AP2Trigger.ON_ENTER_FRAME:
events.append("ON_ENTER_FRAME") events.append("ON_ENTER_FRAME")
if evt_flags & 0x4: if evt_flags & AP2Trigger.ON_UNLOAD:
events.append("ON_UNLOAD") events.append("ON_UNLOAD")
if evt_flags & 0x8: if evt_flags & AP2Trigger.ON_MOUSE_MOVE:
events.append("ON_MOUSE_MOVE") events.append("ON_MOUSE_MOVE")
if evt_flags & 0x10: if evt_flags & AP2Trigger.ON_MOUSE_DOWN:
events.append("ON_MOUSE_DOWN") events.append("ON_MOUSE_DOWN")
if evt_flags & 0x20: if evt_flags & AP2Trigger.ON_MOUSE_UP:
events.append("ON_MOUSE_UP") events.append("ON_MOUSE_UP")
if evt_flags & 0x40: if evt_flags & AP2Trigger.ON_KEY_DOWN:
events.append("ON_KEY_DOWN") events.append("ON_KEY_DOWN")
if evt_flags & 0x80: if evt_flags & AP2Trigger.ON_KEY_UP:
events.append("ON_KEY_UP") events.append("ON_KEY_UP")
if evt_flags & 0x100: if evt_flags & AP2Trigger.ON_DATA:
events.append("ON_DATA") events.append("ON_DATA")
if evt_flags & 0x400: if evt_flags & AP2Trigger.ON_PRESS:
events.append("ON_PRESS") events.append("ON_PRESS")
if evt_flags & 0x800: if evt_flags & AP2Trigger.ON_RELEASE:
events.append("ON_RELEASE") events.append("ON_RELEASE")
if evt_flags & 0x1000: if evt_flags & AP2Trigger.ON_RELEASE_OUTSIDE:
events.append("ON_RELEASE_OUTSIDE") events.append("ON_RELEASE_OUTSIDE")
if evt_flags & 0x2000: if evt_flags & AP2Trigger.ON_ROLL_OVER:
events.append("ON_ROLL_OVER") events.append("ON_ROLL_OVER")
if evt_flags & 0x4000: if evt_flags & AP2Trigger.ON_ROLL_OUT:
events.append("ON_ROLL_OUT") events.append("ON_ROLL_OUT")
bytecode_offset += evt_offset bytecode_offset += evt_offset

View File

@ -4,6 +4,7 @@ from .ap2 import (
AP2Action, AP2Action,
AP2Object, AP2Object,
AP2Pointer, AP2Pointer,
AP2Trigger,
DefineFunction2Action, DefineFunction2Action,
Expression, Expression,
GenericObject, GenericObject,
@ -39,6 +40,7 @@ __all__ = [
'AP2Action', 'AP2Action',
'AP2Object', 'AP2Object',
'AP2Pointer', 'AP2Pointer',
'AP2Trigger',
'DefineFunction2Action', 'DefineFunction2Action',
'Expression', 'Expression',
'GenericObject', 'GenericObject',

View File

@ -2935,3 +2935,20 @@ class AP2Pointer:
display_P = 0xb4 display_P = 0xb4
geom_P = 0xb5 geom_P = 0xb5
filtesr_P = 0xb6 filtesr_P = 0xb6
class AP2Trigger:
ON_LOAD = 0x1
ON_ENTER_FRAME = 0x2
ON_UNLOAD = 0x4
ON_MOUSE_MOVE = 0x8
ON_MOUSE_DOWN = 0x10
ON_MOUSE_UP = 0x20
ON_KEY_DOWN = 0x40
ON_KEY_UP = 0x80
ON_DATA = 0x100
ON_PRESS = 0x400
ON_RELEASE = 0x800
ON_RELEASE_OUTSIDE = 0x1000
ON_ROLL_OVER = 0x2000
ON_ROLL_OUT = 0x4000