From c8238f185381504c805fc9a8a0fa349a8f6ee627 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Sun, 25 Mar 2018 00:18:27 +0000 Subject: [PATCH] Turns out the palette is actually identical to that in wh40k.pcx --- .gitignore | 1 - doc/formats/obj.md | 6 +- internal/data/palette.go | 11 +- investigation/Palette/generate-palette | 34 + investigation/Palette/palette.asn | 12 + investigation/Palette/palette.obj | Bin 0 -> 957 bytes ...te.obj.rendered-by-wh40k-td-screenshot.png | Bin 0 -> 1924 bytes investigation/Palette/palette.ppm | 772 ++++++++++++++++++ investigation/Palette/palette.rgb.raw | Bin 0 -> 768 bytes investigation/Palette/palette.txt | 13 + investigation/Palette/palette.xcf | Bin 0 -> 1428 bytes scripts/try-uncompress | 123 ++- 12 files changed, 959 insertions(+), 13 deletions(-) create mode 100755 investigation/Palette/generate-palette create mode 100644 investigation/Palette/palette.asn create mode 100644 investigation/Palette/palette.obj create mode 100644 investigation/Palette/palette.obj.rendered-by-wh40k-td-screenshot.png create mode 100644 investigation/Palette/palette.ppm create mode 100644 investigation/Palette/palette.rgb.raw create mode 100644 investigation/Palette/palette.txt create mode 100644 investigation/Palette/palette.xcf diff --git a/.gitignore b/.gitignore index 584d3f9..a90871b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -/investigation /loader /orig /view-obj diff --git a/doc/formats/obj.md b/doc/formats/obj.md index 77fab8b..004c842 100644 --- a/doc/formats/obj.md +++ b/doc/formats/obj.md @@ -220,8 +220,10 @@ be 128x96, or 12288 bytes at 8bpp. It seems pixels can be larger than a cell - TZEENTCH.OBJ is almost 2 cells high, for instance. -0x002-0x003 changes in step with total number of pixels, but that doesn't seem -to account for the difference. +Loading a constructed `palette.obj` containing 1x63 pixels, with 0x0000 for the +first 32 bits, caused WH40K_TD.exe to render the pixels far to the left and above +the cell the palette objects were being placed into. This suggests they +represent an x,y offset to draw to. Need to experiment more. Considering sprites with a 1,1 x,y. diff --git a/internal/data/palette.go b/internal/data/palette.go index 21f548b..d32bceb 100644 --- a/internal/data/palette.go +++ b/internal/data/palette.go @@ -1,15 +1,12 @@ package data -import ( - "image/color" -) +import "image/color" var ( - // From Pic/wh40k.pcx. I didn't feel like implementing the read-in for this. - // Not yet, anyway. - // TODO: at least one of these colours is actually transparent. + Transparent = color.RGBA{R: 0, G: 0, B: 0, A: 0} + ColorPalette = color.Palette{ - color.RGBA{R: 0, G: 0, B: 0, A: 255}, + Transparent, color.RGBA{R: 128, G: 0, B: 0, A: 255}, color.RGBA{R: 0, G: 128, B: 0, A: 255}, color.RGBA{R: 128, G: 128, B: 0, A: 255}, diff --git a/investigation/Palette/generate-palette b/investigation/Palette/generate-palette new file mode 100755 index 0000000..72b39a8 --- /dev/null +++ b/investigation/Palette/generate-palette @@ -0,0 +1,34 @@ +#!/usr/bin/env ruby + +lines = File.read("palette.ppm").split("\n") +lines = lines.select { |l| l[0] != "#" } + +raise "Not an ASCII ppm" if lines.shift != "P3" +raise "Incorrect dimensions" unless lines.shift == "1 256" +raise "Incorrect maxval" unless lines.shift == "255" +raise "Too many lines left" unless lines.size == 768 + +puts < d:\warflics\missions\jungtil.flc +# + +0:DEF 2; + +0:TYPE 2; + +END OF FILE + + diff --git a/investigation/Palette/palette.obj b/investigation/Palette/palette.obj new file mode 100644 index 0000000000000000000000000000000000000000..eaf9938fd9ef670f5eb28aa393920a6c70374821 GIT binary patch literal 957 zcmZvbWzZD_5Jk_SyQQT|ltw}X38ke&y1Pq33F%O}LAsId?(Qy8xm#;#qjqlAXhQY8H z4#Q&vjEIpiGDg9u7!9Li42+4fFgC`)xEK#V!uXf~6JjDvj7cylevHX5Ii|prms~V;;pJ77obgx}*2*crRvkN6XI#ctR=WH+Ij z>VZFFPwa)gu@Cmee%K!e;6NONzu>Pp7>D3c9EQVj1dhZ}I2y;`SR9AraRN@n-*6I6 z#wj=zr{Q#*firOy&c-=77w6%8{2dqILR^H4aS1NPWw;zy;7VMDt8opk#dWwIH(>DK zLN&DsH{%xEira8I?!cY63wPrl+>85gKOVq?cnA;U5j={=@Hn2plXwbG;~6}Q=kO0a zkALD{cmXfsCA^GR@G4%z>v#ii;w`+5cknLW!~6I*{)7MG1AK^&@G(BY|L`gPkI(Qq UzIeN-;6p|Z>4M}-e1)%h13J5lTL1t6 literal 0 HcmV?d00001 diff --git a/investigation/Palette/palette.obj.rendered-by-wh40k-td-screenshot.png b/investigation/Palette/palette.obj.rendered-by-wh40k-td-screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..804e03ed81104c7aa69996cb1809093f7957ddc3 GIT binary patch literal 1924 zcmc&#Yfuwc6kZiEH871L#t;#sJe-%urSEyq4&Qb3&` z7!q2~1b^5pVa)^|;>>}F;18Ps4iQYiA%fqV%RR6#A9=#rGm|=s#5d7ORdMT81H-Dv z&`pKo`Zm`DCbiWwtS0*hcG@r|#@F%b{tbQ`q*`PYib8^nOl^XW=HDP^8`c&^GmDeq^@WH86uVRY8V@V0xC^U_*bTJG}qD6|@n>jqhzMPeL$`QVy4r#nO zV#_$KrWxygPGzsH+1oI1e;pp1JVJh=NyRL`zFh!&`40ajsXyh8sa`j6$|}(EBF1v; zGo8b-%yf;=WL)Y9vtdo!;p2qYJ9lV-8GZ}h%MCq*ptg(sZl;lQF(SL5G;H#*3+S7go?L6?8%aj1tkE1z5gyXfcgHoVH z7}$~O-|;Z_(<4aS9_G$sN?jEvU%?MStK1ZBD3>foi}Ep5v7I>F4>|cL^WdH}TIHu( zX#VL0F+I{=7C0o34QqA!mSW0~LW94nelxP|VMca4rNFD^&-_O5K^Zp?DP`UN+hGf7 z3)}KqY|*zKEOD0(s}FIQdCfu_m=CHhyQXHL|wjTV)mpT zFc8_!rf#%!+GZ!Mz!bEBx*fw^8`+kzP|d-~yHb{-igN)`-q-I|fodaj+a^|G`=StO zn`-VJy_~x#%k+FZk7UvQk#9RoxI>fSotfThN-}UmEO^%`hVyi%_ezWcyEG*a_7{w= zHgHQTP;)YmB#XKcO)5c;fSg(+t|d{l*!*ySHv>MRvtGgf0-zmqI)E<{x?-!A!0wnv z2+DDU@8;VO$7sSW@d1 zLl$odXzfiZtXfPKcLWoBs>y5|oF)K7wE{%eCFp(ucBilIoO&x84R|0}Vbt&HbD~CE$uBv^Lf`OVQYuzBtnd ziwCl!w^z_Wowei5m0|AMaKvToYP>AbKqq{b;Yc^?(4#ysaUKL7Tm-l}`7Q2@)!GJ; zd8DiXoxJ$iQru`G-0`BOl2Zl*qWMYf$RovziaD3}3oXW@fQ)*&fn6B=!!ln1se~om z(438xf4|ig<^CpnPM%eQ)>7f@!2s!IFhDEW*c*RP{ZpmZ1n@bGAe9rlSUnvkn|JL6 z+MF2#pqq4gn9S|5-m@?|631I4`s)*NFu)sciYzr~!r15oilk3qqOOocUVn9x_@fQ< z>zekSov|{M5Oj+EwH}b;vId)+1o~6+bC5DL!3p^)nV2?LFc~q%Z_KekTh{K=K=ckc z7^-^CrmkzC^K5YE#DTg}58xi&nCnX$M)&P!M+Qo&kz#)-E2e{SIaT*&`2KYn|> jUo{`UJ;16tG_A>}+3c&9_exs8zJx*o!ve1OM;H79swy{m literal 0 HcmV?d00001 diff --git a/investigation/Palette/palette.ppm b/investigation/Palette/palette.ppm new file mode 100644 index 0000000..8d96e13 --- /dev/null +++ b/investigation/Palette/palette.ppm @@ -0,0 +1,772 @@ +P3 +# CREATOR: GIMP PNM Filter Version 1.1 +1 256 +255 +255 +255 +255 +128 +0 +0 +0 +128 +0 +128 +128 +0 +0 +0 +128 +128 +0 +128 +0 +128 +128 +192 +192 +192 +192 +220 +192 +166 +202 +240 +255 +255 +255 +240 +240 +240 +221 +221 +221 +203 +203 +203 +187 +187 +187 +178 +178 +178 +168 +168 +168 +157 +157 +157 +147 +147 +147 +137 +137 +137 +127 +127 +127 +117 +117 +117 +106 +106 +106 +96 +96 +96 +86 +86 +86 +76 +76 +76 +61 +61 +61 +49 +49 +49 +36 +36 +36 +24 +24 +24 +12 +12 +12 +0 +0 +0 +134 +134 +255 +113 +113 +241 +93 +93 +228 +72 +72 +214 +63 +63 +200 +55 +55 +186 +46 +46 +172 +38 +38 +158 +29 +29 +144 +20 +20 +131 +12 +12 +117 +3 +3 +103 +3 +3 +91 +3 +3 +79 +3 +3 +68 +3 +3 +56 +255 +145 +145 +242 +123 +123 +230 +101 +101 +217 +79 +79 +205 +70 +70 +193 +61 +61 +181 +53 +53 +169 +44 +44 +157 +35 +35 +144 +26 +26 +132 +18 +18 +120 +9 +9 +108 +8 +8 +94 +7 +7 +79 +7 +7 +65 +6 +6 +147 +142 +185 +132 +126 +172 +117 +109 +159 +102 +93 +146 +95 +86 +133 +88 +78 +123 +82 +73 +115 +77 +67 +107 +72 +61 +100 +67 +55 +92 +61 +50 +84 +56 +44 +76 +51 +38 +68 +46 +32 +60 +40 +27 +52 +35 +21 +44 +200 +150 +137 +187 +130 +115 +175 +110 +94 +164 +95 +77 +154 +79 +61 +143 +64 +44 +137 +60 +42 +132 +55 +38 +125 +50 +33 +120 +48 +29 +111 +44 +26 +103 +39 +24 +94 +35 +21 +83 +30 +18 +72 +25 +14 +61 +20 +10 +121 +107 +34 +109 +94 +29 +96 +82 +25 +84 +69 +20 +77 +62 +17 +70 +55 +14 +63 +47 +12 +56 +40 +9 +93 +120 +53 +80 +103 +42 +66 +86 +31 +53 +69 +20 +49 +60 +16 +45 +52 +12 +43 +44 +10 +43 +38 +8 +136 +145 +44 +118 +128 +37 +101 +111 +30 +83 +94 +23 +70 +79 +17 +56 +65 +11 +42 +50 +6 +28 +36 +0 +57 +134 +64 +48 +118 +54 +38 +101 +43 +29 +85 +33 +22 +71 +25 +15 +58 +17 +7 +45 +8 +0 +32 +0 +143 +87 +56 +126 +75 +45 +110 +64 +35 +93 +52 +24 +85 +44 +16 +72 +36 +12 +64 +32 +8 +56 +24 +4 +127 +96 +54 +115 +85 +46 +102 +75 +39 +90 +64 +31 +82 +57 +25 +75 +51 +20 +68 +44 +15 +61 +38 +10 +141 +86 +56 +126 +75 +46 +110 +65 +36 +95 +54 +26 +88 +51 +25 +73 +40 +18 +57 +29 +10 +42 +18 +3 +172 +199 +199 +138 +173 +173 +104 +148 +148 +71 +122 +122 +37 +97 +97 +3 +71 +71 +4 +56 +56 +4 +41 +41 +217 +209 +200 +202 +194 +184 +188 +178 +167 +173 +163 +151 +158 +147 +134 +148 +136 +123 +137 +125 +112 +126 +114 +101 +116 +104 +91 +105 +93 +80 +94 +82 +69 +84 +71 +58 +73 +60 +47 +62 +50 +36 +52 +39 +25 +41 +28 +14 +231 +232 +207 +219 +217 +180 +208 +201 +152 +196 +186 +125 +184 +171 +98 +173 +155 +70 +161 +140 +43 +150 +129 +39 +139 +119 +37 +127 +109 +33 +117 +99 +29 +105 +89 +25 +90 +76 +21 +75 +62 +18 +60 +49 +14 +45 +35 +10 +128 +99 +127 +113 +73 +112 +97 +47 +97 +82 +21 +82 +75 +2 +74 +68 +2 +67 +52 +3 +50 +35 +4 +33 +247 +178 +102 +229 +152 +75 +212 +127 +48 +194 +101 +21 +179 +87 +16 +161 +73 +12 +142 +59 +9 +124 +45 +5 +255 +0 +0 +194 +3 +3 +161 +2 +2 +255 +227 +11 +209 +185 +8 +169 +150 +6 +103 +190 +255 +2 +130 +232 +4 +4 +209 +0 +255 +0 +7 +180 +7 +3 +132 +3 +255 +114 +230 +255 +17 +205 +203 +6 +156 +246 +164 +73 +221 +123 +16 +177 +95 +4 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +0 +132 +18 +18 +113 +13 +13 +94 +7 +7 +107 +8 +8 +120 +9 +9 +193 +62 +62 +181 +54 +54 +168 +45 +45 +157 +36 +36 +144 +27 +27 +62 +50 +36 +57 +45 +31 +52 +39 +25 +46 +34 +20 +41 +28 +14 +255 +251 +240 +160 +160 +164 +128 +128 +128 +255 +0 +0 +0 +255 +0 +255 +255 +0 +0 +0 +255 +255 +0 +255 +0 +255 +255 +255 +255 +255 diff --git a/investigation/Palette/palette.rgb.raw b/investigation/Palette/palette.rgb.raw new file mode 100644 index 0000000000000000000000000000000000000000..8b1aeb4fa887a18647034520b7441e2a82cd5e45 GIT binary patch literal 768 zcmZQzU|?uqXlMXYKpIFK0D?OQmYw?W|Ns9FA3of@d-wF|)4O-?-n41ciWMv7&Ye4X z^5o9W&ieZL($dnbtgM8Dgs`wMA0HoETU$dzLlqSj2?+@v9v+~HZEgPx3qQujKJoCl zW^aGO+)0vs0nVJ2WnO&HfE&flO_^G=3S!(J{fB&;? zZU=2`x0;%+)X|x%tUN(Vszpesf|E0cgCmZe-JhM^k&O*##pJ%7Ep=;3bLXeUPKpm} zjqs}u@+|gs&i1fPaW;>!H43rN@iA6&(NnO|kTy{k)j2V(b9Ynm`nXbF8rsm59$BDc73tBjGYZm6hg;Nn>*hHq$C^ll>IitK@Hi-NSV*wcCzut7>ZN(B zM>)s`SxR~vi@50U+p2N(h5^;<XMo=e9vE;zkPGdg_ARm?5f?dI%(}}w}n00(;C&g%T?=h6-$$4Gb1IVd_=wN zgn%J$W5}ng%+-)wU+7tosGk@l8syF7<-+7_!fd3>qWFDN+S3`{SLzK8rHXD27g*@Y z(`U_Dqs#iAf#DD{^Fk)3|BtyZ?&MfGjV*oOf2O7vEG!op{xh&|VP|e({$KR$zu?)^ zY;(RX@w{6turZzmXdN;Dri4OXUSKlF=HLJ(iGy}_Tg}W?=<3c@QJEku4fKwst~}63 fdP*WdPyPS>VZnkW4Gj%ItASSh2U9>A$N~ZYlZ^#P literal 0 HcmV?d00001 diff --git a/investigation/Palette/palette.txt b/investigation/Palette/palette.txt new file mode 100644 index 0000000..175c931 --- /dev/null +++ b/investigation/Palette/palette.txt @@ -0,0 +1,13 @@ +To build this palette, I did the following: + +* Created the .asn + .obj files by hand +* Referenced them in a .set file and asked WH40K_TD.exe to render the sprites +* Screenshotted the output and used GIMP to create a 1x255 image with them all +* Exported that image as an ASCII .ppm file +* Used the `generate_palette` script to output Go code \o/ + +Note that palette index 0 is ignored, and hardcoded to be transparent black. +This is because 0x00 seems to be used as a record separator in .obj files, so +can't be used as a palette index anyway. + + diff --git a/investigation/Palette/palette.xcf b/investigation/Palette/palette.xcf new file mode 100644 index 0000000000000000000000000000000000000000..b686042e93ba8a871693d081c20f59e0ac4cc265 GIT binary patch literal 1428 zcmYe#%q>u;NKR8o%gjk-00KrZ0)+-3rHgeI=53|a&~%AerX;!*qETo z*nl)B$o~T|2b2v8a!x24NHKtd9;5~YxPTbO=0;-kAhCIo*!(~?$Xpm;2Qpy#VbKk9 z3s8#54yq95bY`Gf6axcm4G?#r$}yEOFff|~@h_l}dqHwn{x>jy;DH0n{(rc8diSOk zb0>G!mu4k|`Pdq&NboSU6~=nlo9n5`itsQqGk^I1>DkS*2e+=AJE5f_C(hsT%72E2 z1_p+P19wglrGH{|s=u49sgAOg5GMyaJKMkiz#wO8U_5a019tuYKR&s3V%M5^6PioY zqy1eh&u`^UYV4e*t!oHgShZg?7 zxH6rIg@K*<|Nqn9?rvmYW_aFG7?)jfaO;Y>6YMNa^fdo3X!sAzMnHHs*;8MXiHVtI z(~SDma8GMp24<$mJE!eyV)?(NrAUx%Np(C>{|g~rb`DNEGhG#FBVBbRncoW<82$sZ zA;^IGg2W&%XCuWlZv#;Q9!^%E0o)vH|6g1LI^fyaIi3P6Km*<)3{V03lkdZl24M1I Q2IXWBXaJKC!U0490Ni+Rf&c&j literal 0 HcmV?d00001 diff --git a/scripts/try-uncompress b/scripts/try-uncompress index ddfcaf5..b7a5c4c 100755 --- a/scripts/try-uncompress +++ b/scripts/try-uncompress @@ -18,7 +18,7 @@ module Obj ) def self.parse(data) - hdr = new(*data[0..SIZE - 1].unpack("V*")) + hdr = new(*data[0..SIZE - 1].unpack("VVVVV")) pp hdr hdr.validate!(data.bytes.size) hdr @@ -28,6 +28,16 @@ module Obj @num_sprites, @dir_offset, @dir_size, @data_offset, @data_size = *entries end + def to_data + [ + @num_sprites, + @dir_offset, + @dir_size, + @data_offset, + @data_size, + ].pack("VVVVV") + end + def validate!(overall_size) raise "Directory overlaps EOF" if overall_size < dir_size + dir_offset raise "Data overlaps EOF" if overall_size < data_offset + data_size @@ -62,6 +72,10 @@ module Obj @sprite_size = sprite_size end + def to_data + [rel_offset, sprite_size].pack("VV") + end + def sprite_range rel_offset...(rel_offset+sprite_size) end @@ -91,8 +105,11 @@ module Obj @entries = entries end - # Convert the directory into an Array of bytes. Until we work out how to - # parse sprites, anyway... + def to_data + entries.map(&:to_data).join("") + end + + # Convert the directory into an Array of sprites def realize(rel_data) entries.map { |entry| Sprite.parse(rel_data[entry.sprite_range]) } end @@ -125,6 +142,18 @@ module Obj @unknown20 = *args end + def to_data + [ + @unknown0, + @width, + @height, + @unknown8, + @size, + @unknown16, + @unknown20 + ].pack("VvvVVVV") + end + def pixel_range SIZE...(SIZE+size) end @@ -148,6 +177,15 @@ module Obj @records = records @raw = raw end + + # raw is optional, so don't use it here + def to_data + header.to_data + records.join("") + end + + def total_size + SpriteHeader::SIZE + records.join("").size + end end class Parsed @@ -161,6 +199,19 @@ module Obj @directory = directory @sprites = sprites end + + def to_data + dir_padding = "\x00"*(header.dir_offset - Header::SIZE) + data_padding = "" # for now, assume all is well + + [ + header.to_data, + dir_padding, + directory.to_data, + data_padding, + sprites.map { |sprite| sprite.to_data }, + ].join("") + end end def self.parse(data) @@ -383,6 +434,70 @@ def sprite(filename, i) end end +# Build a test sprite to investigate the color palette +def build(filename) + sprite_records = [ + # 63 in each sprite except the last, which has just 3 + 1.upto(63).map { |b| "\x01#{b.chr}\x00" }, + 64.upto(126).map { |b| "\x01#{b.chr}\x00" }, + 127.upto(189).map { |b| "\x01#{b.chr}\x00" }, + 190.upto(252).map { |b| "\x01#{b.chr}\x00" }, + 253.upto(255).map { |b| "\x01#{b.chr}\x00" }, + ] + + sprites = sprite_records.map do |records| + data = records.join("") + header = Obj::SpriteHeader.new( + 0, # Stolen from blank.obj + 1, # width of 1 + records.size, # height = number of records + 0, # padding? + data.size, + 0, # padding? + 0, # padding? + ) + + Obj::Sprite.new( + header, + records, + header.to_data + data, + ) + end + + dir_block_offset = 32 # hardcoded + dir_block_size = 8 *sprites.size + data_block_offset = dir_block_offset + dir_block_size + data_block_size = sprites.inject(0) { |x, spr| x + spr.total_size } + + header = Obj::Header.new( + sprites.size, + dir_block_offset, + dir_block_size, + data_block_offset, + data_block_size, + ) + + offset = 0 + directory = Obj::SpriteDir.new( + sprites.map { |sprite| + puts "offset #{offset} => #{sprite.total_size}" + entry = Obj::DirEntry.new(offset, sprite.total_size) + offset += sprite.total_size + entry + } + ) + + pp directory + + built = Obj::Parsed.new( + header, + directory, + sprites + ) + + File.open(filename, "w") { |f| f.write(built.to_data) } +end + case command = ARGV.shift when "sprites" then ARGV.each { |filename| sprites(filename) } @@ -396,6 +511,8 @@ when "decompress" then ARGV.each { |filename| decompress(filename) } when "correlate" then correlate(ARGV) +when "build" then + build(ARGV[0]) else puts "Unrecognized command #{command}" exit(1)