@swf2es/player-hosts 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -65,9 +65,10 @@ export class TextObject extends DisplayObject {
65
65
  embedFonts = false;
66
66
  autoSize = "none";
67
67
  /**
68
- * A script set the size of a field that fits itself: as in Flash, the fit
69
- * waits for its size to be asked, its transform to change or the frame to
70
- * be drawn, and a wordWrap set meanwhile wraps at the size set (adl).
68
+ * A script set the size or the autoSize of a field that fits itself: as
69
+ * in Flash, the fit waits for its size to be asked, its transform to
70
+ * change or the frame to be drawn, and a wordWrap set meanwhile wraps at
71
+ * the size the field has (adl).
71
72
  */
72
73
  fitPending = false;
73
74
  selectable = true;
@@ -201,24 +202,24 @@ export class TextObject extends DisplayObject {
201
202
  this.height = height;
202
203
  this.invalidate(CONTENT);
203
204
  }
204
- /** A script set its size: under autoSize, the fit waits (fitPending). */
205
- sizeSet() {
206
- if (this.autoSize !== "none") {
205
+ /** A script set its size or its autoSize: under autoSize, the fit waits (fitPending). */
206
+ deferFit() {
207
+ if (this.autoSize !== "none" && !this.fitPending) {
207
208
  this.fitPending = true;
208
209
  pendingFits.add(this);
209
210
  }
210
211
  }
211
- /** The fit a set size left pending, applied: its size asked, its transform changed, the frame drawn. */
212
+ /**
213
+ * The fit left pending, applied: as its position, size or bounds are
214
+ * asked, its transform is set, or the frame is drawn; not as its scale,
215
+ * its rotation or its text metrics are asked, or its alpha set (adl).
216
+ */
212
217
  settle() {
213
218
  if (this.fitPending) {
214
219
  this.fitPending = false;
215
220
  this.fit();
216
221
  }
217
222
  }
218
- touch() {
219
- super.touch();
220
- this.settle();
221
- }
222
223
  /** The text, \r between its lines. */
223
224
  get text() {
224
225
  return this.model.text;
@@ -144,6 +144,10 @@ function spriteOnly(traits) {
144
144
  }
145
145
  /** Move a display object, by a copy of its matrix, and have it drawn again. */
146
146
  function transform(d, change) {
147
+ // A field's pending fit first, so that what is set is where it stays.
148
+ if (d instanceof TextObject) {
149
+ d.settle();
150
+ }
147
151
  const m = { ...d.matrix };
148
152
  change(m);
149
153
  d.matrix = m;
@@ -191,6 +195,10 @@ export function displayObjectNatives(s) {
191
195
  }
192
196
  }
193
197
  get x() {
198
+ // A field's pending fit moves a centred or right-aligned one (adl).
199
+ if (this.$display instanceof TextObject) {
200
+ this.$display.settle();
201
+ }
194
202
  return withOffset(this.$display.matrix.tx, fieldOffset(this.$display)[0]);
195
203
  }
196
204
  // A NaN position is 0, as Flash has it (the corpus's `displayobject_invalid_floats`), and
@@ -207,6 +215,9 @@ export function displayObjectNatives(s) {
207
215
  });
208
216
  }
209
217
  get y() {
218
+ if (this.$display instanceof TextObject) {
219
+ this.$display.settle();
220
+ }
210
221
  return withOffset(this.$display.matrix.ty, fieldOffset(this.$display)[1]);
211
222
  }
212
223
  set y(v) {
@@ -340,7 +351,7 @@ export function displayObjectNatives(s) {
340
351
  if (d instanceof TextObject) {
341
352
  d.width = value / (Math.abs(d.scaleX) || 1);
342
353
  d.invalidate(CONTENT);
343
- d.sizeSet();
354
+ d.deferFit();
344
355
  return;
345
356
  }
346
357
  const r = bounds(d, true);
@@ -375,7 +386,7 @@ export function displayObjectNatives(s) {
375
386
  if (d instanceof TextObject) {
376
387
  d.height = value / (Math.abs(d.scaleY) || 1);
377
388
  d.invalidate(CONTENT);
378
- d.sizeSet();
389
+ d.deferFit();
379
390
  return;
380
391
  }
381
392
  const r = bounds(d, true);
@@ -1,9 +1,13 @@
1
1
  // flash.net.NetConnection in its local mode, connect(null), which Flash
2
2
  // keeps for progressive video, with its properties and status events as
3
- // Flash gives them; an HTTP URI is kept, as Flash Remoting would use it,
4
- // and a call over it is not supported yet. Responder holds its callbacks.
3
+ // Flash gives them; and Flash Remoting over an HTTP URI: each call queued,
4
+ // and a frame's calls POSTed together at its end as one packet
5
+ // (remoting.ts), the connection's headers with them, each answer, /n/
6
+ // onResult or /n/onStatus, going to its Responder, as adl does.
7
+ // Responder holds its callbacks.
5
8
  import { avm2 } from "../../../../runtime/index.js";
6
9
  import { dispatchEvent } from "../../../scripting/events.js";
10
+ import { remoteArguments, remoteMessages, remotePacket, remoteValue, } from "./remoting.js";
7
11
  /** NetConnection's private invoke codes, as playerglobal's constants have them. */
8
12
  const CLOSE = 1;
9
13
  const CALL = 2;
@@ -32,8 +36,10 @@ export function netConnectionNatives(s) {
32
36
  /** Null is the local connection, at once and connected; an HTTP URI is only kept. A connection made goes first. */
33
37
  connect(command) {
34
38
  const self = this;
35
- if (this.$connected) {
39
+ // A gateway connected before is closed as a local one is (the corpus's netconnection_send_remote).
40
+ if (this.$connected || this.$remote) {
36
41
  this.$connected = false;
42
+ this.$remote = null;
37
43
  status(self, ["code", "NetConnection.Connect.Closed", "level", "status"]);
38
44
  }
39
45
  if (command === null || command === undefined || s.rt.toString(command) === "null") {
@@ -43,6 +49,16 @@ export function netConnectionNatives(s) {
43
49
  return;
44
50
  }
45
51
  this.$uri = s.rt.toString(command);
52
+ if (/^https?:\/\//i.test(this.$uri)) {
53
+ this.$remote = {
54
+ url: this.$uri,
55
+ headers: [],
56
+ queue: [],
57
+ next: 1,
58
+ responders: new Map(),
59
+ scheduled: false,
60
+ };
61
+ }
46
62
  }
47
63
  "flash.net:NetConnection::invoke"(code) {
48
64
  if (s.rt.toUint(code) === CLOSE) {
@@ -51,13 +67,16 @@ export function netConnectionNatives(s) {
51
67
  }
52
68
  throw s.rt.unsupported(`NetConnection invoke ${s.rt.toUint(code)}`);
53
69
  }
54
- "flash.net:NetConnection::invokeWithArgsArray"(code, _args) {
70
+ "flash.net:NetConnection::invokeWithArgsArray"(code, args) {
55
71
  const which = s.rt.toUint(code);
72
+ const values = (args?.$a ?? []);
56
73
  if (which === ADD_HEADER) {
74
+ addHeader(this, values);
57
75
  return undefined;
58
76
  }
59
77
  if (which === CALL) {
60
- throw s.rt.unsupported("NetConnection.call, Flash Remoting");
78
+ call(this, values);
79
+ return undefined;
61
80
  }
62
81
  throw s.rt.unsupported(`NetConnection invoke ${which}`);
63
82
  }
@@ -135,6 +154,7 @@ export function netConnectionNatives(s) {
135
154
  const wasConnected = o.$connected ?? false;
136
155
  o.$connected = false;
137
156
  o.$uri = null;
157
+ o.$remote = null;
138
158
  if (wasConnected) {
139
159
  status(o, ["code", "NetConnection.Connect.Closed", "level", "status"]);
140
160
  }
@@ -143,6 +163,131 @@ export function netConnectionNatives(s) {
143
163
  status(o, ["code", "", "description", "", "details", "", "level", "status"]);
144
164
  }
145
165
  };
166
+ /**
167
+ * A call queued for the frame's end, numbered on its connection; nothing
168
+ * on a local connection or none, which send nothing, as in adl.
169
+ */
170
+ const call = (o, values) => {
171
+ const remote = o.$remote;
172
+ if (!remote) {
173
+ return;
174
+ }
175
+ const method = s.rt.toString(values[0]);
176
+ const responder = (values[1] ?? null);
177
+ const id = remote.next++;
178
+ const encoding = o.$objectEncoding ?? defaultEncoding;
179
+ remote.queue.push({ method, id, body: remoteArguments(s.rt, values.slice(2), encoding) });
180
+ remote.responders.set(id, responder);
181
+ if (!remote.scheduled) {
182
+ remote.scheduled = true;
183
+ s.afterExitFrame.push(() => send(o, remote, encoding));
184
+ }
185
+ };
186
+ /** One header of a name, its case aside: the last set, a value of null its value (corpus). */
187
+ const addHeader = (o, values) => {
188
+ const remote = o.$remote;
189
+ if (!remote) {
190
+ return;
191
+ }
192
+ const name = s.rt.toString(values[0]);
193
+ const encoding = o.$objectEncoding ?? defaultEncoding;
194
+ const value = remoteValue(s.rt, values[2] ?? null, encoding);
195
+ remote.headers = remote.headers.filter((h) => h.name.toLowerCase() !== name.toLowerCase());
196
+ remote.headers.push({ name, mustUnderstand: s.rt.toBoolean(values[1]), value });
197
+ };
198
+ /** The frame's calls, POSTed as one packet; their answers come in a later frame. */
199
+ const send = (o, remote, encoding) => {
200
+ remote.scheduled = false;
201
+ const calls = remote.queue;
202
+ remote.queue = [];
203
+ const fetch = s.fetch;
204
+ // A connection closed or replaced since sends nothing.
205
+ if (o.$remote !== remote || calls.length === 0 || !fetch) {
206
+ return;
207
+ }
208
+ const body = remotePacket(encoding, remote.headers, calls);
209
+ const request = fetch({
210
+ url: remote.url,
211
+ method: "POST",
212
+ headers: [["Content-Type", "application/x-amf"]],
213
+ body,
214
+ purpose: "data",
215
+ }, new AbortController().signal).then((result) => result, () => ({ bytes: null, status: 0, headers: [] }));
216
+ s.loads.trackRequest(request.then((result) => {
217
+ s.loads.deferHostEvent(() => {
218
+ try {
219
+ answered(o, remote, result.bytes, result.status);
220
+ }
221
+ finally {
222
+ // Its calls are done with, answered or not: their responders go.
223
+ for (const call of calls) {
224
+ remote.responders.delete(call.id);
225
+ }
226
+ }
227
+ });
228
+ }));
229
+ };
230
+ /**
231
+ * An answer: each message to the responder of the call it names; an
232
+ * HTTP error or no answer as Call.Failed, one that is no packet as
233
+ * Call.BadVersion, an empty one as nothing, as adl reports them.
234
+ */
235
+ const answered = (o, remote, bytes, httpStatus) => {
236
+ if (o.$remote !== remote) {
237
+ return;
238
+ }
239
+ if (bytes === null) {
240
+ const description = httpStatus >= 300 ? `HTTP: Status ${httpStatus}` : "HTTP: Failed";
241
+ status(o, [
242
+ "code",
243
+ "NetConnection.Call.Failed",
244
+ "description",
245
+ description,
246
+ "details",
247
+ remote.url,
248
+ "level",
249
+ "error",
250
+ ]);
251
+ return;
252
+ }
253
+ if (bytes.length === 0) {
254
+ return;
255
+ }
256
+ const messages = remoteMessages(s.rt, bytes);
257
+ if (messages === "version") {
258
+ status(o, [
259
+ "code",
260
+ "NetConnection.Call.BadVersion",
261
+ "description",
262
+ "",
263
+ "details",
264
+ "",
265
+ "level",
266
+ "error",
267
+ ]);
268
+ return;
269
+ }
270
+ for (const [target, value] of messages) {
271
+ const m = /^\/(\d+)\/(onResult|onStatus)$/.exec(target);
272
+ if (!m) {
273
+ continue;
274
+ }
275
+ const id = Number(m[1]);
276
+ const responder = remote.responders.get(id);
277
+ remote.responders.delete(id);
278
+ const handler = responder
279
+ ? responder[m[2] === "onResult" ? "$result" : "$status"]
280
+ : null;
281
+ if (typeof handler === "function" || (handler && typeof handler === "object")) {
282
+ try {
283
+ s.rt.call(handler, null, value);
284
+ }
285
+ catch (error) {
286
+ s.reportUncaught(error);
287
+ }
288
+ }
289
+ }
290
+ };
146
291
  class ResponderNatives {
147
292
  "flash.net:Responder::ctor"(result, status) {
148
293
  this.$result = result;
@@ -6,12 +6,29 @@ import * as A from "../../../avm1/amf.js";
6
6
  import { LOCAL_MESSAGE_LIMIT } from "../../../localconnection.js";
7
7
  const encoder = new TextEncoder();
8
8
  const decoder = new TextDecoder();
9
- export function writeLocalValues(rt, args) {
10
- // The original AMF3 payload is already bounded; AMF0 can expand small integers.
11
- const out = new A.Writer(() => null, 8, null, null);
12
- const references = new Map();
13
- let next = 1;
14
- const write = (value, depth) => {
9
+ /**
10
+ * AS3 values as AMF0, through the AS2 codec's writer: LocalConnection's
11
+ * arguments, and Flash Remoting's in AMF0. `limit` bounds strings and
12
+ * arrays (LocalConnection's message size); `next` is the reference the
13
+ * first object takes: 1 where the strict array of arguments is itself the
14
+ * first, as LocalConnection numbers them, 0 for remoting's (adl).
15
+ */
16
+ export class Amf0Writer {
17
+ rt;
18
+ limit;
19
+ next;
20
+ what;
21
+ out = new A.Writer(() => null, 8, null, null);
22
+ references = new Map();
23
+ constructor(rt, limit = LOCAL_MESSAGE_LIMIT, next = 1, what = "LocalConnection") {
24
+ this.rt = rt;
25
+ this.limit = limit;
26
+ this.next = next;
27
+ this.what = what;
28
+ }
29
+ value(value, depth = 0) {
30
+ const out = this.out;
31
+ const rt = this.rt;
15
32
  if (depth >= A.MAX_DEPTH - 2) {
16
33
  out.bytes(A.UNDEFINED);
17
34
  return;
@@ -37,8 +54,8 @@ export function writeLocalValues(rt, args) {
37
54
  }
38
55
  if (traits.name === "XML" || traits.name === "flash.xml::XMLDocument") {
39
56
  const text = rt.toString(rt.callProperty(value, rt.publicName(traits.name === "XML" ? "toXMLString" : "toString")));
40
- if (text.length > LOCAL_MESSAGE_LIMIT) {
41
- throw new RangeError("LocalConnection message too large");
57
+ if (text.length > this.limit) {
58
+ throw new RangeError(`${this.what} message too large`);
42
59
  }
43
60
  const bytes = encoder.encode(text);
44
61
  out.bytes(A.XML);
@@ -46,13 +63,13 @@ export function writeLocalValues(rt, args) {
46
63
  out.push(bytes);
47
64
  return;
48
65
  }
49
- const seen = references.get(value);
66
+ const seen = this.references.get(value);
50
67
  if (seen !== undefined) {
51
68
  out.bytes(A.REFERENCE);
52
69
  out.u16(seen);
53
70
  return;
54
71
  }
55
- references.set(value, next++);
72
+ this.references.set(value, this.next++);
56
73
  if (value.$a) {
57
74
  const array = value.$a;
58
75
  const properties = value.$d;
@@ -61,22 +78,22 @@ export function writeLocalValues(rt, args) {
61
78
  out.u32(array.length);
62
79
  for (const key of Object.keys(array)) {
63
80
  out.name(key);
64
- write(array[Number(key)], depth + 1);
81
+ this.value(array[Number(key)], depth + 1);
65
82
  }
66
83
  for (const [key, field] of properties) {
67
84
  out.name(key);
68
- write(field, depth + 1);
85
+ this.value(field, depth + 1);
69
86
  }
70
87
  out.bytes(0, 0, A.OBJECT_END);
71
88
  return;
72
89
  }
73
- if (array.length > LOCAL_MESSAGE_LIMIT) {
74
- throw new RangeError("LocalConnection message too large");
90
+ if (array.length > this.limit) {
91
+ throw new RangeError(`${this.what} message too large`);
75
92
  }
76
93
  out.bytes(A.STRICT_ARRAY);
77
94
  out.u32(array.length);
78
95
  for (let i = 0; i < array.length; i++) {
79
- write(array[i], depth + 1);
96
+ this.value(array[i], depth + 1);
80
97
  }
81
98
  return;
82
99
  }
@@ -106,80 +123,122 @@ export function writeLocalValues(rt, args) {
106
123
  continue;
107
124
  }
108
125
  out.name(name);
109
- write(field, depth + 1);
126
+ this.value(field, depth + 1);
110
127
  }
111
128
  out.bytes(0, 0, A.OBJECT_END);
112
- };
113
- out.bytes(A.STRICT_ARRAY);
114
- out.u32(args.length);
129
+ }
130
+ }
131
+ export function writeLocalValues(rt, args) {
132
+ // The original AMF3 payload is already bounded; AMF0 can expand small integers.
133
+ const w = new Amf0Writer(rt);
134
+ w.out.bytes(A.STRICT_ARRAY);
135
+ w.out.u32(args.length);
115
136
  for (const value of args) {
116
- write(value, 0);
137
+ w.value(value);
117
138
  }
118
- return out.finish();
139
+ return w.out.finish();
119
140
  }
120
- export function readLocalValues(rt, bytes) {
121
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
122
- const references = [];
123
- let at = 0;
124
- const u16 = () => {
125
- const n = view.getUint16(at);
126
- at += 2;
141
+ /** AMF0's marker for an AMF3 value after it, which Flash Remoting writes and reads. */
142
+ export const AVMPLUS = 0x11;
143
+ /**
144
+ * AMF0 read as AS3 values from `at`: LocalConnection's arguments, and
145
+ * Flash Remoting's answers, whose AMF3 values after 0x11 (with `amf3`)
146
+ * share one AMF3 reader's tables.
147
+ */
148
+ export class Amf0Reader {
149
+ rt;
150
+ bytes;
151
+ amf3;
152
+ what;
153
+ at;
154
+ view;
155
+ references = [];
156
+ amf3Reader = null;
157
+ amf3Bytes = null;
158
+ constructor(rt, bytes, at = 0, amf3 = false, what = "LocalConnection") {
159
+ this.rt = rt;
160
+ this.bytes = bytes;
161
+ this.amf3 = amf3;
162
+ this.what = what;
163
+ this.at = at;
164
+ this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
165
+ }
166
+ u16() {
167
+ const n = this.view.getUint16(this.at);
168
+ this.at += 2;
127
169
  return n;
128
- };
129
- const u32 = () => {
130
- const n = view.getUint32(at);
131
- at += 4;
170
+ }
171
+ u32() {
172
+ const n = this.view.getUint32(this.at);
173
+ this.at += 4;
132
174
  return n;
133
- };
134
- const utf = (length) => {
135
- if (length > bytes.length - at) {
136
- throw new RangeError("LocalConnection message ended early");
175
+ }
176
+ utf(length) {
177
+ if (length > this.bytes.length - this.at) {
178
+ throw new RangeError(`${this.what} message ended early`);
137
179
  }
138
- const text = decoder.decode(bytes.subarray(at, at + length));
139
- at += length;
180
+ const text = decoder.decode(this.bytes.subarray(this.at, this.at + length));
181
+ this.at += length;
140
182
  return text;
141
- };
142
- const name = () => utf(u16());
143
- const read = (depth) => {
183
+ }
184
+ name() {
185
+ return this.utf(this.u16());
186
+ }
187
+ /** An AMF3 value at the reader's place, its tables those of the values before it. */
188
+ amf3Value() {
189
+ if (this.amf3Bytes === null) {
190
+ this.amf3Bytes = new avm2.Bytes(this.rt, {});
191
+ this.amf3Bytes.write(this.bytes);
192
+ this.amf3Reader = new avm2.Amf3Reader(this.rt, this.amf3Bytes);
193
+ }
194
+ this.amf3Bytes.position = this.at;
195
+ const value = this.amf3Reader.value();
196
+ this.at = this.amf3Bytes.position;
197
+ return value;
198
+ }
199
+ value(depth = 0) {
200
+ const rt = this.rt;
201
+ const bytes = this.bytes;
202
+ const view = this.view;
144
203
  if (depth >= A.MAX_DEPTH) {
145
- throw new RangeError("LocalConnection arguments nested too deeply");
204
+ throw new RangeError(`${this.what} arguments nested too deeply`);
146
205
  }
147
- const marker = bytes[at++];
206
+ const marker = bytes[this.at++];
148
207
  switch (marker) {
149
208
  case A.NUMBER: {
150
- const n = view.getFloat64(at);
151
- at += 8;
209
+ const n = view.getFloat64(this.at);
210
+ this.at += 8;
152
211
  return n;
153
212
  }
154
213
  case A.BOOLEAN:
155
- return bytes[at++] !== 0;
214
+ return bytes[this.at++] !== 0;
156
215
  case A.STRING:
157
- return name();
216
+ return this.name();
158
217
  case A.LONG_STRING:
159
- return utf(u32());
218
+ return this.utf(this.u32());
160
219
  case A.NULL:
161
220
  return null;
162
221
  case A.UNDEFINED:
163
222
  case A.UNSUPPORTED:
164
223
  return undefined;
165
224
  case A.REFERENCE:
166
- return references[u16()];
225
+ return this.references[this.u16()];
167
226
  case A.DATE: {
168
- const time = view.getFloat64(at);
169
- at += 10;
227
+ const time = view.getFloat64(this.at);
228
+ this.at += 10;
170
229
  return rt.construct(rt.classNamed("Date"), time);
171
230
  }
172
231
  case A.XML:
173
- return rt.construct(rt.classNamed("flash.xml::XMLDocument"), utf(u32()));
232
+ return rt.construct(rt.classNamed("flash.xml::XMLDocument"), this.utf(this.u32()));
174
233
  case A.STRICT_ARRAY: {
175
- const count = u32();
176
- if (count > bytes.length - at) {
177
- throw new RangeError("LocalConnection message ended early");
234
+ const count = this.u32();
235
+ if (count > bytes.length - this.at) {
236
+ throw new RangeError(`${this.what} message ended early`);
178
237
  }
179
238
  const result = rt.newArray([]);
180
- references.push(result);
239
+ this.references.push(result);
181
240
  for (let i = 0; i < count; i++) {
182
- result.$a.push(read(depth + 1));
241
+ result.$a.push(this.value(depth + 1));
183
242
  }
184
243
  return result;
185
244
  }
@@ -188,30 +247,37 @@ export function readLocalValues(rt, bytes) {
188
247
  case A.ECMA_ARRAY: {
189
248
  let result;
190
249
  if (marker === A.TYPED_OBJECT) {
191
- result = rt.allocate(rt.classByAlias(name(), true)) ?? rt.newObject([]);
250
+ result = rt.allocate(rt.classByAlias(this.name(), true)) ?? rt.newObject([]);
192
251
  }
193
252
  else {
194
253
  result = marker === A.ECMA_ARRAY ? rt.newArray([]) : rt.newObject([]);
195
254
  }
196
- const length = marker === A.ECMA_ARRAY ? u32() : 0;
197
- references.push(result);
255
+ const length = marker === A.ECMA_ARRAY ? this.u32() : 0;
256
+ this.references.push(result);
198
257
  for (;;) {
199
- const key = name();
200
- if (key === "" && bytes[at] === A.OBJECT_END) {
201
- at++;
258
+ const key = this.name();
259
+ if (key === "" && bytes[this.at] === A.OBJECT_END) {
260
+ this.at++;
202
261
  break;
203
262
  }
204
- rt.setProperty(result, rt.publicName(key), read(depth + 1));
263
+ rt.setProperty(result, rt.publicName(key), this.value(depth + 1));
205
264
  }
206
265
  if (marker === A.ECMA_ARRAY && length > result.$a.length) {
207
266
  result.$a.length = length;
208
267
  }
209
268
  return result;
210
269
  }
270
+ case AVMPLUS:
271
+ if (this.amf3) {
272
+ return this.amf3Value();
273
+ }
274
+ throw new RangeError(`Invalid ${this.what} AMF0 marker`);
211
275
  default:
212
- throw new RangeError("Invalid LocalConnection AMF0 marker");
276
+ throw new RangeError(`Invalid ${this.what} AMF0 marker`);
213
277
  }
214
- };
215
- const result = read(0);
278
+ }
279
+ }
280
+ export function readLocalValues(rt, bytes) {
281
+ const result = new Amf0Reader(rt, bytes).value();
216
282
  return result.$a;
217
283
  }
@@ -0,0 +1,162 @@
1
+ // Flash Remoting's packets for AS3's NetConnection.call, as adl sends and
2
+ // reads them. A packet is its version (0, or 3 where the connection's
3
+ // objectEncoding is AMF3), its headers and its calls, each call's
4
+ // arguments a strict array. In AMF0 every value is AMF0, the arguments
5
+ // sharing one table of references whose first object is 0. In AMF3 the
6
+ // strings, numbers, booleans, null and undefined are still AMF0, and every
7
+ // other value is AMF0's 0x11 followed by AMF3, the arguments of a call
8
+ // sharing one AMF3 writer's tables; a header's value is one of its own.
9
+ import { avm2 } from "../../../../runtime/index.js";
10
+ import * as A from "../../../avm1/amf.js";
11
+ import { Amf0Reader, Amf0Writer, AVMPLUS } from "./local-values.js";
12
+ const encoder = new TextEncoder();
13
+ const decoder = new TextDecoder();
14
+ /** Whether AMF3 writes `v` as AMF0 all the same: what has no AMF3 reference to share. */
15
+ function primitive(v) {
16
+ return (v === null || (typeof v !== "object" && typeof v !== "function") || v instanceof avm2.FloatValue);
17
+ }
18
+ /** `v`, a primitive, in AMF0 at the end of `out`. */
19
+ function writeAmf0Primitive(out, v) {
20
+ const w = new A.Writer(() => null, 8, null, null);
21
+ w.value(v instanceof avm2.FloatValue ? v.value : v);
22
+ out.write(w.finish());
23
+ }
24
+ /** AMF3 values, each after 0x11, into one buffer, sharing one AMF3 writer's tables. */
25
+ class Amf3Values {
26
+ bytes;
27
+ writer;
28
+ constructor(rt) {
29
+ this.bytes = new avm2.Bytes(rt, {});
30
+ this.writer = new avm2.Amf3Writer(rt, this.bytes);
31
+ }
32
+ value(v) {
33
+ if (primitive(v)) {
34
+ writeAmf0Primitive(this.bytes, v);
35
+ return;
36
+ }
37
+ this.bytes.write(Uint8Array.of(AVMPLUS));
38
+ this.writer.value(v);
39
+ }
40
+ finish() {
41
+ return this.bytes.buffer.slice(0, this.bytes.length);
42
+ }
43
+ }
44
+ /** A header's value: AMF0, itself its table's first object; or in AMF3, with tables of its own. */
45
+ export function remoteValue(rt, v, encoding) {
46
+ if (encoding === 3) {
47
+ const w = new Amf3Values(rt);
48
+ w.value(v);
49
+ return w.finish();
50
+ }
51
+ const w = new Amf0Writer(rt, Number.POSITIVE_INFINITY, 0, "Remoting");
52
+ w.value(v);
53
+ return w.out.finish();
54
+ }
55
+ /** A call's arguments: a strict array of them, in the connection's encoding. */
56
+ export function remoteArguments(rt, args, encoding) {
57
+ if (encoding === 3) {
58
+ const w = new Amf3Values(rt);
59
+ const head = new Uint8Array(5);
60
+ head[0] = A.STRICT_ARRAY;
61
+ new DataView(head.buffer).setUint32(1, args.length);
62
+ w.bytes.write(head);
63
+ for (const v of args) {
64
+ w.value(v);
65
+ }
66
+ return w.finish();
67
+ }
68
+ const w = new Amf0Writer(rt, Number.POSITIVE_INFINITY, 0, "Remoting");
69
+ w.out.bytes(A.STRICT_ARRAY);
70
+ w.out.u32(args.length);
71
+ for (const v of args) {
72
+ w.value(v);
73
+ }
74
+ return w.out.finish();
75
+ }
76
+ /** Bytes, joined once. */
77
+ function join(chunks) {
78
+ let length = 0;
79
+ for (const chunk of chunks) {
80
+ length += chunk.length;
81
+ }
82
+ const out = new Uint8Array(length);
83
+ let at = 0;
84
+ for (const chunk of chunks) {
85
+ out.set(chunk, at);
86
+ at += chunk.length;
87
+ }
88
+ return out;
89
+ }
90
+ /** A u16-long UTF-8 string, no marker. */
91
+ function utf(text) {
92
+ const b = encoder.encode(text).subarray(0, 0xffff);
93
+ return join([Uint8Array.of(b.length >> 8, b.length & 0xff), b]);
94
+ }
95
+ const u16 = (n) => Uint8Array.of((n >> 8) & 0xff, n & 0xff);
96
+ const u32 = (n) => Uint8Array.of((n >>> 24) & 0xff, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff);
97
+ /** A packet of the calls, each answered at /n, its number on the connection. */
98
+ export function remotePacket(encoding, headers, calls) {
99
+ const out = [u16(encoding === 3 ? 3 : 0), u16(headers.length)];
100
+ for (const h of headers) {
101
+ out.push(utf(h.name), Uint8Array.of(h.mustUnderstand ? 1 : 0), u32(h.value.length), h.value);
102
+ }
103
+ out.push(u16(calls.length));
104
+ for (const call of calls) {
105
+ out.push(utf(call.method), utf(`/${call.id}`), u32(call.body.length), call.body);
106
+ }
107
+ return join(out);
108
+ }
109
+ /**
110
+ * An answer's messages, their targets and values; "version" for one that
111
+ * is no packet of version 0 or 3, which Flash reports as BadVersion.
112
+ * Each value is read on its own, AMF3 after 0x11 included; a message's
113
+ * length, where it gives one, says where the next begins.
114
+ */
115
+ export function remoteMessages(rt, bytes) {
116
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
117
+ try {
118
+ const version = view.getUint16(0);
119
+ if (version !== 0 && version !== 3) {
120
+ return "version";
121
+ }
122
+ let at = 2;
123
+ const text = () => {
124
+ const n = view.getUint16(at);
125
+ at += 2;
126
+ if (at + n > bytes.length) {
127
+ throw new RangeError("past the end");
128
+ }
129
+ const s = decoder.decode(bytes.subarray(at, at + n));
130
+ at += n;
131
+ return s;
132
+ };
133
+ const value = () => {
134
+ const length = view.getUint32(at);
135
+ at += 4;
136
+ const start = at;
137
+ const reader = new Amf0Reader(rt, bytes, at, true, "Remoting");
138
+ const v = reader.value();
139
+ at = length !== 0xffffffff && start + length <= bytes.length ? start + length : reader.at;
140
+ return v;
141
+ };
142
+ const headers = view.getUint16(at);
143
+ at += 2;
144
+ for (let i = 0; i < headers; i++) {
145
+ text();
146
+ at += 1;
147
+ value();
148
+ }
149
+ const count = view.getUint16(at);
150
+ at += 2;
151
+ const messages = [];
152
+ for (let i = 0; i < count; i++) {
153
+ const target = text();
154
+ text();
155
+ messages.push([target, value()]);
156
+ }
157
+ return messages;
158
+ }
159
+ catch {
160
+ return "version";
161
+ }
162
+ }
@@ -342,7 +342,8 @@ export function textFieldNatives(s) {
342
342
  }
343
343
  set autoSize(v) {
344
344
  this.$display.autoSize = choice(v, ["none", "left", "right", "center"], "autoSize");
345
- this.$display.fit();
345
+ // Its fit waits for its size to be asked, or the frame, as one a set width leaves (adl).
346
+ this.$display.deferFit();
346
347
  }
347
348
  get antiAliasType() {
348
349
  return this.$display.antiAliasType;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swf2es/player-hosts",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Optional Node and WebSocket transports, worker threads, an IndexedDB module cache and precompiled modules for the swf2es player",
5
5
  "keywords": [
6
6
  "swf",
@@ -60,6 +60,6 @@
60
60
  "LICENSE.third-party"
61
61
  ],
62
62
  "dependencies": {
63
- "@swf2es/player": "0.1.6"
63
+ "@swf2es/player": "0.1.8"
64
64
  }
65
65
  }