Skip to content

Commit 0f5ced3

Browse files
committed
Update to avm1-types 0.10
This commit updates the AVM1 libraries to the version `0.10`. With this version, the general structure of these libraries should be complete. Initially, AVM1 support was included in the SWF libraries (`swf-types`, `swf-parser`). It used the model defined by Adobe's SWF spec were you can parse actions into a vec by simply reading them sequentially. It quickly occurred that this model is too simple and did not reflect how the player interprets bytecode. Adobe's interpret support jumps to arbitrary offsets, it treats the bytecode as an opaque buffer and reads only one action at a time. Because of this, parsing AVM1 actions ahead of time became much more complicated and AVM1 support was moved to its own libraries. Initially, there was only support to read a single low-level action at a time. This was the only API provided by the version used until now by Flashback. And Flashback used it to read the actions sequentially (as in Adobe's spec). The issue with dropping support for reading all actions was that you were on your own to perform static analysis of AVM1 bytecode. It also meant that control-flow actions such as `Jump` or `If` were tightly dependant on the actual encoding because they included byte offsets. The various versions of the AVM1 libraries were focused on bringing back support for static analysis of AVM1 bytecode. The version 0.10 solves it by providing two ways to view actions: `raw` and `cfg`. The raw mode corresponds to how the interpreter reads bytecode: a single action at a time and using byte offsets for control flow. The Control Flow Graph (CFG) mode represents the code as a graph were nodes represent linear sections of code (where you can safely advance through the sequence of actions) and edges represent jumps in the code. The graph itself is represented as a non-empty vector of blocks. Each block has a unique label, a list of simple actions (with no impact on control flow) and a flow action. The flow action describes the outgoing edges and how they are chosen. The target of the jump is identified by its label, the value `None` indicates the end of the current function. The two main variants are `CfgFlow::Simple` for unconditional jumps and `CfgFlow::If` for jumps based on truthiness of the top of the stack. In the case of Flashback, AVM1 support was minimal. Thanks to this, updating the AVM1 libraries to their latest version was fairly easy. The behavior should be the same, except for a small difference around `WaitForFrame` and `WaitForFrame2`. The previous code simply ignored these actions and continued with what followed. The new code forces to treat them as actions affecting the flow. Because support for control flow is not implemented, it just stops at the end of the block and does not jump to the `ready_target`. Support for control-flow may require larger changes that are best left for some future commit in my opinion.
1 parent 939a574 commit 0f5ced3

File tree

2 files changed

+46
-42
lines changed

2 files changed

+46
-42
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ readme = "README.md"
1010
description = "Adobe Flash / SWF preservation tools."
1111

1212
[dependencies]
13-
avm1-parser = "0.2.0"
14-
avm1-tree = "0.2.1"
13+
avm1-parser = "0.10.0"
14+
avm1-types = "0.10.0"
1515
swf-parser = "0.11.0"
1616
swf-types = "0.11.0"
1717
svg = "0.5.12"

src/avm1.rs

Lines changed: 44 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
use crate::timeline::Frame;
2+
use avm1_parser::parse_cfg;
3+
use avm1_types::cfg::{Action, Cfg, CfgBlock, CfgFlow};
4+
use avm1_types::PushValue;
25

36
#[derive(Clone, Debug)]
47
pub enum Value {
@@ -54,19 +57,13 @@ pub struct Code {
5457
}
5558

5659
impl Code {
57-
pub fn parse_and_compile(mut data: &[u8]) -> Self {
58-
let mut actions = vec![];
59-
while data[0] != 0 {
60-
let (rest, action) = avm1_parser::parse_action(data).unwrap();
61-
data = rest;
62-
actions.push(action);
63-
}
64-
assert_eq!(data, [0]);
60+
pub fn parse_and_compile(data: &[u8]) -> Self {
61+
let cfg = parse_cfg(data);
6562

66-
Code::compile(actions)
63+
Code::compile(cfg)
6764
}
6865

69-
pub fn compile(actions: Vec<avm1_tree::Action>) -> Self {
66+
pub fn compile(cfg: Cfg) -> Self {
7067
let mut consts = vec![];
7168
let mut regs = vec![];
7269
let mut stack = vec![];
@@ -77,48 +74,44 @@ impl Code {
7774
regs.push(Value::Undefined);
7875
regs.pop();
7976

80-
for action in actions {
77+
// FIXME(demurgos) Handle control flow, we're currently only compiling the first block
78+
let block: CfgBlock = cfg.blocks.into_vec().remove(0);
79+
80+
for action in block.actions {
8181
match action {
82-
avm1_tree::Action::Play => ops.push(Op::Play),
83-
avm1_tree::Action::Stop => ops.push(Op::Stop),
84-
avm1_tree::Action::GotoFrame(goto) => {
82+
Action::Play => ops.push(Op::Play),
83+
Action::Stop => ops.push(Op::Stop),
84+
Action::GotoFrame(goto) => {
8585
ops.push(Op::GotoFrame(Frame(goto.frame as u16)));
8686
}
87-
avm1_tree::Action::GotoLabel(goto) => {
87+
Action::GotoLabel(goto) => {
8888
ops.push(Op::GotoLabel(goto.label));
8989
}
90-
avm1_tree::Action::GetUrl(get_url) => {
90+
Action::GetUrl(get_url) => {
9191
ops.push(Op::GetUrl(get_url.url, get_url.target));
9292
}
93-
94-
// All of frames are loaded ahead of time, no waiting needed.
95-
avm1_tree::Action::WaitForFrame(_) => {}
96-
avm1_tree::Action::WaitForFrame2(_) => {
97-
stack.pop();
98-
}
99-
100-
avm1_tree::Action::ConstantPool(pool) => {
101-
consts = pool.constant_pool;
93+
Action::ConstantPool(pool) => {
94+
consts = pool.pool;
10295
}
103-
avm1_tree::Action::Push(push) => {
96+
Action::Push(push) => {
10497
stack.extend(push.values.into_iter().map(|value| match value {
105-
avm1_tree::Value::Undefined => Value::Undefined,
106-
avm1_tree::Value::Null => Value::Null,
107-
avm1_tree::Value::Boolean(x) => Value::Bool(x),
108-
avm1_tree::Value::Sint32(x) => Value::I32(x),
109-
avm1_tree::Value::Float32(x) => Value::F32(x),
110-
avm1_tree::Value::Float64(x) => Value::F64(x),
111-
avm1_tree::Value::String(s) => Value::Str(s),
98+
PushValue::Undefined => Value::Undefined,
99+
PushValue::Null => Value::Null,
100+
PushValue::Boolean(x) => Value::Bool(x),
101+
PushValue::Sint32(x) => Value::I32(x),
102+
PushValue::Float32(x) => Value::F32(x),
103+
PushValue::Float64(x) => Value::F64(x),
104+
PushValue::String(s) => Value::Str(s),
112105

113106
// FIXME(eddyb) avoid per-use cloning.
114-
avm1_tree::Value::Constant(i) => Value::Str(consts[i as usize].to_string()),
115-
avm1_tree::Value::Register(i) => regs[i as usize].clone(),
107+
PushValue::Constant(i) => Value::Str(consts[i as usize].to_string()),
108+
PushValue::Register(i) => regs[i as usize].clone(),
116109
}));
117110
}
118-
avm1_tree::Action::Pop => {
111+
Action::Pop => {
119112
stack.pop();
120113
}
121-
avm1_tree::Action::GetVariable => match stack.pop().unwrap() {
114+
Action::GetVariable => match stack.pop().unwrap() {
122115
Value::Str(name) => {
123116
ops.push(Op::GetVar(name));
124117
stack.push(Value::OpRes(ops.len() - 1));
@@ -128,7 +121,7 @@ impl Code {
128121
break;
129122
}
130123
},
131-
avm1_tree::Action::SetVariable => {
124+
Action::SetVariable => {
132125
let value = stack.pop().unwrap();
133126
match stack.pop().unwrap() {
134127
Value::Str(name) => {
@@ -141,7 +134,7 @@ impl Code {
141134
}
142135
}
143136
}
144-
avm1_tree::Action::CallFunction => {
137+
Action::CallFunction => {
145138
let name = stack.pop().unwrap();
146139
let arg_count = stack.pop().unwrap();
147140
match (name, arg_count.as_i32()) {
@@ -160,7 +153,7 @@ impl Code {
160153
}
161154
}
162155
}
163-
avm1_tree::Action::CallMethod => {
156+
Action::CallMethod => {
164157
let mut name = stack.pop().unwrap();
165158
let this = stack.pop().unwrap();
166159
let arg_count = stack.pop().unwrap();
@@ -195,6 +188,17 @@ impl Code {
195188
}
196189
}
197190

191+
match block.flow {
192+
// All of frames are loaded ahead of time, no waiting needed.
193+
CfgFlow::WaitForFrame(_) => {}
194+
CfgFlow::WaitForFrame2(_) => {
195+
stack.pop();
196+
}
197+
_ => {
198+
eprintln!("unknown flow: {:?}", block.flow);
199+
}
200+
}
201+
198202
Code { ops }
199203
}
200204
}

0 commit comments

Comments
 (0)