mirror of
https://github.com/scratchfoundation/scratch-sb1-converter.git
synced 2025-02-16 03:29:45 -05:00
docs
This commit is contained in:
parent
cbb0b8e321
commit
3301550a8f
12 changed files with 659 additions and 10 deletions
|
@ -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 () {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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');
|
||||
|
||||
|
|
|
@ -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'}) ||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
/**
|
||||
* A `scratch-sb1-converter` assertion.
|
||||
*/
|
||||
class AssertionError extends Error {}
|
||||
|
||||
const assert = function (test, message) {
|
||||
|
|
Loading…
Reference in a new issue