First working body decoding
This commit is contained in:
parent
97c01b459f
commit
2b60bacad6
42
avatars.md
Normal file
42
avatars.md
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
; 0 - number of animation bytes (WRONG? 0x16 in file)
|
||||||
|
; 1 - disk_face (byte)
|
||||||
|
; 3 - bits for cels to draw
|
||||||
|
|
||||||
|
; 7 - offsets of embedded props for limbs (two bytes each, 6 limbs)
|
||||||
|
|
||||||
|
; 19: display_avatar copies _26 bytes_
|
||||||
|
|
||||||
|
head_cel_number:
|
||||||
|
byte 4
|
||||||
|
frozen_when_stands:
|
||||||
|
byte 0xff
|
||||||
|
|
||||||
|
pattern_for_limb:
|
||||||
|
byte AVATAR_LEG_LIMB
|
||||||
|
byte AVATAR_LEG_LIMB
|
||||||
|
byte AVATAR_ARM_LIMB
|
||||||
|
byte AVATAR_TORSO_LIMB
|
||||||
|
byte AVATAR_FACE_LIMB
|
||||||
|
byte AVATAR_ARM_LIMB
|
||||||
|
|
||||||
|
fv_cels: ; order of cels front view
|
||||||
|
byte 0,1,3,4,2,5
|
||||||
|
bv_cels:
|
||||||
|
byte 5,2,4,0,1,3
|
||||||
|
|
||||||
|
limbs_affected_by_height:
|
||||||
|
byte 0,0,1,1,1,1
|
||||||
|
|
||||||
|
; limbs are _embedded_ props?? animate.m get_av_prop_address
|
||||||
|
(A * 2) + 8 - high byte of offset!!
|
||||||
|
|
||||||
|
avatars can have up to 16 cels - each frame is _only_ 1 cel, rather than a composite
|
||||||
|
byte_to_bit lookup table turns it into a bitmask, used by cels_to_draw_2 & cels_to_draw
|
||||||
|
|
||||||
|
; embedded limb "prop":
|
||||||
|
; 0 - number of animation bytes A
|
||||||
|
; 1 - unk
|
||||||
|
; 2 - unk
|
||||||
|
; 3:A+2 - first
|
||||||
|
; A+3 - unk
|
||||||
|
; A+4:A+20 - cel offsets (Word)
|
1
bodies.json
Normal file
1
bodies.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
["bodies/Avatar.bin","bodies/Drag.bin","bodies/Gunship.bin","bodies/Heli.bin","bodies/MP.bin","bodies/Peng_uppercase.bin","bodies/Spid.bin","bodies/Tank.bin","bodies/Tentacle.bin","bodies/fpants.bin","bodies/nillhead.bin"]
|
BIN
bodies/Heli.bin
Normal file
BIN
bodies/Heli.bin
Normal file
Binary file not shown.
BIN
bodies/MP.bin
Normal file
BIN
bodies/MP.bin
Normal file
Binary file not shown.
BIN
bodies/fpants.bin
Normal file
BIN
bodies/fpants.bin
Normal file
Binary file not shown.
BIN
bodies/nillhead.bin
Normal file
BIN
bodies/nillhead.bin
Normal file
Binary file not shown.
62
body.html
Normal file
62
body.html
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8"/>
|
||||||
|
<title>Inhabitor - The Habitat Inspector</title>
|
||||||
|
<style type="text/css">
|
||||||
|
body {
|
||||||
|
margin:40px auto;
|
||||||
|
line-height:1.6;
|
||||||
|
font-size:18px;
|
||||||
|
color:#444;
|
||||||
|
padding:0 10px
|
||||||
|
}
|
||||||
|
h1,h2,h3 {
|
||||||
|
line-height:1.2
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="index.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1 id="filename"></h1>
|
||||||
|
<div id="cels">
|
||||||
|
<h2>Cels</h1>
|
||||||
|
</div>
|
||||||
|
<div id="data">
|
||||||
|
<h2>Data</h1>
|
||||||
|
</div>
|
||||||
|
<div id="errors"></div>
|
||||||
|
<a href="index.html">Back</a>
|
||||||
|
<script>
|
||||||
|
const propFilter = (key, value) => {
|
||||||
|
if (key != "bitmap" && key != "data" && key != "canvas") {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const dumpProp = (prop, container) => {
|
||||||
|
container.appendChild(textNode(JSON.stringify(prop, propFilter, 2), "pre"))
|
||||||
|
}
|
||||||
|
|
||||||
|
const onload = async () => {
|
||||||
|
const q = new URLSearchParams(window.location.search)
|
||||||
|
const filename = q.get("f")
|
||||||
|
document.getElementById("filename").innerText = filename
|
||||||
|
try {
|
||||||
|
const body = await decodeBinary(filename, decodeBody)
|
||||||
|
dumpProp(body, document.getElementById("data"))
|
||||||
|
if (body.error) {
|
||||||
|
showError(body.error, filename)
|
||||||
|
} else {
|
||||||
|
const celContainer = document.getElementById("cels")
|
||||||
|
for (const limb of body.limbs) {
|
||||||
|
showCels(limb, celContainer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showError(e, filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onload()
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -48,7 +48,7 @@
|
||||||
const filename = q.get("f")
|
const filename = q.get("f")
|
||||||
document.getElementById("filename").innerText = filename
|
document.getElementById("filename").innerText = filename
|
||||||
try {
|
try {
|
||||||
const prop = await decodeBinary(filename)
|
const prop = await decodeBinary(filename, decodeProp)
|
||||||
dumpProp(prop, document.getElementById("data"))
|
dumpProp(prop, document.getElementById("data"))
|
||||||
if (prop.error) {
|
if (prop.error) {
|
||||||
showError(prop.error, filename)
|
showError(prop.error, filename)
|
||||||
|
|
12
index.html
12
index.html
|
@ -22,10 +22,11 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayEverything = async () => {
|
const displayEverything = async () => {
|
||||||
await displayList("heads.json", "heads")
|
await displayPropList("heads.json", "heads")
|
||||||
await displayList("props.json", "props")
|
await displayPropList("props.json", "props")
|
||||||
await displayList("misc.json", "misc")
|
await displayPropList("misc.json", "misc")
|
||||||
await displayList("beta.json", "beta")
|
await displayPropList("beta.json", "beta")
|
||||||
|
await displayBodyList("bodies.json", "bodies")
|
||||||
}
|
}
|
||||||
|
|
||||||
displayEverything()
|
displayEverything()
|
||||||
|
@ -48,6 +49,9 @@
|
||||||
Habitat source archive</a> released by the MADE on GitHub. Duplicates have been removed.
|
Habitat source archive</a> released by the MADE on GitHub. Duplicates have been removed.
|
||||||
Some of these objects were never actually included in any released version of Habitat.
|
Some of these objects were never actually included in any released version of Habitat.
|
||||||
</p>
|
</p>
|
||||||
|
<div id="bodies">
|
||||||
|
<h3>Bodies</h3>
|
||||||
|
</div>
|
||||||
<div id="heads">
|
<div id="heads">
|
||||||
<h3>The Hall Of Heads</h3>
|
<h3>The Hall Of Heads</h3>
|
||||||
<p>From: <a href="https://github.com/Museum-of-Art-and-Digital-Entertainment/habitat/tree/master/aric/mic/Gr/Heads">aric/mic/Gr/Heads</a></p>
|
<p>From: <a href="https://github.com/Museum-of-Art-and-Digital-Entertainment/habitat/tree/master/aric/mic/Gr/Heads">aric/mic/Gr/Heads</a></p>
|
||||||
|
|
115
index.js
115
index.js
|
@ -17,6 +17,9 @@ const makeCanvas = (w, h) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
const canvasFromBitmap = (bitmap) => {
|
const canvasFromBitmap = (bitmap) => {
|
||||||
|
if (bitmap.length == 0 || bitmap[0].length == 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
const h = bitmap.length
|
const h = bitmap.length
|
||||||
const w = bitmap[0].length * 2
|
const w = bitmap[0].length * 2
|
||||||
const canvas = makeCanvas(w, h)
|
const canvas = makeCanvas(w, h)
|
||||||
|
@ -473,6 +476,44 @@ const decodeProp = (data) => {
|
||||||
return prop
|
return prop
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const decodeLimb = (data, limb) => {
|
||||||
|
limb.frames = []
|
||||||
|
for (let iframe = 0; iframe < data.getUint8(0); iframe ++) {
|
||||||
|
limb.frames.push(data.getUint8(3 + iframe))
|
||||||
|
}
|
||||||
|
const celOffsetsOff = 4 + limb.frames.length
|
||||||
|
// I don't understand this at all, but it seems to be correct?
|
||||||
|
const maxCelIndex = Math.max(...limb.frames) + 1
|
||||||
|
limb.cels = []
|
||||||
|
for (let icel = 0; icel <= maxCelIndex; icel ++) {
|
||||||
|
const celOff = data.getUint16(celOffsetsOff + (icel * 2), LE)
|
||||||
|
limb.cels.push(decodeCel(new DataView(data.buffer, data.byteOffset + celOff)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodeBody = (data) => {
|
||||||
|
const body = {
|
||||||
|
data: data,
|
||||||
|
headCelNumber: data.getUint8(19),
|
||||||
|
frozenWhenStands: data.getUint8(20),
|
||||||
|
frontFacingLimbOrder: [],
|
||||||
|
backFacingLimbOrder: [],
|
||||||
|
limbs: []
|
||||||
|
}
|
||||||
|
for (let ilimb = 0; ilimb < 6; ilimb ++) {
|
||||||
|
body.frontFacingLimbOrder.push(data.getUint8(27 + ilimb))
|
||||||
|
body.backFacingLimbOrder.push(data.getUint8(33 + ilimb))
|
||||||
|
const limb = {
|
||||||
|
pattern: data.getUint8(21 + ilimb),
|
||||||
|
affectedByHeight: data.getUint8(39 + ilimb)
|
||||||
|
}
|
||||||
|
const limbOff = data.getUint16(7 + (ilimb * 2), LE)
|
||||||
|
decodeLimb(new DataView(data.buffer, limbOff), limb)
|
||||||
|
body.limbs.push(limb)
|
||||||
|
}
|
||||||
|
return body
|
||||||
|
}
|
||||||
|
|
||||||
const celsFromMask = (prop, celMask) => {
|
const celsFromMask = (prop, celMask) => {
|
||||||
const cels = []
|
const cels = []
|
||||||
for (let icel = 0; icel < 8; icel ++) {
|
for (let icel = 0; icel < 8; icel ++) {
|
||||||
|
@ -514,7 +555,7 @@ const compositeCels = (cels) => {
|
||||||
if (cel.canvas) {
|
if (cel.canvas) {
|
||||||
ctx.drawImage(cel.canvas, (cel.xOffset + xRel - minX) * 8, -(cel.yOffset + yRel) - minY)
|
ctx.drawImage(cel.canvas, (cel.xOffset + xRel - minX) * 8, -(cel.yOffset + yRel) - minY)
|
||||||
}
|
}
|
||||||
xRel += cel.xRel
|
xRel += cel.xRel
|
||||||
yRel += cel.yRel
|
yRel += cel.yRel
|
||||||
}
|
}
|
||||||
return { canvas: canvas, xOffset: minX * 8, yOffset: minY, w: w, h: h }
|
return { canvas: canvas, xOffset: minX * 8, yOffset: minY, w: w, h: h }
|
||||||
|
@ -535,11 +576,19 @@ const textNode = (text, type = "span") => {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const wrapLink = (element, href) => {
|
||||||
|
const link = document.createElement("a")
|
||||||
|
link.href = href
|
||||||
|
link.appendChild(element)
|
||||||
|
return link
|
||||||
|
}
|
||||||
|
|
||||||
const linkDetail = (element, filename) => {
|
const linkDetail = (element, filename) => {
|
||||||
const detailLink = document.createElement("a")
|
return wrapLink(element, `detail.html?f=${filename}`)
|
||||||
detailLink.href = `detail.html?f=${filename}`
|
}
|
||||||
detailLink.appendChild(element)
|
|
||||||
return detailLink
|
const linkBody = (element, filename) => {
|
||||||
|
return wrapLink(element, `body.html?f=${filename}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const createAnimation = (prop, animation) => {
|
const createAnimation = (prop, animation) => {
|
||||||
|
@ -608,9 +657,9 @@ const showCels = (prop, container) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const decodeBinary = async (filename) => {
|
const decodeBinary = async (filename, decoder) => {
|
||||||
try {
|
try {
|
||||||
const prop = decodeProp(await readBinary(filename))
|
const prop = decoder(await readBinary(filename))
|
||||||
prop.filename = filename
|
prop.filename = filename
|
||||||
return prop
|
return prop
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -618,11 +667,11 @@ const decodeBinary = async (filename) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const showError = (e, filename) => {
|
const showError = (e, filename, link = (x,_) => x) => {
|
||||||
const container = document.getElementById("errors")
|
const container = document.getElementById("errors")
|
||||||
const errNode = document.createElement("p")
|
const errNode = document.createElement("p")
|
||||||
console.error(e)
|
console.error(e)
|
||||||
errNode.appendChild(linkDetail(textNode(filename, "b"), filename))
|
errNode.appendChild(link(textNode(filename, "b"), filename))
|
||||||
errNode.appendChild(textNode(e.toString(), "p"))
|
errNode.appendChild(textNode(e.toString(), "p"))
|
||||||
if (e.stack) {
|
if (e.stack) {
|
||||||
errNode.appendChild(textNode(e.stack.toString(), "pre"))
|
errNode.appendChild(textNode(e.stack.toString(), "pre"))
|
||||||
|
@ -630,26 +679,22 @@ const showError = (e, filename) => {
|
||||||
container.appendChild(errNode)
|
container.appendChild(errNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayFile = async (filename, container) => {
|
const displayFile = async (filename, container, decode, display, link = (x,_) => x) => {
|
||||||
const prop = await decodeBinary(filename)
|
const prop = await decodeBinary(filename, decode)
|
||||||
if (prop.error) {
|
if (prop.error) {
|
||||||
container.parentNode.removeChild(container)
|
container.parentNode.removeChild(container)
|
||||||
showError(prop.error, prop.filename)
|
showError(prop.error, prop.filename, link)
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
if (prop.animations.length > 0) {
|
display(prop, container)
|
||||||
showAnimations(prop, container)
|
|
||||||
} else {
|
|
||||||
showStates(prop, container)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
container.parentNode.removeChild(container)
|
container.parentNode.removeChild(container)
|
||||||
showError(e, prop.filename)
|
showError(e, prop.filename, link)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const displayList = async (indexFile, containerId) => {
|
const displayList = async (indexFile, containerId, decode, display, link = (x,_) => x) => {
|
||||||
const response = await fetch(indexFile, { cache: "no-cache" })
|
const response = await fetch(indexFile, { cache: "no-cache" })
|
||||||
const filenames = await response.json()
|
const filenames = await response.json()
|
||||||
const container = document.getElementById(containerId)
|
const container = document.getElementById(containerId)
|
||||||
|
@ -659,12 +704,32 @@ const displayList = async (indexFile, containerId) => {
|
||||||
fileContainer.style.margin = "2px"
|
fileContainer.style.margin = "2px"
|
||||||
fileContainer.style.padding = "2px"
|
fileContainer.style.padding = "2px"
|
||||||
fileContainer.style.display = "inline-block"
|
fileContainer.style.display = "inline-block"
|
||||||
fileContainer.appendChild(linkDetail(textNode(filename, "div"), filename))
|
fileContainer.appendChild(link(textNode(filename, "div"), filename))
|
||||||
container.appendChild(fileContainer)
|
container.appendChild(fileContainer)
|
||||||
if (filename != 'heads/fhead.bin') {
|
displayFile(filename, fileContainer, decode, display, link)
|
||||||
displayFile(filename, fileContainer)
|
|
||||||
} else {
|
|
||||||
fileContainer.appendChild(textNode("CW: Pixel genitals"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const displayProp = (prop, container) => {
|
||||||
|
if (prop.filename == 'heads/fhead.bin') {
|
||||||
|
container.appendChild(textNode("CW: Pixel genitals"))
|
||||||
|
} else if (prop.animations.length > 0) {
|
||||||
|
showAnimations(prop, container)
|
||||||
|
} else {
|
||||||
|
showStates(prop, container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayBody = (body, container) => {
|
||||||
|
for (const limb of body.limbs) {
|
||||||
|
showCels(limb, container)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayPropList = async (indexFile, containerId) => {
|
||||||
|
await displayList(indexFile, containerId, decodeProp, displayProp, linkDetail)
|
||||||
|
}
|
||||||
|
|
||||||
|
const displayBodyList = async (indexFile, containerId) => {
|
||||||
|
await displayList(indexFile, containerId, decodeBody, displayBody, linkBody)
|
||||||
|
}
|
|
@ -1 +1 @@
|
||||||
["misc/Avatar.bin","misc/Drag.bin","misc/Gunship.bin","misc/Peng_uppercase.bin","misc/Spid.bin","misc/Tank.bin","misc/Tentacle.bin","misc/angelwing.bin","misc/kenhead201.bin","misc/kenhead202.bin","misc/kenhead203.bin","misc/kenhead205.bin","misc/kenhead206.bin","misc/kenhead207.bin","misc/kenhead208.bin","misc/kenhead209.bin","misc/kenhead210.bin","misc/kenhead211.bin","misc/kenhead212.bin","misc/kenhead213.bin","misc/kenhead214.bin","misc/kenhead215.bin","misc/kenhead216.bin","misc/kenhead217.bin","misc/kenhead218.bin","misc/kenhead219.bin","misc/kenhead220.bin","misc/kenhead221.bin","misc/kenhead222.bin","misc/kenhead223.bin","misc/kenhead224.bin","misc/kenhead225.bin","misc/kenhead226.bin","misc/kenhead227.bin","misc/kenhead228.bin","misc/kenhead229.bin","misc/kenhead230.bin","misc/kenhead231.bin","misc/kenhead232.bin","misc/kenhead233.bin","misc/kenhead234.bin","misc/kenhead235.bin","misc/kenhead236.bin","misc/kenhead237.bin","misc/kenhead238.bin","misc/kenhead239.bin","misc/mouse0.bin","misc/newhab1.bin","misc/newhab10.bin","misc/newhab11.bin","misc/newhab13.bin","misc/newhab14.bin","misc/newhab15.bin","misc/newhab16.bin","misc/newhab17.bin","misc/newhab18.bin","misc/newhab19.bin","misc/newhab20.bin","misc/newhab21.bin","misc/newhab22.bin","misc/newhab23.bin","misc/newhab24.bin","misc/newhab3.bin","misc/newhab5.bin","misc/newhab6.bin","misc/newhab7.bin","misc/newhab8.bin","misc/newhab9.bin","misc/nillhead.bin"]
|
["misc/angelwing.bin","misc/kenhead201.bin","misc/kenhead202.bin","misc/kenhead203.bin","misc/kenhead205.bin","misc/kenhead206.bin","misc/kenhead207.bin","misc/kenhead208.bin","misc/kenhead209.bin","misc/kenhead210.bin","misc/kenhead211.bin","misc/kenhead212.bin","misc/kenhead213.bin","misc/kenhead214.bin","misc/kenhead215.bin","misc/kenhead216.bin","misc/kenhead217.bin","misc/kenhead218.bin","misc/kenhead219.bin","misc/kenhead220.bin","misc/kenhead221.bin","misc/kenhead222.bin","misc/kenhead223.bin","misc/kenhead224.bin","misc/kenhead225.bin","misc/kenhead226.bin","misc/kenhead227.bin","misc/kenhead228.bin","misc/kenhead229.bin","misc/kenhead230.bin","misc/kenhead231.bin","misc/kenhead232.bin","misc/kenhead233.bin","misc/kenhead234.bin","misc/kenhead235.bin","misc/kenhead236.bin","misc/kenhead237.bin","misc/kenhead238.bin","misc/kenhead239.bin","misc/mouse0.bin","misc/newhab1.bin","misc/newhab10.bin","misc/newhab11.bin","misc/newhab13.bin","misc/newhab14.bin","misc/newhab15.bin","misc/newhab16.bin","misc/newhab17.bin","misc/newhab18.bin","misc/newhab19.bin","misc/newhab20.bin","misc/newhab21.bin","misc/newhab22.bin","misc/newhab23.bin","misc/newhab24.bin","misc/newhab3.bin","misc/newhab5.bin","misc/newhab6.bin","misc/newhab7.bin","misc/newhab8.bin","misc/newhab9.bin","misc/nillhead.bin"]
|
Loading…
Reference in a new issue