This commit is contained in:
Michael "Z" Goddard 2018-12-10 14:08:55 -05:00
parent cbb0b8e321
commit 3301550a8f
No known key found for this signature in database
GPG key ID: 762CD40DD5349872
12 changed files with 659 additions and 10 deletions

View file

@ -1,9 +1,56 @@
/**
* @typedef {function} PacketConstructor
*/
/**
* A packet of bytes represented with getter/setter properties for decoding and
* encoding values mapped to names, located at known offsets.
*
* ```js
* // Defining a subclass:
* import {Packet} from '../coders/byte-packets';
* import {Uint8, Uint16LE} from '../coders/byte-primitives';
*
* class MyIdentifiedUint16 extends Packet.extend({
* binaryType: Uint8,
* value: Uint16LE
* }) {}
*
* Packet.initConstructor(MyIdentifiedUint16);
*
* // One way to use it:
* const indentifiedUint16 = new MyIdentifiedUint16(uint8a, position);
* indentifiedUint16.binaryType = IDENTIFIED_UINT_16;
* indentifiedUint16.value = value;
* ```
*/
class Packet {
/**
* @param {Uint8Array=} [uint8a=new Uint8Array(this.size)] - byte array to
* encode to and decode from
* @param {number=} offset - offset in addition to the member offsets to
* encode to and decode from
*/
constructor (uint8a = new Uint8Array(this.size), offset = 0) {
/**
* Byte array to encode to and decode from.
* @type {Uint8Array}
*/
this.uint8a = uint8a;
/**
* Offset in addition to the member offsets to encode to and decode
* from.
* @type {number}
*/
this.offset = offset;
}
/**
* Check that the decoded values of this Packet match the values in other.
* @param {object} other - object to match against
* @returns {boolean} true if all keys in other match values in this packet
*/
equals (other) {
for (const key in other) {
if (this[key] !== other[key]) {
@ -26,11 +73,23 @@ class Packet {
return obj;
}
/**
* Initialize the Packet subclass constructor for easy access to static
* members like size.
* @param {function} PacketConstructor - constuctor function for the Packet
* subclass
* @returns {function} initialized constructor
*/
static initConstructor (PacketConstructor) {
PacketConstructor.size = PacketConstructor.prototype.size;
return PacketConstructor;
}
/**
* Extend a subclass of Packet with given BytePrimitive members.
* @param {object} shape - shape of the packet defined with BytePrimitives
* @returns {function} Packet subclass constructor
*/
static extend (shape) {
const DefinedPacket = class extends Packet {
get shape () {

View file

@ -4,6 +4,11 @@ const notImplemented = () => {
throw new Error('Not implemented');
};
/**
* Is the host computer little or big endian.
* @const IS_HOST_LITTLE_ENDIAN
* @type {boolean}
*/
const IS_HOST_LITTLE_ENDIAN = (() => {
const ab16 = new Uint16Array(1);
const ab8 = new Uint8Array(ab16.buffer);
@ -11,7 +16,54 @@ const IS_HOST_LITTLE_ENDIAN = (() => {
return ab8[0] === 0xbb;
})();
/**
* @callback BytePrimitive~sizeOfCallback
* @param {Uint8Array} uint8a
* @param {number} position
* @returns {number}
*/
/**
* @callback BytePrimitive~writeSizeOfCallback
* @param {Uint8Array} uint8a
* @param {number} position
* @param {*} value
* @returns {number}
*/
/**
* @callback BytePrimitive~writeCallback
* @param {Uint8Array} uint8a
* @param {number} position
* @param {*} value
*/
/**
* An interface for reading and writing binary values to typed arrays.
*
* Combined with {@link Packet Packet} this makes reading and writing packets
* of binary values easy.
*/
class BytePrimitive {
/**
* @constructor
* @param {object} options - Options to initialize BytePrimitive instance
* with.
* @param {number} [options.size=0] - Fixed size of the BytePrimitive.
* Should be 0 if the primitive has a variable size.
* @param {BytePrimitive~sizeOfCallback} [options.sizeOf=() => size] -
* Variable size of the primitive depending on its value stored in the
* array.
* @param {BytePrimitive~writeSizeOfCallback} [options.writeSizeOf] -
* Variable size of the primitive depending on the value being written.
* @param {TypedArray} [options.toBytes=new Uint8Array(1)] - Temporary
* space to copy bytes to/from to translate between a value and its
* representative byte set.
* @param {BytePrimitive#read} options.read - How is a value read
* at the given position of the array.
* @param {BytePrimitive~writeCallback} [options.write] - How to write a
* value to the array at the given position.
*/
constructor ({
size = 0,
sizeOf = () => size,
@ -31,6 +83,15 @@ class BytePrimitive {
this.write = write;
}
/**
* Create an object that can be used with `Object.defineProperty` to read
* and write values offset by `position` and the object's `this.offset`
* from `this.uint8a` by getting or setting the property.
* @param {number} position - Additional offset with `this.offset` to read
* from or write to.
* @returns {object} - A object that can be passed as the third argument to
* `Object.defineProperty`.
*/
asPropertyObject (position) {
const _this = this;
@ -46,8 +107,22 @@ class BytePrimitive {
enumerable: true
};
}
/**
* Read a value from `position` in `uint8a`.
* @param {Uint8Array} uint8a - Array to read from.
* @param {number} position - Position in `uint8a` to read from.
* @returns {*} - Value read from `uint8a` at `position`.
*/
read () {
return null;
}
}
/**
* @const Uint8
* @type {BytePrimitive}
*/
const Uint8 = new BytePrimitive({
size: 1,
read (uint8a, position) {
@ -97,10 +172,18 @@ if (IS_HOST_LITTLE_ENDIAN) {
BE16 = HOSTBE_BE16;
}
/**
* @const Uint16BE
* @type {BytePrimitive}
*/
const Uint16BE = new BytePrimitive(Object.assign({}, BE16, {
toBytes: new Uint16Array(1)
}));
/**
* @const Int16BE
* @type {BytePrimitive}
*/
const Int16BE = new BytePrimitive(Object.assign({}, BE16, {
toBytes: new Int16Array(1)
}));
@ -124,6 +207,7 @@ const HOSTLE_BE32 = {
return value;
}
};
const HOSTBE_BE32 = {
size: 4,
// toBytes: Defined by instance.
@ -151,10 +235,18 @@ if (IS_HOST_LITTLE_ENDIAN) {
BE32 = HOSTBE_BE32;
}
/**
* @const Int32BE
* @type {BytePrimitive}
*/
const Int32BE = new BytePrimitive(Object.assign({}, BE32, {
toBytes: new Int32Array(1)
}));
/**
* @const Uint32BE
* @type {BytePrimitive}
*/
const Uint32BE = new BytePrimitive(Object.assign({}, BE32, {
toBytes: new Uint32Array(1)
}));
@ -166,6 +258,10 @@ if (IS_HOST_LITTLE_ENDIAN) {
LE16 = HOSTLE_BE16;
}
/**
* @const Uint16LE
* @type {BytePrimitive}
*/
const Uint16LE = new BytePrimitive(Object.assign({}, LE16, {
toBytes: new Uint16Array(1)
}));
@ -177,6 +273,10 @@ if (IS_HOST_LITTLE_ENDIAN) {
LE32 = HOSTLE_BE32;
}
/**
* @const Uint32LE
* @type {BytePrimitive}
*/
const Uint32LE = new BytePrimitive(Object.assign({}, LE32, {
toBytes: new Uint32Array(1)
}));
@ -218,11 +318,21 @@ if (IS_HOST_LITTLE_ENDIAN) {
BEDOUBLE = HOSTBE_BEDOUBLE;
}
/**
* @const DoubleBE
* @type {BytePrimitive}
*/
const DoubleBE = new BytePrimitive(Object.assign({}, BEDOUBLE, {
toBytes: new Float64Array(1)
}));
/**
* @extends BytePrimitive
*/
class FixedAsciiString extends BytePrimitive {
/**
* @param {number} size - Number of bytes the FixedAsciiString uses.
*/
constructor (size) {
super({
size,

View file

@ -1,13 +1,42 @@
import {assert} from '../util/assert';
/**
* Read and write a stream of {@link BytePrimitive}s, {@link Packet}s, or byte
* arrays to an ArrayBuffer.
*/
class ByteStream {
/**
* @param {ArrayBuffer} buffer - The ArrayBuffer to read from or write to.
* @param {number=} [position=0] - The position to start reading or writing
* from in the ArrayBuffer.
*/
constructor (buffer, position = 0) {
/**
* The ArrayBuffer to read from or write to.
* @type {ArrayBuffer}
*/
this.buffer = buffer;
/**
* The position to start reading or writing from in the ArrayBuffer.
* @type {number}
*/
this.position = position;
/**
* The typed array view of the buffer to read and write with.
* @type {Uint8Array}
*/
this.uint8a = new Uint8Array(this.buffer);
}
/**
* Read one instance of a given BytePrimitive and increment position based
* on the size of that value.
* @param {BytePrimitive} member - BytePrimitive to read and increment size
* with.
* @returns {*} Return the value produced by the BytePrimitive.
*/
read (member) {
const value = member.read(this.uint8a, this.position);
if (member.size === 0) {
@ -18,12 +47,25 @@ class ByteStream {
return value;
}
/**
* Read one instance of a given Packet subclass and increment position
* based on the size of that value.
* @param {PacketConstructor} StructType - Packet subclass constructor that
* can read from the current stream position.
* @returns {Packet} Instance of a Packet pointed at the position of the
* stream before calling readStruct.
*/
readStruct (StructType) {
const obj = new StructType(this.uint8a, this.position);
this.position += StructType.size;
return obj;
}
/**
* Resize the internal buffer to allow for the needed amount of space.
* @param {number} needed - How many bytes need to fit in the buffer.
* @private
*/
resize (needed) {
if (this.buffer.byteLength < needed) {
const uint8a = this.uint8a;
@ -35,6 +77,14 @@ class ByteStream {
}
}
/**
* Write a value to the stream (with a BytePrimitive defining how to do so)
* and increment the position.
* @param {BytePrimitive} member - BytePrimitive to define how to write the
* value.
* @param {*} value - Value to write.
* @returns {*} Value passed to the method.
*/
write (member, value) {
if (member.size === 0) {
this.resize(this.position + member.writeSizeOf(value));
@ -51,6 +101,14 @@ class ByteStream {
return value;
}
/**
* Write data to the stream structured by a given Packet subclass
* constructor and increment the position.
* @param {PacketConstructor} StructType - The Packet subclass constructor
* defining how to write the data.
* @param {object} data - Data to write.
* @returns {Packet} - Constructed packet after writing data.
*/
writeStruct (StructType, data) {
this.resize(this.position + StructType.size);
@ -59,6 +117,14 @@ class ByteStream {
return st;
}
/**
* Write bytes from the given Uint8Array array to the stream and increment
* the position.
* @param {Uint8Array} bytes - Bytes to write to the stream.
* @param {number=} [start=0] - Where in bytes to start writing from.
* @param {number=} [end=bytes.length] - Where in bytes to stop writing, excluding position at bytes[end].
* @returns {Uint8Array} Passed bytes Uint8Array.
*/
writeBytes (bytes, start = 0, end = bytes.length) {
assert(bytes instanceof Uint8Array, 'writeBytes must be passed an Uint8Array');

View file

@ -3,10 +3,29 @@ import {assert} from './util/assert';
import {Packet} from './coders/byte-packets';
import {FixedAsciiString, Uint8, Uint32BE} from './coders/byte-primitives';
/**
* @augments Packet
*/
class SB1Signature extends Packet.extend({
/**
* 10 byte ascii string equaling `'ScratchV01'` or `'ScratchV02'`.
* @type {string}
* @memberof SB1Signature#
*/
version: new FixedAsciiString(10),
/**
* Number of bytes in the info block.
* @type {number}
* @memberof SB1Signature#
*/
infoByteLength: Uint32BE
}) {
/**
* Is this a valid SB1Signature?
* @method
* @throws {AssertionError} Throws if it is not valid.
*/
validate () {
assert(
this.equals({version: 'ScratchV01'}) ||

View file

@ -6,6 +6,10 @@ import {IS_HOST_LITTLE_ENDIAN, Int16BE, BytePrimitive, Uint8, Uint32BE} from '..
const BUFFER_TOO_BIG = 10 * 1024 * 1024;
/**
* @const ReferenceBE
* @type BytePrimitive
*/
let ReferenceBE;
if (IS_HOST_LITTLE_ENDIAN) {
ReferenceBE = new BytePrimitive({
@ -31,6 +35,10 @@ if (IS_HOST_LITTLE_ENDIAN) {
});
}
/**
* @const LargeInt
* @type BytePrimitive
*/
const LargeInt = new BytePrimitive({
sizeOf (uint8a, position) {
const count = Int16BE.read(uint8a, position);
@ -48,6 +56,10 @@ const LargeInt = new BytePrimitive({
}
});
/**
* @const AsciiString
* @type BytePrimitive
*/
const AsciiString = new BytePrimitive({
sizeOf (uint8a, position) {
const count = Uint32BE.read(uint8a, position);
@ -65,6 +77,10 @@ const AsciiString = new BytePrimitive({
}
});
/**
* @const Bytes
* @type BytePrimitive
*/
const Bytes = new BytePrimitive({
sizeOf (uint8a, position) {
return Uint32BE.size + Uint32BE.read(uint8a, position);
@ -79,6 +95,10 @@ const Bytes = new BytePrimitive({
}
});
/**
* @const SoundBytes
* @type BytePrimitive
*/
const SoundBytes = new BytePrimitive({
sizeOf (uint8a, position) {
return Uint32BE.size + (Uint32BE.read(uint8a, position) * 2);
@ -94,6 +114,10 @@ const SoundBytes = new BytePrimitive({
}
});
/**
* @const Bitmap32BE
* @type BytePrimitive
*/
const Bitmap32BE = new BytePrimitive({
sizeOf (uint8a, position) {
return Uint32BE.size + (Uint32BE.read(uint8a, position) * Uint32BE.size);
@ -121,6 +145,10 @@ if (typeof TextDecoder === 'undefined') {
decoder = new TextDecoder();
}
/**
* @const UTF8
* @type BytePrimitive
*/
const UTF8 = new BytePrimitive({
sizeOf (uint8a, position) {
return Uint32BE.size + Uint32BE.read(uint8a, position);
@ -135,6 +163,10 @@ const UTF8 = new BytePrimitive({
}
});
/**
* @const OpaqueColor
* @type BytePrimitive
*/
const OpaqueColor = new BytePrimitive({
size: 4,
read (uint8a, position) {
@ -147,6 +179,10 @@ const OpaqueColor = new BytePrimitive({
}
});
/**
* @const TranslucentColor
* @type BytePrimitive
*/
const TranslucentColor = new BytePrimitive({
size: 5,
read (uint8a, position) {

View file

@ -1,13 +1,35 @@
/**
* An iterator that only takes bytes up to a certain position.
*
* Take iterators constrain the number of times an inner iterator can return
* values. Normally it constrains the number of returned values.
* ByteTakeIterator instead constrains the number of bytes the inner iterator
* may take from its stream before ByteTakeIterator returns done objects.
*
* Primarily used to wrap {@link FieldIterator}.
*/
class ByteTakeIterator {
/**
* @param {{stream: ByteStream}} iter - Iterator with `stream` member.
* @param {number} [maxPosition=Infinity] - Position `stream` may not go
* beyond when yielding the next value.
*/
constructor (iter, maxPosition = Infinity) {
this.iter = iter;
this.maxPosition = maxPosition;
}
/**
* @returns {ByteTakeIterator} - Returns itself.
*/
[Symbol.iterator] () {
return this;
}
/**
* @returns {{value: *, done: boolean}} - Return the next value or indicate
* the Iterator has reached its end.
*/
next () {
if (this.iter.stream.position >= this.maxPosition) {
return {

View file

@ -7,7 +7,22 @@ import {
import {BuiltinObjectHeader, FieldObjectHeader, Header, Reference, Value} from './fields';
import {TYPES} from './ids';
/**
* Consume values for the byte stream with a iterator-like interface.
*/
class Consumer {
/**
* @param {object} options - Define the consumer.
* @param {function} [options.type=Value] - The {@link Field} type to
* create.
* @param {BytePrimitive} options.read - How to read the third Field
* argument. The third field argument is the value the field represented in
* the `.sb` file. It is either the Value's value, the Reference's index,
* or the Header's field size.
* @param {function} [options.value] - How to produce the third Field
* argument from a stream. Defaults to `stream =>
* stream.read(options.read)`.
*/
constructor ({
type = Value,
read,
@ -17,6 +32,13 @@ class Consumer {
this.value = value;
}
/**
* @param {ByteStream} stream - Stream to read from.
* @param {TYPES} classId - FieldObject TYPES identifying the value to read.
* @param {number} position - Position in the stream the classId was read
* from.
* @returns {{value: *, done: boolean}} - An iterator.next() return value.
*/
next (stream, classId, position) {
return {
value: new this.type(classId, position, this.value(stream)),
@ -25,6 +47,10 @@ class Consumer {
}
}
/**
* @const CONSUMER_PROTOS
* @type {Object.<number, {type, read, value}>}
*/
const CONSUMER_PROTOS = {
[TYPES.NULL]: {value: () => null},
[TYPES.TRUE]: {value: () => true},
@ -61,6 +87,10 @@ const CONSUMER_PROTOS = {
[TYPES.OBJECT_REF]: {type: Reference, read: ReferenceBE}
};
/**
* @const CONSUMERS
* @type {Array.<Consumer|null>}
*/
const CONSUMERS = Array.from(
{length: 256},
(_, i) => {
@ -74,16 +104,29 @@ const builtinConsumer = new Consumer({
value: () => null
});
/**
* Field iterator.
*/
class FieldIterator {
/**
* @param {ArrayBuffer} buffer - Buffer to read from.
* @param {number} position - Position in buffer to start at.
*/
constructor (buffer, position) {
this.buffer = buffer;
this.stream = new ByteStream(buffer, position);
}
/**
* @returns {FieldIterator} - Returns this.
*/
[Symbol.iterator] () {
return this;
}
/**
* @returns {{value: *, done: boolean}} - An iterator.next() value.
*/
next () {
if (this.stream.position >= this.stream.uint8a.length) {
return {

View file

@ -4,13 +4,44 @@ const toTitleCase = str => (
str.toLowerCase().replace(/_(\w)/g, ([, letter]) => letter.toUpperCase())
);
/**
* A object representation of a {@link Header} collecting the given {@link
* Header#size} in fields.
*/
class FieldObject {
/**
* @param {TYPES} classId - {@link TYPES} id that informs what the shape of
* this object is.
* @param {number} version - Version number of this object. Some items in
* the same class may have different content and so will be different
* versions.
* @param {Array.<Field>} fields - An array of fields in this FieldObject.
*/
constructor ({classId, version, fields}) {
/** @type {number} */
this.classId = classId;
/** @type {number} */
this.version = version;
/** @type {Array.<Field>} */
this.fields = fields;
}
/**
* @type {object}
*/
get FIELDS () {
return [];
}
/**
* @type {Array.<Field>}
*/
get RAW_FIELDS () {
return this.fields;
}
string (field) {
return String(this.fields[field]);
}
@ -30,16 +61,20 @@ class FieldObject {
return this.constructor.name;
}
/**
* Define a FieldObject subclass by mapping field names to indices in
* {@link FieldObject#fields}.
* @param {object} FIELDS - Mapping of ALL_CAPS keys to index in {@link
* FieldObject#fields}.
* @param {function} [Super] - Parent class of the returned subclass.
* @returns {function} - FieldObject subclass constructor.
*/
static define (FIELDS, Super = FieldObject) {
class Base extends Super {
class DefinedObject extends Super {
get FIELDS () {
return FIELDS;
}
get RAW_FIELDS () {
return this.fields;
}
static get FIELDS () {
return FIELDS;
}
@ -47,14 +82,14 @@ class FieldObject {
Object.keys(FIELDS).forEach(key => {
const index = FIELDS[key];
Object.defineProperty(Base.prototype, toTitleCase(key), {
Object.defineProperty(DefinedObject.prototype, toTitleCase(key), {
get () {
return this.fields[index];
}
});
});
return Base;
return DefinedObject;
}
}

View file

@ -1,8 +1,30 @@
import {TYPES} from './ids';
/**
* An abstract value contained in a `.sb` file.
*
* `.sb` files are made up of two blocks of Fields. Each field in the binary
* file defines its "class" and is possibly followed by some binary data
* depending on the class. Each class explicitly defines what follows. Knowing
* all the possible classes each block can be broken up into a series of Field
* objects.
*/
class Field {
/**
* @param {TYPES} classId - The class identifier of this Field.
* @param {number} position - Byte position in the `.sb` file.
*/
constructor (classId, position) {
/**
* The class identifier of this Field.
* @type {TYPES}
*/
this.classId = classId;
/**
* Byte position in the `.sb` file.
* @type {number}
*/
this.position = position;
}
}
@ -12,9 +34,24 @@ const valueOf = obj => {
return obj;
};
/**
* A concrete value contained in a `.sb` file.
* @extends Field
*/
class Value extends Field {
/**
* @param {TYPES} classId - The class identifier of this Field.
* @param {number} position - Byte position in the `.sb` file.
* @param {*} value - A value decoded according to `classId` from an `.sb`
* file.
*/
constructor (classId, position, value) {
super(classId, position);
/**
* A value decoded according to `classId` from an `.sb` file.
* @type {*}
*/
this.value = value;
}
@ -38,16 +75,50 @@ class Value extends Field {
}
}
/**
* A header for a FieldObject representing its class and how many fields are in
* the object.
*
* The `size` of a header is the number of Fields that appear in the byte
* stream after the header that are related to the header. That set of `size`
* length Fields make up a FieldObject of `classId` passed to this header.
* @extends Field
*/
class Header extends Field {
/**
* @param {TYPES} classId - The class identifier of this Field.
* @param {number} position - Byte position in the `.sb` file.
* @param {number} size - The number of fields to collect.
*/
constructor (classId, position, size) {
super(classId, position);
/**
* The number of fields to collect.
* @type {number}
*/
this.size = size;
}
}
/**
* A integer reference of an object in an array produced by TypeIterator of
* Values and FieldObjects.
* @extends Field
*/
class Reference extends Field {
/**
* @param {TYPES} classId - The class identifier of this Field.
* @param {number} position - Byte position in the `.sb` file.
* @param {number} index - The index this Reference refers to.
*/
constructor (classId, position, index) {
super(classId, position);
/**
* The index this Reference refers to.
* @type {number}
*/
this.index = index;
}
@ -56,15 +127,38 @@ class Reference extends Field {
}
}
/**
* An object header of 0 size.
* @extends Header
*/
class BuiltinObjectHeader extends Header {
constructor (classId, position) {
super(classId, position, 0);
}
}
/**
* An object header with an id more than 99, a version, and a size. The version
* and size appear in the `sb` file as one byte for version followed by another
* byte for the size.
* @extends Header
*/
class FieldObjectHeader extends Header {
/**
* @param {TYPES} classId - The class identifier of this Field.
* @param {number} position - Byte position in the `.sb` file.
* @param {number} version - The version of this instance of a certain
* value.
* @param {number} size - The number of fields in this object.
*/
constructor (classId, position, version, size) {
super(classId, position, size);
/**
* The version of this instance of a certain value.
* @type {number}
*/
this.version = version;
}
}

View file

@ -1,50 +1,168 @@
/**
* A numeric identifier for each possible class of {@link Field} that can be in
* a `.sb` file.
* @enum {number}
*/
const TYPES = {
/** A `null` {@link Value}. No data is stored after the class id. */
NULL: 1,
/** A `true` {@link Value}. No data is stored after the class id. */
TRUE: 2,
/** A `false` {@link Value}. No data is stored after the class id. */
FALSE: 3,
/** A small integer {@link Value}. The next 4 bytes represent an integer. */
SMALL_INT: 4,
/** A small integer {@link Value}. The next 2 bytes represent an integer. */
SMALL_INT_16: 5,
/** A large integer {@link Value}. The value is a variable number of bytes.
* The next byte defines the number of bytes after that represent the
* integer. The integer's bytes are stored least value first (little
* endian). */
LARGE_INT_POSITIVE: 6,
/** A large integer {@link Value}. The value is a variable number of bytes.
* The next byte defines the number of bytes after that represent the
* integer. The integer's bytes are stored least value first (little
* endian). */
LARGE_INT_NEGATIVE: 7,
/** A floating point {@link Value}. The next 8 bytes are stored as a double
* precision floating point value. */
FLOATING: 8,
/** A ascii string {@link Value}. The next 4 bytes defines the number of
* following bytes that make up the string. */
STRING: 9,
/** A ascii string {@link Value}. The next 4 bytes defines the number of
* following bytes that make up the string. */
SYMBOL: 10,
/** A sequence of bytes ({@link Value}). The next 4 bytes defines the
* number of bytes in the sequence. */
BYTES: 11,
/** A sequence of 16 bit samples ({@link Value}). The next 4 bytes defines
* the number of samples in the sequence. */
SOUND: 12,
/** A sequence of 32 bit color integers ({@link Value}). The next 4 bytes
* defines the number of colors in the bitmap. */
BITMAP: 13,
/** A utf8 string {@link Value}. The next 4 bytes defines the number of
* bytes used by the string. */
UTF8: 14,
/** An array {@link Header}. The next 4 bytes defines the following number
* of fields in the array. */
ARRAY: 20,
/** An array {@link Header}. The next 4 bytes defines the following number
* of fields in the array. */
ORDERED_COLLECTION: 21,
/** An array {@link Header}. The next 4 bytes defines the following number
* of fields in the array. */
SET: 22,
/** An array {@link Header}. The next 4 bytes defines the following number
* of fields in the array. */
IDENTITY_SET: 23,
/** A dictionary {@link Header}. The next 4 bytes defines the following
* number of key/value field pairs in the dictionary. */
DICTIONARY: 24,
/** A dictionary {@link Header}. The next 4 bytes defines the following
* number of key/value field pairs in the dictionary. */
IDENTITY_DICTIONARY: 25,
/** A color {@link Value}. The next 4 bytes represents the color. */
COLOR: 30,
/** A color {@link Value}. The next 4 bytes represents the red, green, and
* blue subpixels. The following byte represents the alpha. */
TRANSLUCENT_COLOR: 31,
/** A 2 field point {@link Header}. The next 2 fields are the x and y
* values of this point. */
POINT: 32,
/** A 4 field rectangle {@link Header}. The next 4 fields are the x, y, x2,
* y2 values of this rectangle. */
RECTANGLE: 33,
/** A 5 field image {@link Header}. The next 5 fields are the width,
* height, bit depth, unused, and bytes. */
FORM: 34,
/** A 6 field image {@link Header}. The next 6 fields are the width,
* height, bit depth, unsued, bytes and colormap. */
SQUEAK: 35,
/** An object {@link Reference} to a position in the top level array of fields in a
* block. */
OBJECT_REF: 99,
/** A variable {@link FieldObjectHeader}. */
MORPH: 100,
/** A variable {@link FieldObjectHeader}. */
ALIGNMENT: 104,
// Called String in Scratch 2. To reduce confusion this is called
// STATIC_STRING to differentiate it from STRING in this codebase.
/** A variable {@link FieldObjectHeader}.
*
* In Scratch 2 this is called String. To reduce confusion in the set of
* types, this is called STATIC_STRING in this converter. */
STATIC_STRING: 105,
/** A variable {@link FieldObjectHeader}. */
UPDATING_STRING: 106,
/** A variable {@link FieldObjectHeader}. */
SAMPLED_SOUND: 109,
/** A variable {@link FieldObjectHeader}. */
IMAGE_MORPH: 110,
/** A variable {@link FieldObjectHeader}. */
SPRITE: 124,
/** A variable {@link FieldObjectHeader}. */
STAGE: 125,
/** A variable {@link FieldObjectHeader}. */
WATCHER: 155,
/** A variable {@link FieldObjectHeader}. */
IMAGE_MEDIA: 162,
/** A variable {@link FieldObjectHeader}. */
SOUND_MEDIA: 164,
/** A variable {@link FieldObjectHeader}. */
MULTILINE_STRING: 171,
/** A variable {@link FieldObjectHeader}. */
WATCHER_READOUT_FRAME: 173,
/** A variable {@link FieldObjectHeader}. */
WATCHER_SLIDER: 174,
/** A variable {@link FieldObjectHeader}. */
LIST_WATCHER: 175
};
/**
* A inverted map of TYPES. Map id numbers to their string names.
* @type {object.<number, string>}
*/
const TYPE_NAMES = Object.entries(TYPES)
.reduce((carry, [key, value]) => {
carry[value] = key;

View file

@ -6,8 +6,20 @@ import {FieldObject} from './field-object';
import {value as valueOf} from './fields';
import {TYPES} from './ids';
/**
* @extends FieldObject
*/
class PointData extends FieldObject.define({
/**
* @memberof PointData#
* @type {Value}
*/
X: 0,
/**
* @memberof PointData#
* @type {Value}
*/
Y: 1
}) {}
@ -40,14 +52,43 @@ const _bgra2rgbaInPlace = uint8a => {
return uint8a;
};
/**
* @extends FieldObject
*/
class ImageData extends FieldObject.define({
/**
* @memberof ImageData#
* @type {Value}
*/
WIDTH: 0,
/**
* @memberof ImageData#
* @type {Value}
*/
HEIGHT: 1,
/**
* @memberof ImageData#
* @type {Value}
*/
DEPTH: 2,
SOMETHING: 3,
/**
* @memberof ImageData#
* @type {Value}
*/
BYTES: 4,
/**
* @memberof ImageData#
* @type {Value}
*/
COLORMAP: 5
}) {
/**
* @type {Uint8Array}
*/
get decoded () {
if (!this._decoded) {
this._decoded = _bgra2rgbaInPlace(new Uint8Array(
@ -63,6 +104,9 @@ class ImageData extends FieldObject.define({
return this._decoded;
}
/**
* @type {string}
*/
get extension () {
return 'uncompressed';
}

View file

@ -1,3 +1,6 @@
/**
* A `scratch-sb1-converter` assertion.
*/
class AssertionError extends Error {}
const assert = function (test, message) {