-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
279 additions
and
16 deletions.
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { LGR } from '../src'; | ||
|
||
describe('LGR', () => { | ||
test('load and toBuffer matches #1', async () => { | ||
const lgr = await LGR.load('__tests__/assets/lgr/Default.lgr'); | ||
lgr.path = ''; | ||
const lgrBuffer = await lgr.toBuffer(); | ||
const loadedBuffer = await LGR.load(lgrBuffer); | ||
expect(lgr).toEqual(loadedBuffer); | ||
}); | ||
|
||
test('load and toBuffer matches #2', async () => { | ||
const lgr = await LGR.load('__tests__/assets/lgr/Across.lgr'); | ||
lgr.path = ''; | ||
const lgrBuffer = await lgr.toBuffer(); | ||
const loadedBuffer = await LGR.load(lgrBuffer); | ||
expect(lgr).toEqual(loadedBuffer); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
export { default as ElmaObject, Gravity, ObjectType } from './ElmaObject'; | ||
export { default as Picture, Clip } from './Picture'; | ||
export { default as Picture } from './Picture'; | ||
export { default as Polygon } from './Polygon'; | ||
export { default as Level, ITimeEntry, ITop10, Version } from './Level'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
import { readFile, writeFile } from 'fs-extra'; | ||
import { | ||
Clip, | ||
PictureData, | ||
PictureDeclaration, | ||
PictureType, | ||
Transparency, | ||
} from '../'; | ||
import { nullpadString, trimString } from '../util'; | ||
|
||
// Magic arbitrary number to signify start of LGR file. | ||
const LGRStart = 0x000003ea; | ||
// Magic arbitrary number to signify end of LGR file. | ||
const LGREOF = 0x0b2e05e7; | ||
|
||
export default class LGR { | ||
/** | ||
* Loads a level file. | ||
* @param source Can either be a file path or a buffer | ||
*/ | ||
public static async load(source: string | Buffer): Promise<LGR> { | ||
if (typeof source === 'string') { | ||
const file = await readFile(source); | ||
return this._parseBuffer(file, source); | ||
} else if (source instanceof Buffer) { | ||
return this._parseBuffer(source); | ||
} | ||
throw new Error( | ||
'Invalid input argument. Expected string or Buffer instance object' | ||
); | ||
} | ||
|
||
private static async _parseBuffer( | ||
buffer: Buffer, | ||
path?: string | ||
): Promise<LGR> { | ||
const lgr = new LGR(); | ||
if (path) lgr.path = path; | ||
|
||
const version = buffer.toString('ascii', 0, 5); | ||
|
||
// there are no other LGR versions possible, so no need to store it (?) | ||
if (version !== 'LGR12') { | ||
throw new Error(`Invalid LGR version: ${version}`); | ||
} | ||
|
||
const pictureLen = buffer.readInt32LE(5); | ||
const expectedHeader = buffer.readInt32LE(9); | ||
if (expectedHeader !== LGRStart) { | ||
throw new Error(`Invalid header: ${expectedHeader}`); | ||
} | ||
|
||
// picture.lst section | ||
const listLen = buffer.readInt32LE(13); | ||
lgr.pictureList = await lgr._parseListData( | ||
buffer.slice(17, 17 + 26 * listLen), | ||
listLen | ||
); | ||
|
||
// pcx data | ||
const [pictureData, bytesRead] = await lgr._parsePictureData( | ||
buffer.slice(17 + 26 * listLen), | ||
pictureLen | ||
); | ||
lgr.pictureData = pictureData; | ||
|
||
const expectedEof = buffer.readInt32LE(17 + 26 * listLen + bytesRead); | ||
if (expectedEof !== LGREOF) { | ||
throw new Error( | ||
`EOF marker expected at byte: ${17 + 26 * listLen + bytesRead}` | ||
); | ||
} | ||
return lgr; | ||
} | ||
|
||
public pictureList: PictureDeclaration[] = []; | ||
public pictureData: PictureData[] = []; | ||
public path: string = ''; | ||
|
||
/** | ||
* Returns a buffer representation of the LGR. | ||
*/ | ||
public async toBuffer(): Promise<Buffer> { | ||
// calculate how many bytes to allocate: | ||
// - 21 known static bytes, plus 26 bytes for each item in picture.lst | ||
// - the image data is then reduced and added to that. | ||
const pictureListLength = this.pictureList.length; | ||
const bytesToAlloc = this.pictureData.reduce( | ||
(bytes, picture) => bytes + picture.data.length + 24, | ||
21 + 26 * pictureListLength | ||
); | ||
const buffer = Buffer.alloc(bytesToAlloc); | ||
let offset = 0; | ||
buffer.write('LGR12', offset, 5, 'ascii'); | ||
offset += 5; | ||
buffer.writeUInt32LE(this.pictureData.length, offset); | ||
offset += 4; | ||
buffer.writeInt32LE(LGRStart, offset); | ||
offset += 4; | ||
buffer.writeUInt32LE(pictureListLength, offset); | ||
offset += 4; | ||
|
||
this.pictureList.forEach((pictureDeclaration, n) => { | ||
buffer.write( | ||
nullpadString(pictureDeclaration.name, 10), | ||
offset + n * 10, | ||
10, | ||
'ascii' | ||
); | ||
buffer.writeUInt32LE( | ||
pictureDeclaration.pictureType, | ||
offset + 10 * pictureListLength + 4 * n | ||
); | ||
buffer.writeUInt32LE( | ||
pictureDeclaration.distance, | ||
offset + 14 * pictureListLength + 4 * n | ||
); | ||
buffer.writeUInt32LE( | ||
pictureDeclaration.clipping, | ||
offset + 18 * pictureListLength + 4 * n | ||
); | ||
buffer.writeUInt32LE( | ||
pictureDeclaration.transparency, | ||
offset + 22 * pictureListLength + 4 * n | ||
); | ||
}); | ||
|
||
offset += 26 * pictureListLength; | ||
|
||
this.pictureData.forEach(pictureData => { | ||
buffer.write(nullpadString(pictureData.name, 20), offset, 20, 'ascii'); | ||
offset += 20; | ||
buffer.writeUInt32LE(pictureData.data.length, offset); | ||
offset += 4; | ||
offset += pictureData.data.copy(buffer, offset); | ||
}); | ||
buffer.writeInt32LE(LGREOF, offset); | ||
return buffer; | ||
} | ||
|
||
public async save(path?: string) { | ||
const buffer = await this.toBuffer(); | ||
await writeFile(path || this.path, buffer); | ||
} | ||
|
||
private async _parseListData( | ||
buffer: Buffer, | ||
length: number | ||
): Promise<PictureDeclaration[]> { | ||
const pictureDeclarations: PictureDeclaration[] = []; | ||
let offset = 0; | ||
const names = buffer.slice(offset, offset + length * 10); | ||
offset += length * 10; | ||
const pictureTypes = buffer.slice(offset, offset + length * 4); | ||
offset += length * 4; | ||
const distances = buffer.slice(offset, offset + length * 4); | ||
offset += length * 4; | ||
const clips = buffer.slice(offset, offset + length * 4); | ||
offset += length * 4; | ||
const transparencies = buffer.slice(offset, offset + length * 4); | ||
for (let i = 0; i < length; i++) { | ||
const pictureDeclaration = new PictureDeclaration(); | ||
pictureDeclaration.name = trimString(names.slice(10 * i, 10 * i + 10)); | ||
pictureDeclaration.pictureType = pictureTypes.readInt32LE(i * 4); | ||
pictureDeclaration.distance = distances.readInt32LE(i * 4); | ||
pictureDeclaration.clipping = clips.readInt32LE(i * 4); | ||
pictureDeclaration.transparency = transparencies.readInt32LE(i * 4); | ||
pictureDeclarations.push(pictureDeclaration); | ||
} | ||
|
||
return pictureDeclarations; | ||
} | ||
|
||
private async _parsePictureData( | ||
buffer: Buffer, | ||
length: number | ||
): Promise<[PictureData[], number]> { | ||
const pictures: PictureData[] = []; | ||
let offset = 0; | ||
for (let i = 0; i < length; i++) { | ||
const name = trimString(buffer.slice(offset, offset + 12)); | ||
offset += 20; // +8 garbage (?) bytes | ||
const bytesToRead = buffer.readUInt32LE(offset); | ||
offset += 4; | ||
const data = buffer.slice(offset, offset + bytesToRead); | ||
pictures.push(new PictureData(name, data)); | ||
offset += bytesToRead; | ||
} | ||
// we need to return amount of bytes read in order to correctly get the offset later. | ||
return [pictures, offset]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export default class PictureData { | ||
public name: string; | ||
public data: Buffer; | ||
|
||
constructor(name: string, buffer: Buffer) { | ||
this.name = name; | ||
this.data = buffer; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
import { Clip } from '../shared'; | ||
|
||
export default class PictureDeclaration { | ||
/// Picture name. | ||
public name: string = ''; | ||
/// Picture type. | ||
public pictureType: PictureType = PictureType.Normal; | ||
/// Default distance, 1-999. | ||
public distance: number = 450; | ||
/// Default clipping. | ||
public clipping: Clip = Clip.Sky; | ||
/// Transparency. | ||
public transparency: Transparency = Transparency.TopLeft; | ||
} | ||
|
||
export enum PictureType { | ||
Normal = 100, | ||
Texture = 101, | ||
Mask = 102, | ||
} | ||
|
||
export enum Transparency { | ||
// No transparency. Only valid for ´Mask´ picture types. | ||
Solid = 10, | ||
// Palette index 0 is transparent color. | ||
Palette = 11, | ||
// Top left pixel is transparent color. | ||
TopLeft = 12, | ||
// Top right pixel is transparent color. | ||
TopRight = 13, | ||
// Bottom left pixel is transparent color. | ||
BottomLeft = 14, | ||
// Bottom right pixel is transparent color. | ||
BottomRight = 15, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export { default as LGR } from './LGR'; | ||
export { default as PictureData } from './PictureData'; | ||
export { | ||
default as PictureDeclaration, | ||
PictureType, | ||
Transparency, | ||
} from './PictureDeclaration'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters