-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix(map): override length values appropriately when reading map chunks
- Loading branch information
Showing
7 changed files
with
146 additions
and
66 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
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,73 @@ | ||
import { IoContext, IoSource, IoType, IoMode, IoStream, openStream } from '@wowserhq/io'; | ||
import * as mcnkIo from './mcnk.js'; | ||
|
||
/** | ||
* An IoType to read MCNK data from ADT files. | ||
* | ||
* While MCNK is nominally a headered TLV format, a number of presumed bugs in Blizzard's authoring | ||
* tools lead to multiple tags with invalid lengths. The game client uses alternative length values | ||
* specified in the header for these tags. | ||
*/ | ||
class McnkIo implements IoType { | ||
getSize() { | ||
return 0; | ||
} | ||
|
||
#readChunk(stream: IoStream, context: IoContext, header: Record<string, any>) { | ||
const tagValue = mcnkIo.chunkTag.read(stream); | ||
|
||
let lengthValue = stream.readUint32Le(); | ||
if (tagValue === 'MCAL') { | ||
lengthValue = header.mcalSize - 8; | ||
} else if (tagValue === 'MCLQ') { | ||
lengthValue = header.mclqSize - 8; | ||
} else if (tagValue === 'MCSH') { | ||
lengthValue = header.mcshSize; | ||
} | ||
|
||
const valueType = mcnkIo.chunks[tagValue]; | ||
const valueBytes = stream.readBytes(lengthValue); | ||
|
||
let valueValue = valueBytes; | ||
if (valueType && valueType.read) { | ||
const valueStream = openStream(valueBytes, IoMode.Read); | ||
valueValue = valueType.read(valueStream, context); | ||
} | ||
|
||
// If tag is padded, adjust offset accordingly | ||
const padding = mcnkIo.padding[tagValue] ?? 0; | ||
stream.offset += padding; | ||
|
||
return { | ||
tag: tagValue, | ||
length: lengthValue, | ||
value: valueValue, | ||
}; | ||
} | ||
|
||
#readChunks(stream: IoStream, context: IoContext, header: Record<string, any>) { | ||
const chunks = []; | ||
|
||
while (!stream.eof) { | ||
chunks.push(this.#readChunk(stream, context, header)); | ||
} | ||
|
||
return chunks; | ||
} | ||
|
||
read(source: IoSource, context: IoContext = {}) { | ||
const stream = openStream(source, IoMode.Read); | ||
const value: Record<string, any> = {}; | ||
|
||
context.local = null; | ||
context.root = context.root ?? null; | ||
|
||
const header = mcnkIo.header.read(source); | ||
value.header = header; | ||
value.data = this.#readChunks(stream, context, header); | ||
|
||
return value; | ||
} | ||
} | ||
|
||
export default McnkIo; |
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 |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import * as io from '@wowserhq/io'; | ||
import { IoType } from '@wowserhq/io'; | ||
import { MAP_CHUNK_VERTEX_COUNT } from '../const.js'; | ||
|
||
const header: IoType = io.struct({ | ||
flags: io.uint32le, | ||
indexX: io.uint32le, | ||
indexY: io.uint32le, | ||
layerCount: io.uint32le, | ||
doodadRefCount: io.uint32le, | ||
mcvtOffset: io.uint32le, | ||
mcnrOffset: io.uint32le, | ||
mclyOffset: io.uint32le, | ||
mcrfOffset: io.uint32le, | ||
mcalOffset: io.uint32le, | ||
mcalSize: io.uint32le, | ||
mcshOffset: io.uint32le, | ||
mcshSize: io.uint32le, | ||
areaId: io.uint32le, | ||
mapObjRefCount: io.uint32le, | ||
holes: io.uint16le, | ||
padding: io.uint16le, | ||
predTex: io.array(io.uint16le, { size: 8 }), | ||
noEffectDoodad: io.array(io.uint8, { size: 8 }), | ||
mcseOffset: io.uint32le, | ||
soundEmitterCount: io.uint32le, | ||
mclqOffset: io.uint32le, | ||
mclqSize: io.uint32le, | ||
position: io.array(io.float32le, { size: 3 }), | ||
mccvOffset: io.uint32le, | ||
padding2: io.array(io.uint32le, { size: 2 }), | ||
}); | ||
|
||
const layer = io.struct({ | ||
textureId: io.uint32le, | ||
properties: io.uint32le, | ||
alphaOffset: io.uint32le, | ||
effectId: io.uint32le, | ||
}); | ||
|
||
const chunkTag: IoType = io.string({ size: 4, reverse: true, terminate: false }); | ||
|
||
const mcvt = io.typedArray(io.float32le, { size: MAP_CHUNK_VERTEX_COUNT }); | ||
|
||
const mcly = io.array(layer); | ||
|
||
const mcnr = io.typedArray(io.int8, { size: 3 * MAP_CHUNK_VERTEX_COUNT }); | ||
|
||
const chunks: Record<string, IoType> = { | ||
MCVT: mcvt, | ||
MCLY: mcly, | ||
MCNR: mcnr, | ||
}; | ||
|
||
const padding = { | ||
MCNR: 13, | ||
}; | ||
|
||
export { header, chunkTag, chunks, padding }; |
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