parse animation data!
also clean up some offsetting stuff a bit, by adding a RAII-like utility that lets the code temporairly offset elsewhere.
This commit is contained in:
parent
3718b508a5
commit
9d88e332f5
7 changed files with 603 additions and 348 deletions
|
@ -68,7 +68,15 @@ export class BufferStream {
|
||||||
readU32LE() { return this.readImpl(DataView.prototype.getUint32, 4, true); }
|
readU32LE() { return this.readImpl(DataView.prototype.getUint32, 4, true); }
|
||||||
readU32BE() { return this.readImpl(DataView.prototype.getUint32, 4, false); }
|
readU32BE() { return this.readImpl(DataView.prototype.getUint32, 4, false); }
|
||||||
|
|
||||||
// converts easy!
|
// Use this for temporary offset modification, e.g: when reading
|
||||||
|
// a structure *pointed to* inside another structure.
|
||||||
|
withOffset(where: number, cb: () => void) {
|
||||||
|
let last = this.tell();
|
||||||
|
this.seek(where, SeekDir.BEG);
|
||||||
|
cb();
|
||||||
|
this.seek(last, SeekDir.BEG);
|
||||||
|
}
|
||||||
|
|
||||||
readBool() : boolean {
|
readBool() : boolean {
|
||||||
let res = this.readU8();
|
let res = this.readU8();
|
||||||
return res != 0;
|
return res != 0;
|
||||||
|
@ -80,7 +88,8 @@ export class BufferStream {
|
||||||
for(let i = 0; i < len; ++i)
|
for(let i = 0; i < len; ++i)
|
||||||
str += String.fromCharCode(charReader.call(this));
|
str += String.fromCharCode(charReader.call(this));
|
||||||
|
|
||||||
// dispose of a nul terminator
|
// dispose of a nul terminator. We don't support other bare Agent formats,
|
||||||
|
// so we shouldn't need to add the "support" for that.
|
||||||
charReader.call(this);
|
charReader.call(this);
|
||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
@ -93,9 +102,13 @@ export class BufferStream {
|
||||||
return this.readString(len, charReader);
|
return this.readString(len, charReader);
|
||||||
}
|
}
|
||||||
|
|
||||||
readDataChunk(lengthReader: (this: BufferStream) => number = BufferStream.prototype.readU32LE) {
|
readDataChunkBuffer(lengthReader: (this: BufferStream) => number = BufferStream.prototype.readU32LE) {
|
||||||
let len = lengthReader.call(this);
|
let len = lengthReader.call(this);
|
||||||
return this.subBuffer(len).raw();
|
return this.subBuffer(len);
|
||||||
|
}
|
||||||
|
|
||||||
|
readDataChunk(lengthReader: (this: BufferStream) => number = BufferStream.prototype.readU32LE) {
|
||||||
|
return this.readDataChunkBuffer(lengthReader).raw();
|
||||||
}
|
}
|
||||||
|
|
||||||
// reads a counted list. The length reader is on the other end so you don't need to specify it
|
// reads a counted list. The length reader is on the other end so you don't need to specify it
|
||||||
|
@ -103,6 +116,8 @@ export class BufferStream {
|
||||||
readCountedList<TObject>(objReader: (stream: BufferStream) => TObject, lengthReader: (this: BufferStream) => number = BufferStream.prototype.readU32LE): TObject[] {
|
readCountedList<TObject>(objReader: (stream: BufferStream) => TObject, lengthReader: (this: BufferStream) => number = BufferStream.prototype.readU32LE): TObject[] {
|
||||||
let len = lengthReader.call(this);
|
let len = lengthReader.call(this);
|
||||||
let arr: TObject[] = [];
|
let arr: TObject[] = [];
|
||||||
|
if(len == 0)
|
||||||
|
return arr;
|
||||||
|
|
||||||
for(let i = 0; i < len; ++i)
|
for(let i = 0; i < len; ++i)
|
||||||
arr.push(objReader(this));
|
arr.push(objReader(this));
|
||||||
|
|
|
@ -1,41 +1,61 @@
|
||||||
import { BufferStream, SeekDir } from "./buffer.js";
|
import { BufferStream, SeekDir } from './buffer.js';
|
||||||
|
|
||||||
import { LOCATION } from "./structs/core.js";
|
import { LOCATION } from './structs/core.js';
|
||||||
import { AcsCharacterInfo } from "./structs/character.js";
|
import { AcsCharacterInfo } from './structs/character.js';
|
||||||
|
import { AcsAnimationEntry } from './structs/animation.js';
|
||||||
|
|
||||||
|
// Experiment for storing parsed data
|
||||||
|
class AcsData {
|
||||||
|
characterInfo = new AcsCharacterInfo();
|
||||||
|
animInfo: AcsAnimationEntry[] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function logOffset(o: number, name: string) {
|
||||||
|
let n = o >>> 0;
|
||||||
|
console.log(name, 'offset:', '0x' + n.toString(16));
|
||||||
|
}
|
||||||
|
|
||||||
function agentCharacterParseACS(buffer: BufferStream) {
|
function agentCharacterParseACS(buffer: BufferStream) {
|
||||||
let magic = buffer.readU32LE();
|
// Make sure the magic is correct for the ACS file.
|
||||||
|
if (buffer.readU32LE() != 0xabcdabc3) {
|
||||||
|
throw new Error('The provided data buffer does not contain valid ACS data.');
|
||||||
|
}
|
||||||
|
|
||||||
if(magic != 0xabcdabc3) {
|
let acsData = new AcsData();
|
||||||
throw new Error("This is not an ACS file.");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the rest of the header.
|
// Read the rest of the header.
|
||||||
let characterInfoLocation = LOCATION.read(buffer);
|
let characterInfoLocation = LOCATION.read(buffer);
|
||||||
let animationInfoLocation = LOCATION.read(buffer);
|
let animationInfoLocation = LOCATION.read(buffer);
|
||||||
let imageInfoLocation = LOCATION.read(buffer);
|
let imageInfoLocation = LOCATION.read(buffer);
|
||||||
let audioInfoLocation = LOCATION.read(buffer);
|
let audioInfoLocation = LOCATION.read(buffer);
|
||||||
|
|
||||||
console.log(characterInfoLocation.offset.toString(16));
|
logOffset(characterInfoLocation.offset, 'character info');
|
||||||
|
logOffset(animationInfoLocation.offset, 'animation info');
|
||||||
|
logOffset(imageInfoLocation.offset, 'image info');
|
||||||
|
logOffset(audioInfoLocation.offset, 'audio info');
|
||||||
|
|
||||||
// Read the character info in.
|
buffer.withOffset(characterInfoLocation.offset, () => {
|
||||||
buffer.seek(characterInfoLocation.offset, SeekDir.BEG);
|
acsData.characterInfo = AcsCharacterInfo.read(buffer);
|
||||||
let characterInfo = AcsCharacterInfo.read(buffer);
|
});
|
||||||
console.log(characterInfo)
|
|
||||||
|
|
||||||
// Read animation info
|
buffer.withOffset(animationInfoLocation.offset, () => {
|
||||||
|
acsData.animInfo = buffer.readCountedList(() => {
|
||||||
|
return AcsAnimationEntry.read(buffer);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(acsData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the testbed code only, remove when that gets axed
|
// For the testbed code only, remove when that gets axed
|
||||||
// (or don't, I'm not your dad)
|
// (or don't, I'm not your dad)
|
||||||
export function agentParseCharacterTestbed(buffer: Uint8Array) {
|
export function agentParseCharacterTestbed(buffer: Uint8Array) {
|
||||||
return agentCharacterParseACS(new BufferStream(buffer));
|
return agentCharacterParseACS(new BufferStream(buffer));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this will be the public API
|
// TODO this will be the public API
|
||||||
// Dunno about maintaining canvases. We can pass a div into agentInit and add a characterInit() which recieves it
|
// Dunno about maintaining canvases. We can pass a div into agentInit and add a characterInit() which recieves it
|
||||||
// (which we then mount characters and their wordballoons into?)
|
// (which we then mount characters and their wordballoons into?)
|
||||||
export function agentCreateCharacter(data: Uint8Array) : Promise<void> {
|
export function agentCreateCharacter(data: Uint8Array): Promise<void> {
|
||||||
throw new Error("Not implemented yet");
|
throw new Error('Not implemented yet');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,118 +1,110 @@
|
||||||
|
|
||||||
// Please note that the "meaningless" shifts of 0 are to force
|
// Please note that the "meaningless" shifts of 0 are to force
|
||||||
// the value to be a 32-bit integer. Do not remove them.
|
// the value to be a 32-bit integer. Do not remove them.
|
||||||
|
|
||||||
function LOWORD(n: number) {
|
function LOWORD(n: number) {
|
||||||
return (n >>> 0) & 0xffff;
|
return (n >>> 0) & 0xffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
function LOBYTE(n: number) {
|
function LOBYTE(n: number) {
|
||||||
return (n >>> 0) & 0xff;
|
return (n >>> 0) & 0xff;
|
||||||
}
|
}
|
||||||
|
|
||||||
function HIWORD(n: number) {
|
function HIWORD(n: number) {
|
||||||
return (n >>> 16) & 0xffff;
|
return (n >>> 16) & 0xffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decompress Agent compressed data. This compression algorithm sucks.
|
// Decompress Agent compressed data. This compression algorithm sucks.
|
||||||
// [dest] is to be preallocated to the decompressed data size.
|
// [dest] is to be preallocated to the decompressed data size.
|
||||||
export function compressDecompress(src: Uint8Array, dest: Uint8Array) {
|
export function compressDecompress(src: Uint8Array, dest: Uint8Array) {
|
||||||
let bitCount = 0;
|
let bitCount = 0;
|
||||||
let srcPtr = 0;
|
let srcPtr = 0;
|
||||||
let destPtr = 0;
|
let destPtr = 0;
|
||||||
let srcOffset = 0;
|
let srcOffset = 0;
|
||||||
|
|
||||||
let dv = new DataView(src.buffer, src.byteOffset, src.byteLength);
|
let dv = new DataView(src.buffer, src.byteOffset, src.byteLength);
|
||||||
|
|
||||||
let putb = (b: number) => dest[destPtr++] = b;
|
let putb = (b: number) => (dest[destPtr++] = b);
|
||||||
|
|
||||||
// Make sure the bitstream is valid
|
// Make sure the bitstream is valid
|
||||||
if(src.length <= 7 || src[0] != 0)
|
if (src.length <= 7 || src[0] != 0) return 0;
|
||||||
return 0;
|
|
||||||
|
|
||||||
for(bitCount = 1; src[src.length - bitCount] == 0xff; bitCount++) {
|
for (bitCount = 1; src[src.length - bitCount] == 0xff; bitCount++) {
|
||||||
if(bitCount > 6)
|
if (bitCount > 6) break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(bitCount < 6)
|
if (bitCount < 6) return 0;
|
||||||
return 0;
|
|
||||||
|
|
||||||
bitCount = 0;
|
bitCount = 0;
|
||||||
srcPtr += 5;
|
srcPtr += 5;
|
||||||
|
|
||||||
while((srcPtr < src.length) && (destPtr < dest.length)) {
|
while (srcPtr < src.length && destPtr < dest.length) {
|
||||||
let quad = dv.getUint32(srcPtr - 4, true);
|
let quad = dv.getUint32(srcPtr - 4, true);
|
||||||
|
|
||||||
if(quad & (1 << LOWORD(bitCount))) {
|
if (quad & (1 << LOWORD(bitCount))) {
|
||||||
srcOffset = 1;
|
srcOffset = 1;
|
||||||
|
|
||||||
if(quad & (1 << LOWORD(bitCount+1))) {
|
if (quad & (1 << LOWORD(bitCount + 1))) {
|
||||||
if(quad & (1 << LOWORD(bitCount + 2))) {
|
if (quad & (1 << LOWORD(bitCount + 2))) {
|
||||||
if(quad & (1 << LOWORD(bitCount + 3))) {
|
if (quad & (1 << LOWORD(bitCount + 3))) {
|
||||||
quad >>= LOWORD(bitCount + 4);
|
quad >>= LOWORD(bitCount + 4);
|
||||||
quad &= 0x000FFFFF;
|
quad &= 0x000fffff;
|
||||||
|
|
||||||
// End of compressed bitstream
|
// End of compressed bitstream
|
||||||
if(quad == 0x000FFFFF)
|
if (quad == 0x000fffff) break;
|
||||||
break;
|
|
||||||
|
|
||||||
quad += 4673;
|
quad += 4673;
|
||||||
bitCount += 24;
|
bitCount += 24;
|
||||||
srcOffset = 2;
|
srcOffset = 2;
|
||||||
} else {
|
} else {
|
||||||
quad >>= LOWORD(bitCount + 4);
|
quad >>= LOWORD(bitCount + 4);
|
||||||
quad &= 0x0000FFF;
|
quad &= 0x0000fff;
|
||||||
quad += 577;
|
quad += 577;
|
||||||
bitCount += 16;
|
bitCount += 16;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quad >>= LOWORD(bitCount + 3);
|
quad >>= LOWORD(bitCount + 3);
|
||||||
quad &= 0x000001FF;
|
quad &= 0x000001ff;
|
||||||
quad += 65;
|
quad += 65;
|
||||||
bitCount += 12;
|
bitCount += 12;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
quad >>= LOWORD(bitCount + 2);
|
quad >>= LOWORD(bitCount + 2);
|
||||||
quad &= 0x0000003F;
|
quad &= 0x0000003f;
|
||||||
quad += 1;
|
quad += 1;
|
||||||
bitCount += 8;
|
bitCount += 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
srcPtr += (bitCount / 8);
|
srcPtr += bitCount / 8;
|
||||||
bitCount &= 7;
|
bitCount &= 7;
|
||||||
let runCount = 0;
|
let runCount = 0;
|
||||||
let runLength = dv.getUint32(srcPtr - 4, true);
|
let runLength = dv.getUint32(srcPtr - 4, true);
|
||||||
|
|
||||||
while(runLength & (1 << LOWORD(bitCount + runCount))) {
|
while (runLength & (1 << LOWORD(bitCount + runCount))) {
|
||||||
runCount++;
|
runCount++;
|
||||||
|
|
||||||
if(runCount > 11)
|
if (runCount > 11) break;
|
||||||
break;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
runLength >>= LOWORD(bitCount + runCount + 1);
|
runLength >>= LOWORD(bitCount + runCount + 1);
|
||||||
runLength &= (1 << runCount) -1;
|
runLength &= (1 << runCount) - 1;
|
||||||
runLength += 1 << runCount;
|
runLength += 1 << runCount;
|
||||||
runLength += srcOffset;
|
runLength += srcOffset;
|
||||||
bitCount = runCount * 2 + 1;
|
bitCount = runCount * 2 + 1;
|
||||||
|
|
||||||
if(destPtr + runLength > dest.length)
|
if (destPtr + runLength > dest.length) break;
|
||||||
break;
|
|
||||||
|
|
||||||
while(runLength > 0) {
|
while (runLength > 0) {
|
||||||
putb(dest[destPtr - quad]);
|
putb(dest[destPtr - quad]);
|
||||||
runLength--;
|
runLength--;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// a literal byte
|
||||||
|
quad >>= LOWORD(bitCount + 1);
|
||||||
|
bitCount += 9;
|
||||||
|
putb(LOBYTE(quad));
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
srcPtr += bitCount / 8;
|
||||||
// a literal byte
|
bitCount &= 7;
|
||||||
quad >>= LOWORD(bitCount + 1)
|
}
|
||||||
bitCount += 9;
|
|
||||||
putb(LOBYTE(quad));
|
|
||||||
}
|
|
||||||
|
|
||||||
srcPtr += bitCount / 8;
|
|
||||||
bitCount &= 7;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
13
msagent.js/src/structs/README.md
Normal file
13
msagent.js/src/structs/README.md
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
# Structs
|
||||||
|
|
||||||
|
This contains all the structures we read.
|
||||||
|
|
||||||
|
# How to use
|
||||||
|
|
||||||
|
Simple. Given a bufferstream that has been already set up, to read a structure, you just do
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
let obj = TYPE.read(buffer);
|
||||||
|
```
|
||||||
|
|
||||||
|
and this will get you a instance of the read type. Easy as that!
|
161
msagent.js/src/structs/animation.ts
Normal file
161
msagent.js/src/structs/animation.ts
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
import { BufferStream } from '../buffer';
|
||||||
|
import { LOCATION, RGNDATA } from './core';
|
||||||
|
|
||||||
|
export enum AcsAnimationTransitionType {
|
||||||
|
UseReturn = 0x0,
|
||||||
|
UseExitBranches = 0x1,
|
||||||
|
None = 0x2
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AcsAnimationOverlayType {
|
||||||
|
MouthClosed = 0x0,
|
||||||
|
MouthOpenWideShape1 = 0x1,
|
||||||
|
MouthOpenWideShape2 = 0x2,
|
||||||
|
MouthOpenWideShape3 = 0x3,
|
||||||
|
MouthOpenWideShape4 = 0x4,
|
||||||
|
MouthOpenMedium = 0x5,
|
||||||
|
MouthOpenNarror = 0x6
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AcsFrameImage {
|
||||||
|
imageIndex = 0;
|
||||||
|
xOffset = 0;
|
||||||
|
yOffset = 0;
|
||||||
|
|
||||||
|
static read(buffer: BufferStream) {
|
||||||
|
let img = new AcsFrameImage();
|
||||||
|
img.imageIndex = buffer.readU32LE();
|
||||||
|
img.xOffset = buffer.readU16LE();
|
||||||
|
img.yOffset = buffer.readU16LE();
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AcsBranchInfo {
|
||||||
|
branchFrameIndex = 0;
|
||||||
|
branchFrameProbability = 0;
|
||||||
|
|
||||||
|
static read(buffer: BufferStream) {
|
||||||
|
let bi = new AcsBranchInfo();
|
||||||
|
bi.branchFrameIndex = buffer.readU16LE();
|
||||||
|
bi.branchFrameProbability = buffer.readU16LE();
|
||||||
|
return bi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AcsOverlayInfo {
|
||||||
|
overlayType = AcsAnimationOverlayType.MouthClosed;
|
||||||
|
replacesTopImage = false;
|
||||||
|
overlayImageIndex = 0;
|
||||||
|
|
||||||
|
xOffset = 0;
|
||||||
|
yOffset = 0;
|
||||||
|
|
||||||
|
width = 0;
|
||||||
|
height = 0;
|
||||||
|
|
||||||
|
regionData: RGNDATA | null = null;
|
||||||
|
|
||||||
|
static read(buffer: BufferStream) {
|
||||||
|
let info = new AcsOverlayInfo();
|
||||||
|
info.overlayType = buffer.readU8();
|
||||||
|
info.replacesTopImage = buffer.readBool();
|
||||||
|
info.overlayImageIndex = buffer.readU16LE();
|
||||||
|
|
||||||
|
// Some stuff we read but don't use
|
||||||
|
buffer.readU8();
|
||||||
|
let regionDataPresent = buffer.readBool();
|
||||||
|
|
||||||
|
info.xOffset = buffer.readS16LE();
|
||||||
|
info.yOffset = buffer.readS16LE();
|
||||||
|
|
||||||
|
info.width = buffer.readU16LE();
|
||||||
|
info.height = buffer.readU16LE();
|
||||||
|
|
||||||
|
if (regionDataPresent) {
|
||||||
|
let regionDataBuffer = buffer.readDataChunkBuffer();
|
||||||
|
info.regionData = RGNDATA.read(regionDataBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AcsAnimationFrameInfo {
|
||||||
|
// list type u16
|
||||||
|
images: AcsFrameImage[] = [];
|
||||||
|
|
||||||
|
// Currently unused, but this is the sound to play when this frame is played.
|
||||||
|
// This is used for sound effects. We could play them pretty easily, since
|
||||||
|
// the audio data is just WAV (and browsers support that fine).
|
||||||
|
// -1 means no sound should be played.
|
||||||
|
soundIndex = 0;
|
||||||
|
|
||||||
|
frameDuration = 0; // The duration of the frame in (1/100)th seconds.
|
||||||
|
nextFrame = 0; // -2 = animation has ended (although, I imagine this could be detected in better ways!)
|
||||||
|
|
||||||
|
branchInfo: AcsBranchInfo[] = [];
|
||||||
|
overlayInfo: AcsOverlayInfo[] = [];
|
||||||
|
|
||||||
|
static read(buffer: BufferStream) {
|
||||||
|
let info = new AcsAnimationFrameInfo();
|
||||||
|
|
||||||
|
info.images = buffer.readCountedList(() => {
|
||||||
|
return AcsFrameImage.read(buffer);
|
||||||
|
}, BufferStream.prototype.readU16LE);
|
||||||
|
|
||||||
|
info.soundIndex = buffer.readS16LE();
|
||||||
|
info.frameDuration = buffer.readU16LE();
|
||||||
|
info.nextFrame = buffer.readS16LE();
|
||||||
|
|
||||||
|
info.branchInfo = buffer.readCountedList(() => {
|
||||||
|
return AcsBranchInfo.read(buffer);
|
||||||
|
}, BufferStream.prototype.readU8);
|
||||||
|
|
||||||
|
info.overlayInfo = buffer.readCountedList(() => {
|
||||||
|
return AcsOverlayInfo.read(buffer);
|
||||||
|
}, BufferStream.prototype.readU8);
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AcsAnimation {
|
||||||
|
name = '';
|
||||||
|
transitionType = AcsAnimationTransitionType.UseReturn;
|
||||||
|
returnName = '';
|
||||||
|
frameInfo: AcsAnimationFrameInfo[] = [];
|
||||||
|
|
||||||
|
static read(buffer: BufferStream) {
|
||||||
|
let anim = new AcsAnimation();
|
||||||
|
|
||||||
|
anim.name = buffer.readPascalString();
|
||||||
|
anim.transitionType = buffer.readU8();
|
||||||
|
anim.returnName = buffer.readPascalString();
|
||||||
|
|
||||||
|
anim.frameInfo = buffer.readCountedList(() => {
|
||||||
|
return AcsAnimationFrameInfo.read(buffer);
|
||||||
|
}, BufferStream.prototype.readU16LE);
|
||||||
|
|
||||||
|
return anim;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AcsAnimationEntry {
|
||||||
|
name = '';
|
||||||
|
animationData = new AcsAnimation();
|
||||||
|
|
||||||
|
static read(buffer: BufferStream) {
|
||||||
|
let data = new AcsAnimationEntry();
|
||||||
|
data.name = buffer.readPascalString();
|
||||||
|
|
||||||
|
// This is part of the in-file data, but for simplicity
|
||||||
|
// we read the data here and discard
|
||||||
|
let animDataLoc = LOCATION.read(buffer);
|
||||||
|
|
||||||
|
buffer.withOffset(animDataLoc.offset, () => {
|
||||||
|
data.animationData = AcsAnimation.read(buffer);
|
||||||
|
});
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,253 +1,247 @@
|
||||||
import { BufferStream, SeekDir } from "../buffer.js";
|
import { BufferStream, SeekDir } from '../buffer.js';
|
||||||
|
|
||||||
import { GUID, LOCATION, RGBAColor } from "./core.js";
|
import { GUID, LOCATION, RGBAColor } from './core.js';
|
||||||
|
|
||||||
// DA doesn't test individual bits, but for brevity I do
|
// DA doesn't test individual bits, but for brevity I do
|
||||||
export enum AcsCharacterInfoFlags {
|
export enum AcsCharacterInfoFlags {
|
||||||
// This agent is configured for a given TTS.
|
// This agent is configured for a given TTS.
|
||||||
VoiceOutput = (1 << 5),
|
VoiceOutput = 1 << 5,
|
||||||
|
|
||||||
// Could be a 2-bit value (where 01 = disable and 10 = enable)
|
// Could be a 2-bit value (where 01 = disable and 10 = enable)
|
||||||
// I wonder why.
|
// I wonder why.
|
||||||
WordBalloonDisabled = (1 << 8),
|
WordBalloonDisabled = 1 << 8,
|
||||||
WordBalloonEnabled = (1 << 9),
|
WordBalloonEnabled = 1 << 9,
|
||||||
|
|
||||||
// 16-18 are a 3-bit unsigned
|
|
||||||
// value which stores a inner set of
|
|
||||||
// bits to control the style of the wordballoon.
|
|
||||||
|
|
||||||
StandardAnimationSet = (1 << 20)
|
|
||||||
};
|
|
||||||
|
|
||||||
export class AcsCharacterInfo {
|
|
||||||
minorVersion = 0;
|
|
||||||
majorVersion = 0;
|
|
||||||
|
|
||||||
localizationInfoListLocation = new LOCATION();
|
|
||||||
|
|
||||||
guid = new GUID();
|
|
||||||
|
|
||||||
charWidth = 0;
|
|
||||||
charHeight = 0;
|
|
||||||
|
|
||||||
// Color index in the palette for the transparent color
|
|
||||||
transparencyColorIndex = 0;
|
|
||||||
|
|
||||||
flags = 0;
|
|
||||||
|
|
||||||
animSetMajorVer = 0;
|
|
||||||
animSetMinorVer = 0;
|
|
||||||
|
|
||||||
voiceInfo : AcsVoiceInfo | null = null;
|
|
||||||
balloonInfo : AcsBalloonInfo | null = null;
|
|
||||||
|
|
||||||
// The color palette.
|
|
||||||
palette: RGBAColor[] = [];
|
|
||||||
|
|
||||||
trayIcon: AcsTrayIcon | null = null;
|
|
||||||
|
|
||||||
stateInfo: AcsStateInfo[] = [];
|
|
||||||
|
|
||||||
localizedInfo: AcsLocalizedInfo[] = [];
|
|
||||||
|
|
||||||
static read(buffer: BufferStream) {
|
|
||||||
let info = new AcsCharacterInfo();
|
|
||||||
|
|
||||||
info.minorVersion = buffer.readU16LE();
|
|
||||||
info.majorVersion = buffer.readU16LE();
|
|
||||||
|
|
||||||
info.localizationInfoListLocation = LOCATION.read(buffer);
|
|
||||||
info.guid = GUID.read(buffer);
|
|
||||||
|
|
||||||
info.charWidth = buffer.readU16LE();
|
|
||||||
info.charHeight = buffer.readU16LE();
|
|
||||||
|
|
||||||
info.transparencyColorIndex = buffer.readU8();
|
|
||||||
|
|
||||||
info.flags = buffer.readU32LE();
|
|
||||||
|
|
||||||
info.animSetMajorVer = buffer.readU16LE();
|
|
||||||
info.animSetMinorVer = buffer.readU16LE();
|
|
||||||
|
|
||||||
if((info.flags & AcsCharacterInfoFlags.VoiceOutput)) {
|
|
||||||
info.voiceInfo = AcsVoiceInfo.read(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(
|
|
||||||
(info.flags & AcsCharacterInfoFlags.WordBalloonEnabled) &&
|
|
||||||
!(info.flags & AcsCharacterInfoFlags.WordBalloonDisabled)
|
|
||||||
) {
|
|
||||||
info.balloonInfo = AcsBalloonInfo.read(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
info.palette = buffer.readCountedList(() => {
|
|
||||||
return RGBAColor.read(buffer);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set transparency for the magic transparency color
|
|
||||||
info.palette[info.transparencyColorIndex].a = 0;
|
|
||||||
|
|
||||||
// Tray icon
|
|
||||||
if(buffer.readBool() == true) {
|
|
||||||
info.trayIcon = AcsTrayIcon.read(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
// this makes me wish typescript had sensible generics
|
|
||||||
// so this could be encoded in a type, like c++ or rust lol
|
|
||||||
info.stateInfo = buffer.readCountedList(() => {
|
|
||||||
return AcsStateInfo.read(buffer);
|
|
||||||
}, BufferStream.prototype.readU16LE);
|
|
||||||
|
|
||||||
if(info.localizationInfoListLocation.offset != 0) {
|
|
||||||
let lastOffset = buffer.tell();
|
|
||||||
|
|
||||||
buffer.seek(info.localizationInfoListLocation.offset, SeekDir.BEG);
|
|
||||||
|
|
||||||
info.localizedInfo = buffer.readCountedList(() => {
|
|
||||||
return AcsLocalizedInfo.read(buffer);
|
|
||||||
}, BufferStream.prototype.readU16LE)
|
|
||||||
|
|
||||||
buffer.seek(lastOffset, SeekDir.BEG);
|
|
||||||
}
|
|
||||||
|
|
||||||
return info;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 16-18 are a 3-bit unsigned
|
||||||
|
// value which stores a inner set of
|
||||||
|
// bits to control the style of the wordballoon.
|
||||||
|
|
||||||
|
StandardAnimationSet = 1 << 20
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AcsCharacterInfo {
|
||||||
|
minorVersion = 0;
|
||||||
|
majorVersion = 0;
|
||||||
|
|
||||||
|
localizationInfoListLocation = new LOCATION();
|
||||||
|
|
||||||
|
guid = new GUID();
|
||||||
|
|
||||||
|
charWidth = 0;
|
||||||
|
charHeight = 0;
|
||||||
|
|
||||||
|
// Color index in the palette for the transparent color
|
||||||
|
transparencyColorIndex = 0;
|
||||||
|
|
||||||
|
flags = 0;
|
||||||
|
|
||||||
|
animSetMajorVer = 0;
|
||||||
|
animSetMinorVer = 0;
|
||||||
|
|
||||||
|
voiceInfo: AcsVoiceInfo | null = null;
|
||||||
|
balloonInfo: AcsBalloonInfo | null = null;
|
||||||
|
|
||||||
|
// The color palette.
|
||||||
|
palette: RGBAColor[] = [];
|
||||||
|
|
||||||
|
trayIcon: AcsTrayIcon | null = null;
|
||||||
|
|
||||||
|
stateInfo: AcsStateInfo[] = [];
|
||||||
|
|
||||||
|
localizedInfo: AcsLocalizedInfo[] = [];
|
||||||
|
|
||||||
|
static read(buffer: BufferStream) {
|
||||||
|
let info = new AcsCharacterInfo();
|
||||||
|
|
||||||
|
info.minorVersion = buffer.readU16LE();
|
||||||
|
info.majorVersion = buffer.readU16LE();
|
||||||
|
|
||||||
|
info.localizationInfoListLocation = LOCATION.read(buffer);
|
||||||
|
info.guid = GUID.read(buffer);
|
||||||
|
|
||||||
|
info.charWidth = buffer.readU16LE();
|
||||||
|
info.charHeight = buffer.readU16LE();
|
||||||
|
|
||||||
|
info.transparencyColorIndex = buffer.readU8();
|
||||||
|
|
||||||
|
info.flags = buffer.readU32LE();
|
||||||
|
|
||||||
|
info.animSetMajorVer = buffer.readU16LE();
|
||||||
|
info.animSetMinorVer = buffer.readU16LE();
|
||||||
|
|
||||||
|
if (info.flags & AcsCharacterInfoFlags.VoiceOutput) {
|
||||||
|
info.voiceInfo = AcsVoiceInfo.read(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.flags & AcsCharacterInfoFlags.WordBalloonEnabled && !(info.flags & AcsCharacterInfoFlags.WordBalloonDisabled)) {
|
||||||
|
info.balloonInfo = AcsBalloonInfo.read(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
info.palette = buffer.readCountedList(() => {
|
||||||
|
return RGBAColor.read(buffer);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set transparency for the magic transparency color
|
||||||
|
info.palette[info.transparencyColorIndex].a = 0;
|
||||||
|
|
||||||
|
// Tray icon
|
||||||
|
if (buffer.readBool() == true) {
|
||||||
|
info.trayIcon = AcsTrayIcon.read(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// this makes me wish typescript had sensible generics
|
||||||
|
// so this could be encoded in a type, like c++ or rust lol
|
||||||
|
info.stateInfo = buffer.readCountedList(() => {
|
||||||
|
return AcsStateInfo.read(buffer);
|
||||||
|
}, BufferStream.prototype.readU16LE);
|
||||||
|
|
||||||
|
if (info.localizationInfoListLocation.offset != 0) {
|
||||||
|
let lastOffset = buffer.tell();
|
||||||
|
|
||||||
|
buffer.seek(info.localizationInfoListLocation.offset, SeekDir.BEG);
|
||||||
|
|
||||||
|
info.localizedInfo = buffer.readCountedList(() => {
|
||||||
|
return AcsLocalizedInfo.read(buffer);
|
||||||
|
}, BufferStream.prototype.readU16LE);
|
||||||
|
|
||||||
|
buffer.seek(lastOffset, SeekDir.BEG);
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class AcsVoiceInfoExtraData {
|
export class AcsVoiceInfoExtraData {
|
||||||
langId = 0;
|
langId = 0;
|
||||||
langDialect = "";
|
langDialect = '';
|
||||||
|
|
||||||
gender = 0;
|
gender = 0;
|
||||||
age = 0;
|
age = 0;
|
||||||
|
|
||||||
style = "";
|
style = '';
|
||||||
|
|
||||||
static read(buffer: BufferStream) {
|
static read(buffer: BufferStream) {
|
||||||
let info = new AcsVoiceInfoExtraData();
|
let info = new AcsVoiceInfoExtraData();
|
||||||
|
|
||||||
info.langId = buffer.readU16LE();
|
info.langId = buffer.readU16LE();
|
||||||
|
|
||||||
info.langDialect = buffer.readPascalString();
|
info.langDialect = buffer.readPascalString();
|
||||||
|
|
||||||
info.gender = buffer.readU16LE();
|
info.gender = buffer.readU16LE();
|
||||||
info.age = buffer.readU16LE();
|
info.age = buffer.readU16LE();
|
||||||
|
|
||||||
info.style = buffer.readPascalString();
|
info.style = buffer.readPascalString();
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export class AcsVoiceInfo {
|
export class AcsVoiceInfo {
|
||||||
ttsEngineId = new GUID();
|
ttsEngineId = new GUID();
|
||||||
ttsModeId = new GUID();
|
ttsModeId = new GUID();
|
||||||
|
|
||||||
speed = 0;
|
speed = 0;
|
||||||
pitch = 0;
|
pitch = 0;
|
||||||
|
|
||||||
extraData: AcsVoiceInfoExtraData | null = null;
|
extraData: AcsVoiceInfoExtraData | null = null;
|
||||||
|
|
||||||
static read(buffer: BufferStream) {
|
static read(buffer: BufferStream) {
|
||||||
let info = new AcsVoiceInfo();
|
let info = new AcsVoiceInfo();
|
||||||
|
|
||||||
info.ttsEngineId = GUID.read(buffer);
|
info.ttsEngineId = GUID.read(buffer);
|
||||||
info.ttsModeId = GUID.read(buffer);
|
info.ttsModeId = GUID.read(buffer);
|
||||||
|
|
||||||
info.speed = buffer.readU32LE();
|
info.speed = buffer.readU32LE();
|
||||||
info.pitch = buffer.readU16LE();
|
info.pitch = buffer.readU16LE();
|
||||||
|
|
||||||
// extraData member
|
// extraData member
|
||||||
if(buffer.readBool()) {
|
if (buffer.readBool()) {
|
||||||
info.extraData = AcsVoiceInfoExtraData.read(buffer);
|
info.extraData = AcsVoiceInfoExtraData.read(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export class AcsBalloonInfo {
|
export class AcsBalloonInfo {
|
||||||
nrTextLines = 0;
|
nrTextLines = 0;
|
||||||
charsPerLine = 0;
|
charsPerLine = 0;
|
||||||
|
|
||||||
foreColor = new RGBAColor();
|
foreColor = new RGBAColor();
|
||||||
backColor = new RGBAColor();
|
backColor = new RGBAColor();
|
||||||
borderColor = new RGBAColor();
|
borderColor = new RGBAColor();
|
||||||
|
|
||||||
fontName = "";
|
fontName = '';
|
||||||
|
|
||||||
fontHeight = 0;
|
fontHeight = 0;
|
||||||
fontWeight = 0;
|
fontWeight = 0;
|
||||||
|
|
||||||
italic = false;
|
italic = false;
|
||||||
unkFlag = false;
|
unkFlag = false;
|
||||||
|
|
||||||
static read(buffer: BufferStream) {
|
static read(buffer: BufferStream) {
|
||||||
let info = new AcsBalloonInfo();
|
let info = new AcsBalloonInfo();
|
||||||
|
|
||||||
info.nrTextLines = buffer.readU8();
|
info.nrTextLines = buffer.readU8();
|
||||||
info.charsPerLine = buffer.readU8();
|
info.charsPerLine = buffer.readU8();
|
||||||
|
|
||||||
info.foreColor = RGBAColor.read(buffer);
|
info.foreColor = RGBAColor.read(buffer);
|
||||||
info.backColor = RGBAColor.read(buffer);
|
info.backColor = RGBAColor.read(buffer);
|
||||||
info.borderColor = RGBAColor.read(buffer);
|
info.borderColor = RGBAColor.read(buffer);
|
||||||
|
|
||||||
info.fontName = buffer.readPascalString();
|
info.fontName = buffer.readPascalString();
|
||||||
info.fontHeight = buffer.readS32LE();
|
info.fontHeight = buffer.readS32LE();
|
||||||
info.fontWeight = buffer.readS32LE();
|
info.fontWeight = buffer.readS32LE();
|
||||||
|
|
||||||
info.italic = buffer.readBool();
|
info.italic = buffer.readBool();
|
||||||
info.unkFlag = buffer.readBool();
|
info.unkFlag = buffer.readBool();
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export class AcsTrayIcon {
|
export class AcsTrayIcon {
|
||||||
monoBitmap: Uint8Array | null = null;
|
monoBitmap: Uint8Array | null = null;
|
||||||
colorBitmap: Uint8Array | null = null;
|
colorBitmap: Uint8Array | null = null;
|
||||||
|
|
||||||
static read(buffer: BufferStream) {
|
static read(buffer: BufferStream) {
|
||||||
let icon = new AcsTrayIcon();
|
let icon = new AcsTrayIcon();
|
||||||
icon.monoBitmap = buffer.readDataChunk(BufferStream.prototype.readU32LE);
|
icon.monoBitmap = buffer.readDataChunk(BufferStream.prototype.readU32LE);
|
||||||
icon.colorBitmap = buffer.readDataChunk(BufferStream.prototype.readU32LE);
|
icon.colorBitmap = buffer.readDataChunk(BufferStream.prototype.readU32LE);
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AcsStateInfo {
|
export class AcsStateInfo {
|
||||||
stateName = "";
|
stateName = '';
|
||||||
animations : string[] = [];
|
animations: string[] = [];
|
||||||
|
|
||||||
static read(buffer: BufferStream) {
|
static read(buffer: BufferStream) {
|
||||||
let info = new AcsStateInfo();
|
let info = new AcsStateInfo();
|
||||||
|
|
||||||
info.stateName = buffer.readPascalString();
|
info.stateName = buffer.readPascalString();
|
||||||
info.animations = buffer.readCountedList(() => {
|
info.animations = buffer.readCountedList(() => {
|
||||||
return buffer.readPascalString();
|
return buffer.readPascalString();
|
||||||
}, BufferStream.prototype.readU16LE);
|
}, BufferStream.prototype.readU16LE);
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AcsLocalizedInfo {
|
export class AcsLocalizedInfo {
|
||||||
langId = 0;
|
langId = 0;
|
||||||
charName = "";
|
charName = '';
|
||||||
charDescription = "";
|
charDescription = '';
|
||||||
charExtraData = "";
|
charExtraData = '';
|
||||||
|
|
||||||
static read(buffer: BufferStream) {
|
static read(buffer: BufferStream) {
|
||||||
let info = new AcsLocalizedInfo();
|
let info = new AcsLocalizedInfo();
|
||||||
|
|
||||||
info.langId = buffer.readU16LE();
|
info.langId = buffer.readU16LE();
|
||||||
info.charName = buffer.readPascalString();
|
info.charName = buffer.readPascalString();
|
||||||
info.charDescription = buffer.readPascalString();
|
info.charDescription = buffer.readPascalString();
|
||||||
info.charExtraData = buffer.readPascalString();
|
info.charExtraData = buffer.readPascalString();
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,56 +1,116 @@
|
||||||
import { BufferStream, SeekDir } from "../buffer.js";
|
import { BufferStream, SeekDir } from '../buffer.js';
|
||||||
|
|
||||||
|
// Win32 Rect
|
||||||
|
export class RECT {
|
||||||
|
left = 0;
|
||||||
|
top = 0;
|
||||||
|
right = 0;
|
||||||
|
bottom = 0;
|
||||||
|
|
||||||
|
static read(buffer: BufferStream) {
|
||||||
|
let rect = new RECT();
|
||||||
|
rect.left = buffer.readU32LE();
|
||||||
|
rect.top = buffer.readU32LE();
|
||||||
|
rect.right = buffer.readU32LE();
|
||||||
|
rect.bottom = buffer.readU32LE();
|
||||||
|
return rect;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RGNDATAHEADER {
|
||||||
|
size = 0x20; // I think?
|
||||||
|
type = 1;
|
||||||
|
count = 0;
|
||||||
|
rgnSize = 0;
|
||||||
|
bound = new RECT();
|
||||||
|
|
||||||
|
static read(buffer: BufferStream) {
|
||||||
|
let hdr = new RGNDATAHEADER();
|
||||||
|
|
||||||
|
hdr.size = buffer.readU32LE();
|
||||||
|
//if(hdr.size != 0x20)
|
||||||
|
// throw new Error("Invalid RGNDATAHEADER!");
|
||||||
|
|
||||||
|
hdr.type = buffer.readU32LE();
|
||||||
|
if (hdr.type != 1) throw new Error('Invalid RGNDATAHEADER type!');
|
||||||
|
|
||||||
|
hdr.count = buffer.readU32LE();
|
||||||
|
hdr.rgnSize = buffer.readU32LE();
|
||||||
|
|
||||||
|
hdr.bound = RECT.read(buffer);
|
||||||
|
|
||||||
|
return hdr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class RGNDATA {
|
||||||
|
header = new RGNDATAHEADER();
|
||||||
|
rects: RECT[] = [];
|
||||||
|
|
||||||
|
static read(buffer: BufferStream) {
|
||||||
|
let region = new RGNDATA();
|
||||||
|
region.header = RGNDATAHEADER.read(buffer);
|
||||||
|
|
||||||
|
for (let i = 0; i < region.header.count; ++i) {
|
||||||
|
region.rects.push(RECT.read(buffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
return region;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class GUID {
|
export class GUID {
|
||||||
bytes: number[] = [];
|
bytes: number[] = [];
|
||||||
|
|
||||||
static read(buffer: BufferStream) {
|
static read(buffer: BufferStream) {
|
||||||
let guid = new GUID();
|
let guid = new GUID();
|
||||||
|
|
||||||
for(var i = 0; i < 16; ++i)
|
for (var i = 0; i < 16; ++i) {
|
||||||
guid.bytes.push(buffer.readU8());
|
guid.bytes.push(buffer.readU8());
|
||||||
|
}
|
||||||
|
|
||||||
return guid;
|
return guid;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export class LOCATION {
|
export class LOCATION {
|
||||||
offset: number = 0;
|
offset: number = 0;
|
||||||
size: number = 0;
|
size: number = 0;
|
||||||
|
|
||||||
static read(buffer: BufferStream) {
|
static read(buffer: BufferStream) {
|
||||||
let loc = new LOCATION();
|
let loc = new LOCATION();
|
||||||
loc.offset = buffer.readU32LE();
|
loc.offset = buffer.readU32LE();
|
||||||
loc.size = buffer.readU32LE();
|
loc.size = buffer.readU32LE();
|
||||||
return loc;
|
return loc;
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
export class RGBAColor {
|
export class RGBAColor {
|
||||||
r = 0;
|
r = 0;
|
||||||
g = 0;
|
g = 0;
|
||||||
b = 0;
|
b = 0;
|
||||||
a = 0;
|
a = 0;
|
||||||
|
|
||||||
// Does what it says on the tin, converts to RGBA
|
// Does what it says on the tin, converts to RGBA
|
||||||
to_rgba(): number {
|
to_rgba(): number {
|
||||||
return (this.r << 24) | (this.g << 16) | (this.b << 8) | this.a;
|
return (this.r << 24) | (this.g << 16) | (this.b << 8) | this.a;
|
||||||
}
|
}
|
||||||
|
|
||||||
static from_gdi_rgbquad(val: number) {
|
static from_gdi_rgbquad(val: number) {
|
||||||
let quad = new RGBAColor();
|
let quad = new RGBAColor();
|
||||||
|
|
||||||
// Extract individual RGB values from the RGBQUAD
|
// Extract individual RGB values from the RGBQUAD
|
||||||
// We ignore the last 8 bits because it is always left
|
// We ignore the last 8 bits because it is always left
|
||||||
// as 0x00 or if uncleared, just random garbage.
|
// as 0x00 or if uncleared, just random garbage.
|
||||||
quad.r = (val & 0xff000000) >> 24;
|
quad.r = (val & 0xff000000) >> 24;
|
||||||
quad.g = (val & 0x00ff0000) >> 16;
|
quad.g = (val & 0x00ff0000) >> 16;
|
||||||
quad.b = (val & 0x0000ff00) >> 8;
|
quad.b = (val & 0x0000ff00) >> 8;
|
||||||
quad.a = 255;
|
quad.a = 255;
|
||||||
|
|
||||||
return quad;
|
return quad;
|
||||||
}
|
}
|
||||||
|
|
||||||
static read(buffer: BufferStream) {
|
static read(buffer: BufferStream) {
|
||||||
return RGBAColor.from_gdi_rgbquad(buffer.readU32LE());
|
return RGBAColor.from_gdi_rgbquad(buffer.readU32LE());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue