From 2b60bacad6454ca40172dd0d0a7d381b15552282 Mon Sep 17 00:00:00 2001 From: Jeremy Penner Date: Wed, 27 Dec 2023 15:34:56 -0500 Subject: [PATCH] First working body decoding --- avatars.md | 42 ++++++++++ bodies.json | 1 + {misc => bodies}/Avatar.bin | Bin {misc => bodies}/Drag.bin | Bin {misc => bodies}/Gunship.bin | Bin bodies/Heli.bin | Bin 0 -> 424 bytes bodies/MP.bin | Bin 0 -> 1106 bytes {misc => bodies}/Peng_uppercase.bin | Bin {misc => bodies}/Spid.bin | Bin {misc => bodies}/Tank.bin | Bin {misc => bodies}/Tentacle.bin | Bin bodies/fpants.bin | Bin 0 -> 88 bytes bodies/nillhead.bin | Bin 0 -> 70 bytes body.html | 62 +++++++++++++++ detail.html | 2 +- index.html | 12 ++- index.js | 115 ++++++++++++++++++++++------ misc.json | 2 +- 18 files changed, 205 insertions(+), 31 deletions(-) create mode 100644 avatars.md create mode 100644 bodies.json rename {misc => bodies}/Avatar.bin (100%) rename {misc => bodies}/Drag.bin (100%) rename {misc => bodies}/Gunship.bin (100%) create mode 100644 bodies/Heli.bin create mode 100644 bodies/MP.bin rename {misc => bodies}/Peng_uppercase.bin (100%) rename {misc => bodies}/Spid.bin (100%) rename {misc => bodies}/Tank.bin (100%) rename {misc => bodies}/Tentacle.bin (100%) create mode 100644 bodies/fpants.bin create mode 100644 bodies/nillhead.bin create mode 100644 body.html diff --git a/avatars.md b/avatars.md new file mode 100644 index 0000000..e0c95b1 --- /dev/null +++ b/avatars.md @@ -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) diff --git a/bodies.json b/bodies.json new file mode 100644 index 0000000..a1e2f73 --- /dev/null +++ b/bodies.json @@ -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"] \ No newline at end of file diff --git a/misc/Avatar.bin b/bodies/Avatar.bin similarity index 100% rename from misc/Avatar.bin rename to bodies/Avatar.bin diff --git a/misc/Drag.bin b/bodies/Drag.bin similarity index 100% rename from misc/Drag.bin rename to bodies/Drag.bin diff --git a/misc/Gunship.bin b/bodies/Gunship.bin similarity index 100% rename from misc/Gunship.bin rename to bodies/Gunship.bin diff --git a/bodies/Heli.bin b/bodies/Heli.bin new file mode 100644 index 0000000000000000000000000000000000000000..dd455283f2a0c0ba2bbac7a97cffb5a9dea48131 GIT binary patch literal 424 zcmYk2Jxc>Y5Qg8`y*tg9?1c-dlqiA{{5YdQ3ybtt0l~`7#?r!mH_`tf77{JbpxD_Z zjX%LYWl%)R6yeq}8&Ozh-g#!`Wmru&^buhl3BIsLr*uXZfFLPALVEr*GGmH7mqi@l zgexK;g3P$a@)A65(5m+CTWITQ&)NiSnB?;WBW3Q{`rO?o{a| zdY!6zx#U!MJl~Wku!{|`j9q9UrSQjWL2(FE=)G($wFd+HHK9aEG#CtHG~3*3Jwm&e}eZ~is96e5S=cK zlJgvNB!NhU#l^TVGs$8wFff=vIKOvQ65W08n!ES;@%g^*^FFNdht&y<&@et7K9q#zcwwu>S>U6~s66r% zcxrsI%8moOOdemU^o&ZeJAx(Qg=7*Cj0CVkH2ok91Fk9zI;#npM|#>p+T%h4<~NXz z^}7jhM;wRCqgTl~RI6U6i1C9U^bAtI>Sh$M6cgfYXop*IJkndOx)+9tvVrY)a88*X zFQ(NciDk$0>I`yyV^EJpJ2+O1#j(q9Ssqe?c!P{%jf_ABEbSwz3Zbr}!g^+|La)Ap zdb7U1;v#2C4w?#XayLR`YHPDI(6vILr|16E)F75*09}fbYop{NJvaGzW8?AUByx7i z;Kc=YwN`^#U7Mf3+ib#em!5BKa;yLH?b_`A{!0qn5dw9k(EN)J*F2n?o`#;=+gqZB zWyflhP>GgAmK}8oC5$Fpl2R&hnXj%B`(35!0<=Xw^aSa2+Vpuzbu0{Y8uAKm;{g`& z8eee|r%SCH7Ry)={cwfI!pdi%re~qIk9c6IHrW|^i)cvImCt9pMz_z;Pfw4?)m7-& zA7>9hiClhsyiys1sxCln^Y6hbw7(r32$e+<{n<(-$&Aj9+Y?pBSZv&$Lg56~;m?~{ z_F@r5&ME&6>hvg%!!lBFRJBSgJ9O*t>})KX@Cr2EgLSm^p`9ugXA)Zb`1tH1=>MEL vkb=$1@en&B6~$8;_%--5pweCWzP>*EzK!@Kdxv&Z@9?hb9om(>!#m~|`3L^3 literal 0 HcmV?d00001 diff --git a/misc/Peng_uppercase.bin b/bodies/Peng_uppercase.bin similarity index 100% rename from misc/Peng_uppercase.bin rename to bodies/Peng_uppercase.bin diff --git a/misc/Spid.bin b/bodies/Spid.bin similarity index 100% rename from misc/Spid.bin rename to bodies/Spid.bin diff --git a/misc/Tank.bin b/bodies/Tank.bin similarity index 100% rename from misc/Tank.bin rename to bodies/Tank.bin diff --git a/misc/Tentacle.bin b/bodies/Tentacle.bin similarity index 100% rename from misc/Tentacle.bin rename to bodies/Tentacle.bin diff --git a/bodies/fpants.bin b/bodies/fpants.bin new file mode 100644 index 0000000000000000000000000000000000000000..f4c1c1f1da3f42dc302c167725eef0051c5bc133 GIT binary patch literal 88 zcmZQzU|?uqXJD3OU}j*@TD3}b>NF4v4bz$$r8+eYhNdl@6}EIH6fKJkTNxR)6a%S7 KhCvY5suckGb091L literal 0 HcmV?d00001 diff --git a/bodies/nillhead.bin b/bodies/nillhead.bin new file mode 100644 index 0000000000000000000000000000000000000000..ee088addda2165830d0b1d1adf0d6a118a9b8341 GIT binary patch literal 70 zcmY#jU|=}F$soY6P3Yf028RD^9RGeYFfc7rTC_-kiD{8S6NFpld4FJ}U6j}fP literal 0 HcmV?d00001 diff --git a/body.html b/body.html new file mode 100644 index 0000000..36949b0 --- /dev/null +++ b/body.html @@ -0,0 +1,62 @@ + + + + + Inhabitor - The Habitat Inspector + + + + +

+
+

Cels

+
+
+

Data

+
+
+ Back + + + \ No newline at end of file diff --git a/detail.html b/detail.html index 6f0737f..fc7dcbc 100644 --- a/detail.html +++ b/detail.html @@ -48,7 +48,7 @@ const filename = q.get("f") document.getElementById("filename").innerText = filename try { - const prop = await decodeBinary(filename) + const prop = await decodeBinary(filename, decodeProp) dumpProp(prop, document.getElementById("data")) if (prop.error) { showError(prop.error, filename) diff --git a/index.html b/index.html index f74ea7f..a5d0b8d 100644 --- a/index.html +++ b/index.html @@ -22,10 +22,11 @@ } const displayEverything = async () => { - await displayList("heads.json", "heads") - await displayList("props.json", "props") - await displayList("misc.json", "misc") - await displayList("beta.json", "beta") + await displayPropList("heads.json", "heads") + await displayPropList("props.json", "props") + await displayPropList("misc.json", "misc") + await displayPropList("beta.json", "beta") + await displayBodyList("bodies.json", "bodies") } displayEverything() @@ -48,6 +49,9 @@ Habitat source archive released by the MADE on GitHub. Duplicates have been removed. Some of these objects were never actually included in any released version of Habitat.

+
+

Bodies

+

The Hall Of Heads

From: aric/mic/Gr/Heads

diff --git a/index.js b/index.js index ccc9b1a..3f8cf31 100644 --- a/index.js +++ b/index.js @@ -17,6 +17,9 @@ const makeCanvas = (w, h) => { } const canvasFromBitmap = (bitmap) => { + if (bitmap.length == 0 || bitmap[0].length == 0) { + return null + } const h = bitmap.length const w = bitmap[0].length * 2 const canvas = makeCanvas(w, h) @@ -473,6 +476,44 @@ const decodeProp = (data) => { 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 cels = [] for (let icel = 0; icel < 8; icel ++) { @@ -514,7 +555,7 @@ const compositeCels = (cels) => { if (cel.canvas) { ctx.drawImage(cel.canvas, (cel.xOffset + xRel - minX) * 8, -(cel.yOffset + yRel) - minY) } - xRel += cel.xRel + xRel += cel.xRel yRel += cel.yRel } return { canvas: canvas, xOffset: minX * 8, yOffset: minY, w: w, h: h } @@ -535,11 +576,19 @@ const textNode = (text, type = "span") => { return node } +const wrapLink = (element, href) => { + const link = document.createElement("a") + link.href = href + link.appendChild(element) + return link +} + const linkDetail = (element, filename) => { - const detailLink = document.createElement("a") - detailLink.href = `detail.html?f=${filename}` - detailLink.appendChild(element) - return detailLink + return wrapLink(element, `detail.html?f=${filename}`) +} + +const linkBody = (element, filename) => { + return wrapLink(element, `body.html?f=${filename}`) } const createAnimation = (prop, animation) => { @@ -608,9 +657,9 @@ const showCels = (prop, container) => { } } -const decodeBinary = async (filename) => { +const decodeBinary = async (filename, decoder) => { try { - const prop = decodeProp(await readBinary(filename)) + const prop = decoder(await readBinary(filename)) prop.filename = filename return prop } 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 errNode = document.createElement("p") console.error(e) - errNode.appendChild(linkDetail(textNode(filename, "b"), filename)) + errNode.appendChild(link(textNode(filename, "b"), filename)) errNode.appendChild(textNode(e.toString(), "p")) if (e.stack) { errNode.appendChild(textNode(e.stack.toString(), "pre")) @@ -630,26 +679,22 @@ const showError = (e, filename) => { container.appendChild(errNode) } -const displayFile = async (filename, container) => { - const prop = await decodeBinary(filename) +const displayFile = async (filename, container, decode, display, link = (x,_) => x) => { + const prop = await decodeBinary(filename, decode) if (prop.error) { container.parentNode.removeChild(container) - showError(prop.error, prop.filename) + showError(prop.error, prop.filename, link) } else { try { - if (prop.animations.length > 0) { - showAnimations(prop, container) - } else { - showStates(prop, container) - } + display(prop, container) } catch (e) { 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 filenames = await response.json() const container = document.getElementById(containerId) @@ -659,12 +704,32 @@ const displayList = async (indexFile, containerId) => { fileContainer.style.margin = "2px" fileContainer.style.padding = "2px" fileContainer.style.display = "inline-block" - fileContainer.appendChild(linkDetail(textNode(filename, "div"), filename)) + fileContainer.appendChild(link(textNode(filename, "div"), filename)) container.appendChild(fileContainer) - if (filename != 'heads/fhead.bin') { - displayFile(filename, fileContainer) - } else { - fileContainer.appendChild(textNode("CW: Pixel genitals")) - } + displayFile(filename, fileContainer, decode, display, link) } } + +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) +} \ No newline at end of file diff --git a/misc.json b/misc.json index c4fd1e3..c001fab 100644 --- a/misc.json +++ b/misc.json @@ -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"] \ No newline at end of file +["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"] \ No newline at end of file