First working body decoding

This commit is contained in:
Jeremy Penner 2023-12-27 15:34:56 -05:00
parent 97c01b459f
commit 2b60bacad6
18 changed files with 205 additions and 31 deletions

42
avatars.md Normal file
View 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
View 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

Binary file not shown.

BIN
bodies/MP.bin Normal file

Binary file not shown.

BIN
bodies/fpants.bin Normal file

Binary file not shown.

BIN
bodies/nillhead.bin Normal file

Binary file not shown.

62
body.html Normal file
View 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>

View file

@ -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)

View file

@ -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>

109
index.js
View file

@ -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 ++) {
@ -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) }
}
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 { } else {
fileContainer.appendChild(textNode("CW: Pixel genitals")) 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)
} }

View file

@ -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"]