From c36572f8dc827f6e828032e4e6ba7818dfd00de6 Mon Sep 17 00:00:00 2001 From: Andy Dixon Date: Mon, 19 Oct 2026 20:43:34 +0000 Subject: [PATCH] Extract the track thumbnails and the ball sprite bank - Track files: decode the selector thumbnails (a 640x64 16-colour picture, four sequential bitplanes, six 123x32 tiles in a 3x2 grid) into tracks//thumb.png, and show them in the menu at double height (they are Amiga hi-res) - gfx/ball.spr: decode the 64-frame attached-sprite bank (spinning ball with the Acid logo) into gfx/ball.png; its colours are not in the file - Command-line output no longer panics when piped into e.g. head --- assets/README.md | 16 +++++- assets/gfx/ball.png | Bin 0 -> 17800 bytes assets/tracks/track1-1/thumb.png | Bin 0 -> 1057 bytes assets/tracks/track1-2/thumb.png | Bin 0 -> 1187 bytes assets/tracks/track1-3/thumb.png | Bin 0 -> 1264 bytes assets/tracks/track1-4/thumb.png | Bin 0 -> 856 bytes assets/tracks/track1-5/thumb.png | Bin 0 -> 1284 bytes assets/tracks/track1-6/thumb.png | Bin 0 -> 1303 bytes assets/tracks/track2-1/thumb.png | Bin 0 -> 1013 bytes assets/tracks/track2-2/thumb.png | Bin 0 -> 1028 bytes assets/tracks/track2-3/thumb.png | Bin 0 -> 1280 bytes assets/tracks/track2-4/thumb.png | Bin 0 -> 1226 bytes assets/tracks/track2-5/thumb.png | Bin 0 -> 941 bytes assets/tracks/track2-6/thumb.png | Bin 0 -> 1258 bytes assets/tracks/track3-1/thumb.png | Bin 0 -> 1478 bytes assets/tracks/track3-2/thumb.png | Bin 0 -> 1459 bytes assets/tracks/track3-3/thumb.png | Bin 0 -> 1656 bytes assets/tracks/track3-4/thumb.png | Bin 0 -> 1574 bytes assets/tracks/track3-5/thumb.png | Bin 0 -> 1537 bytes assets/tracks/track3-6/thumb.png | Bin 0 -> 1402 bytes assets/tracks/track4-1/thumb.png | Bin 0 -> 1416 bytes assets/tracks/track4-2/thumb.png | Bin 0 -> 1070 bytes assets/tracks/track4-3/thumb.png | Bin 0 -> 1333 bytes assets/tracks/track4-4/thumb.png | Bin 0 -> 1725 bytes assets/tracks/track4-5/thumb.png | Bin 0 -> 1424 bytes assets/tracks/track4-6/thumb.png | Bin 0 -> 1481 bytes docs/FORMATS.md | 31 ++++++++--- src/app.rs | 18 ++++++- src/data/mod.rs | 20 +++++++- src/data/track.rs | 66 +++++++++++++++++++++++- src/formats/iff.rs | 85 +++++++++++++++++++++++++++++++ src/main.rs | 62 ++++++++++++++-------- 32 files changed, 262 insertions(+), 36 deletions(-) create mode 100644 assets/gfx/ball.png create mode 100644 assets/tracks/track1-1/thumb.png create mode 100644 assets/tracks/track1-2/thumb.png create mode 100644 assets/tracks/track1-3/thumb.png create mode 100644 assets/tracks/track1-4/thumb.png create mode 100644 assets/tracks/track1-5/thumb.png create mode 100644 assets/tracks/track1-6/thumb.png create mode 100644 assets/tracks/track2-1/thumb.png create mode 100644 assets/tracks/track2-2/thumb.png create mode 100644 assets/tracks/track2-3/thumb.png create mode 100644 assets/tracks/track2-4/thumb.png create mode 100644 assets/tracks/track2-5/thumb.png create mode 100644 assets/tracks/track2-6/thumb.png create mode 100644 assets/tracks/track3-1/thumb.png create mode 100644 assets/tracks/track3-2/thumb.png create mode 100644 assets/tracks/track3-3/thumb.png create mode 100644 assets/tracks/track3-4/thumb.png create mode 100644 assets/tracks/track3-5/thumb.png create mode 100644 assets/tracks/track3-6/thumb.png create mode 100644 assets/tracks/track4-1/thumb.png create mode 100644 assets/tracks/track4-2/thumb.png create mode 100644 assets/tracks/track4-3/thumb.png create mode 100644 assets/tracks/track4-4/thumb.png create mode 100644 assets/tracks/track4-5/thumb.png create mode 100644 assets/tracks/track4-6/thumb.png diff --git a/assets/README.md b/assets/README.md index b6c46ae..c195555 100644 --- a/assets/README.md +++ b/assets/README.md @@ -47,7 +47,14 @@ Plain text; lines starting with `#` are comments. - **`start`, `flags`:** values from the original data that the game doesn't use. -To add a track, make a new folder with both files. Tracks are listed in +### `thumb.png` (optional) + +The track's 123 × 32 thumbnail from the original track-select screen, in +the track's palette. It's drawn at double height in the menu, because the +original's pixels are half-width. Without one, the menu shows a shrunken +`track.png`. + +To add a track, make a new folder with `track.png` and `track.txt`. Tracks are listed in name order. ## Vehicles: `vehicles/.png` and `.txt` @@ -125,7 +132,12 @@ files, the first in name order plays. ## Pictures: `gfx/*.png` Indexed PNGs. `skidtoon.png` is the title picture at the top of the menu, -shown at half size. The others aren't used. +shown at half size. The others aren't used: + +- `ball.png`: 64 frames of a spinning ball with the Acid logo, 16 frames + across, each 32 × 33. Its real colours aren't known, so it has a grey + palette. +- `chat2.png`, `mouse.png`, `sign.png`: small odds and ends. ## Palettes: `palettes/*.pal` (optional) diff --git a/assets/gfx/ball.png b/assets/gfx/ball.png new file mode 100644 index 0000000000000000000000000000000000000000..af73698ed8ec881497b11fcfc21ad6006bd5653e GIT binary patch literal 17800 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 literal 0 HcmV?d00001 diff --git a/assets/tracks/track1-1/thumb.png b/assets/tracks/track1-1/thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..113c77e211eaf692800b674c959d3843f10026c0 GIT binary patch literal 1057 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 literal 0 HcmV?d00001 diff --git a/assets/tracks/track1-6/thumb.png b/assets/tracks/track1-6/thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..58fea65f642369b4ed4ee5bebb67f214a7788def GIT binary patch literal 1303 zcmV+y1?c*TP)OWXQ000D{ zNklhkEO~{WX^C9MVn&UmwKnuflnq7HqUgU z_ah~hyx|F&ri^le<95#rEdJq)Ra5D=#7lKFR`al_%||M;vYuj zl%)w|je80~!=L1~iSLVgEHHd0WlX1TONlM0MBaFo23A=mFm>wT?B-3={(#M0iyafP zN|1uAUhZ@Q2UUg;c*&v^LjC(1`QoW>9HrZg(ST-g8t1h(CQldTh$TNQ^>qW9fKP(m zxMNMt!ww8irJ_OxdLOp-KQ#ssrjkp+@~DlLA~# zOk@+4^FUSlfs*BK(u4*@g!U_R;C?kK2UE$wZ7M8Kk=I68PKDLzdgk#R^gyd_dA+Go zd4yy$RcSY@Z{UH|3bO16e1<)nJF0JKrtZr|-e=c+{TVjO+XZ}G>t-s_NqBfpPwM*` z)q}1QtO}k~RhPd~)x*WCjb{_l#$wdK;@CeM?JA`}N;|Bp*Qr(G^$PtS?Q%q?>Za8$ zR2bN*oW8i{wZ^4R#@;l7F8{V_qV&=&Z}oQT$Un==tSJ0tiM*wRlq`w7B=crbJ2chY zEqvcBZ(Z)t9ml#3Kw~_|!@|$d(r?w``^rA3G`BO~n{;nv8Q3n0x6pQXJIamQ7wDnZ1V!g^ zmXlWeydS3t|C_c3_oq5h+Y94bT+dUpKWGVx+RAylzleBkCV7@EC{Pw!{1tZd(7&Yx zy`UjJ!>>lHnh5>;f%i0Jws2cnr$IOySWQsQye`|Av7MzcE0{u7Pj|W~_ax@o{ZsIi zLpmDnWI-!P)yq)j2Uc9k&oE)9`+Ngh_ZMZVY?9aP*PZp~_qrE-ns+y|JUdtc*l~6A z6E=8Tw#O`^bAnO4M`%Bsr5I=fCAXaq#h{tnn!LhUp#!$Z9u6HV{OX4_5a&am1(
7#&|6&vpBtjlRCcHrM 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 P00000NkvXXu0mjfBFMM6nF9c12=XaE zIoLC~5LnlsJd-cEwFO3Y5hyG6YXT+=0kgbbrB>V0HyMmqNx4o zB!M$83C^x@&>eZ)@DtYH=BMdt1j!BNkQk|;PVY0VG*~bMY~mR%E`szhw~6pnEWZPs zDuofrxuQ-?S9(b>UD~Z02M?0qHrD3nr8JUwis2ha{{3~20wyY(_&>TP5wk@YTOP(juEmaOyx324X-yf$j$M1>attiFxUt(pt3aW@>qrQEWe+?SrG~>WhGp zPcyOB6*wg7=uo`bAph?8@DaNfGe+}FD?;|Z_N}Y|9%&zDA%oUlPWy0=YczwxM(($!g{QF66t^vi1nlpZmFuSqU%SwX^j-E+kyW|vo2M6|XbOs%WLH{6qg z4~SG;{H!}zB2*G2d3+DD4_Pu|F>58PFQuJ3)f4}6pJNhT{JO4k6v_HDoTWdFHVY)a zuD=u4dj9^C7O>j2n`3+%nV;0%U=R;S1;E1}*{*mImVL{k)}iT#j&r+NJc8i%$>zF| zZFm8H?bn2|_)T9Q^~++OY=Duh$v&Kc_A#4*WN3RVr~4(LW56~McRlYGNM1jNK)U%m zEd!~IOg7bMrmEqB!p0&?1stS-I5k@>l4Fcz1hXseY}}ZaL+FL;v*wh*p(vZTNFkaM zW>QC6FIi_M^}H3Lr3wGX2Dwajhkhssn`$f3f!zCFQeOvIEt;D)}PWdS+)06zRR$tajNS=iu0V*W@cN{>cU}{5Yqukm z^6uzi!6c_;Ps9SaKGGeD+ZSPeUT`Nkxo-Oo&Mv`?4T~%CAJV;pdRgSws}4k)Dd)L= z`VBCo>uR@^8{h4feHk1Pw$r2k$k|{$f^CZGAxx#8eZh3^W0@9Q9qSQNTP^34;wDpX z#Kz`o-_L@6@?DR!#jqb9ybf&pDrBw1bE+cO1z|*hv}k9CBqcRR;LdP4D~rkh0m0B& UE`62k>i_@%07*qoM6N<$f-0(2Hvj+t literal 0 HcmV?d00001 diff --git a/assets/tracks/track3-1/thumb.png b/assets/tracks/track3-1/thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..0c1ceec0130dcd382fcc9eb21c580299083a7e08 GIT binary patch literal 1478 zcmV;%1v&bOP)BEf$a zUn&MD)yOr~H!2x#@XwWsr;~8qZ~F^jrmry~?Kk6+=}rpyHof@bB5}fyx2+2v=^`Wu-fsWx%%kzMWY| zcFZ;atA3FWl)xe5@vs8d;-v4x%T`_?kE>I+tnSPfYYh<({duR`bj@7ojV2pUTH(f( z7a9yq%>tc@dWt3K3uL$7Ix3FVSck(HPmFc=VI@^v4x`k>J`|;#=a#N2m7-WeRb!)| zw)=-!ftSDE5hfj16SZ~l)bOyM(RcVbbI|nUyb&MZ?=-k=7|_`=@9Gm@b!Hc(6EfuM z{p9u@dE{m8&rVl*CE(kmwlg5u9{D6~gEia(!x5{mAyM4)giQ2FM`%q225HByaiX&GMcabi`W`8 z0xF}kAAxN-5RNLQ~GQqldoD5SmUg+{<+W_tUcYHA>acPnCg8w=n8?yi4u5 zvjvR>02bK_rTW4imSP!o2ki>|0bMd&@lXObM`Z_Yp^2+*suapRNJ+mQ2$SI&R?h1O zqcS`Y?!b$dk*|_5VUdRWhuWYPbSnB5@z9{%B#re)kh-`xBt2YPM04IgYc6?-ycN1L z^sa^^@(;aO9R6Ml`=xdar@NI{=7h?$k{m`!fFQ-)d@Xjny!zT2yGW)$W&WG6a@ScW<2b2Zv$?6_W+DGi9J< znH`s)*P4FwN2_bgTJyl>JsPNE*RyM>{*LR1UswCpbilVHIIG!IL4Dg=--8V^4rVV(v2XKce=^?4%(CQ0tBTiX#Ozi^!UO-PqKZxZ$P;r1(@#Z{~#H%IYxzC1O z#Gjm%%w$k`f!@KDTKfr??Yp7my;nkIm4Kd4cd9#R_kxoYoF%8!P;h3r3TRfO!F&J0 z5wCmiK=jat5gk67Rq9SqvV`rk>4`LA5z(8Cq-`F{9~-{7O|BOU%gz?c81^1}yC+WK z>3gw~soom?o*)+C_9U72g4BH7>TScLQS_UZc8G@dMfUhzGCnwd7Wv%_Csu%YSVy<` zxOCv(iSaIbi$DAQVS@7C(C6*<2FZ%+cpM$eOY~nZ&hI%-r&wxdZm9G>@sH_89=9_T gp{4ch4E{;^Uo~r@7s~iQ>;M1&07*qoM6N<$f}%v;8~^|S literal 0 HcmV?d00001 diff --git a/assets/tracks/track3-2/thumb.png b/assets/tracks/track3-2/thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..4617418dbdf21674753cf0d437c12acdfc23fecd GIT binary patch literal 1459 zcmV;k1x)&hP)3=I+L*w%gj=e*90w-sTN6F{7H)~{7j$VpCqh~6hhj^vl+JgtP& zBp1CUJngS?mtFMHCt^p*xx_iK43lp&y2<5;i{yoTVw>Xrth?Jqj05yUYb2dg zpv#dskrTA&Rm;f~%b8eT+E7L^gH$eOCxh(VTRo1$1wqV|63ZfIxGT(0Vjg8qH*i5l z2GOFYxoS4y$uV3_6P+$vh{8E@=HIa)IoGj~me>`^Yc=dQY=Ks!#(^GdcU_bCrTDZv zWa#xIlCJcPM>|8)yDb%26CE(+nC$|%r3^V?xK4nb4jzbZv^aU|T>lU;77p2Fe7^OX zclu9e_Pm(g;#}6yp*m#s^=a9$To!*Dmj}AUMP++kL3m&~$5~^&JXMa#tW3uY@Q{8& zuhlGhejW3c%MdowFjQrP`^}~MAVkP7hE>k`rx=4t(ZO>b_l+M&HDyhVVNO}2T$3(_ z6E;R`IS>1NgLyF$NOz&dK+5hCUZ^VSL%1sIV4=(4m_fA}O(0#oDxr0x#o2qy$73jV zQuVir+$qnPS$-{Xn{1CeLCEGRE{@9t$0c8#kW33luRidP=Jg}iQoc89ZHZjI!H$&+ zr7$p_S%+h&2rxe50QF~7Ojw|(I}2P3of4%po*;Cop38;F#_13+-%$4DiJ7) zBfzsW&c&to%D2(&pK7#sB`h+jX03KLcCb6V=<_Ac8K~GUt42D^QiLV@5^GrBE-dd74PP)BN=&muTw*2#f@r!dH+* zwl>zi#33Hbp70V(aQCOLL&MlLfc@_Bt}!XHojCr5`ZVOSEvB~R6bLra9@t|zsml{zwB9=aL0BEgMRYa@*#Z%9p1@p+488HZI&m8O=`D#Ms@rT0?JbTim1oi zM)i<|l2~d-?H$bNX}G=HmrjTDsoNH%OAB?E6mBoZxoyF}O8ByNqShUJ6CLQ$*YA;E zfM@wxL(^>)+B`BUz`qnn16FM=agN{=rxx2e4v@3VZ;;oLU5D}&T=x$C6b^5eo??}RK36XBaf<%ga8KXVe*v3_tqP8u4PyWR N002ovPDHLkV1n*xw*deE literal 0 HcmV?d00001 diff --git a/assets/tracks/track3-3/thumb.png b/assets/tracks/track3-3/thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..748dbaae14d7f0d00bedaae9f9b3d8a4dc407e7a GIT binary patch literal 1656 zcmV-;28a2HP)KY-i5@|L2@mQ-xe0FWWQg+8K?S1lVjIP6&3$h0CQLA?11b zxu1|gPF^nkVEE<0_vJDRKj!1-V)u*e08}I{uraJj-qh!bou07oiF!L^G6vDnEpM74 zeA-nNP}x_N1Htt8el#VqsxQ*(0Cl{=t3q)D_=~3P^7FmD$qO6Aiwo+sRf&QQ@PzX4_U zK)1l`GvD;tYsC#q}`7gwkO8@jSss8hCBA^nRSG+?Vf7d@N5 zx}v%n!qf5nN6;?LnDj-&t$U&R3_}LDi*lqI$bEd`jb2x|oquH|2HZkgyckBwSyT+l zb_iD3R@!WV$)1kBN149DGw!x5T4kkTxA3s&5X{zg%bolNY{@J7FuZbbsiqVx@8>flH?fBD1usn3Yl!z_u*6g8s=UxG-ICs^s`Tg{u#V9DZ z+(L|9ia&$JnB!JYTD|Z`Zk9|0BT=^$CY$nG!8E4$bsM`E^p^xwXLyqXHGlatZY92H zke3VXR&pi4;kFI)3gh&B*fNT%x5ZQH;>GxyQyYEe7nn}Mm2sTeNtVldOn#5AoB35AFQu5sY^y^UPslgPV5j&f z9>i3RgBhn}c3ZCM(KiZ(!n;1zRW8AWkBw3ClN2he7bz08%ICZ&WiifCu2FgL$DV0*}SL2+Trj<1FjPQtu0>e{~$jfC1nPC}L zYo@N4EE@Ew+})2*WQ$?v&FeH4FFMmh9WR?9kSvTO;M47JaUm_thHNz_X8}=X$&HjB z`78Z^2^Gcd(BeHGrS!X^lvMp5bej6N&xY@Ubb!riijtzoH3C}&P#gn3)dy~bnR@T+ z<-ncy4aV+amuR}R_&=RPPtYLO)oNbpS>zF^X_^A1yU|1_fMYfa8jdGTau=7=DSFz( zC=hGeJp}RQfl5|?D)zwb6xU&z5Th};J8(i8Y1nbnr zm-=G0Ou?gCj<15oK@VqD&4(s?o(1T*%w*|JPp2R>33uW|N+8W!8uQ8+$1RLbzSDtN z+g{F8t4dbs)?@2gJ*1(z?)QSOQ+NIuwNM9}Hfx-FjuypNt*9vFcNFL97i^y=<0ET< zWdrVQGhEu`^(VmDM49}rjlS^bTYWA~qz*VK7DyjWv3xDj3azl{Mhj@u4If8_$x}#t z{j8C79u`VHZQH;4+fqd+&0Za`d?2HE+EP)55EZuJ&RBSAJ6ss^WND%4--PG;b)kV~-Lum_Qo>H@EBIY&+c}OYCLaKE!P;Ef zxoUxY$Ley&@%E(RU<3=6AP-%xiv^f9Gw;j_q<{bdwpGu9Qu#oou!0g&jyLR&RM#@a zYJ&yYr2#OcmerBOWmR#9ASSHU*=g{_ghIvp)#p@`ttU<_{k@h_ocU5)Hgk_b0xjpeQ*gzQr%c4 zGS^gy>CFJ^YnL){g_Vz-HhxMp-Y$_D7h1{qq?a?@{BL4b)k?U3+UAC%&u?*?M8m(G za*+4}xDR7jc*s1#hV`;~%RFXvmlLsV?q=(h;1A>qXZsGjZB=6Rcx@D3RP$5jVxaMrT542H)32V~A|WqlDkq47Xr%9?JH* z9k9Ge`k2arAB$e!`L?XJ?q*ayeu8G{$faGLuCk9JY)+Q(YT*1S@*r&rS;Mu~lO8Wa zqh=Nx&bX|>=#0?mNE~oSZsV_$3jGnpuiaC&5D`^B1;Y<+dd#|Ikh6M_I*=M;*feP` z3tTJQT^-yGq#Jc2_00nz=XK30!r`SGN_PzP3>|pn8CmaIs$Whj>VaTJ6~t>wODhYWp{5HBL|Y%z%`l4 z;=RRh%5=WKvON#U-Vs~1jC6DisbXMF^4!@SA|)Dc>?y=RJkbs14{0ZthmD|>M0T2$ z`Jn6uU7V|BwaF(xWjP8K%-qsAIWa${G-=cU`TTo4sbO7@muhcgH^mX_N#o#djFwZ66v?==MwDZt0ZYJO1OTZQKl%`VwtETk^&K1p5V&pk1b=bnb zT3^dMn-~MpyiuL$>PMG4lbw~RN%f{l^2r>=&Gi8{RV!J*s4FR!Dbf(T(mDlNDddyB zZNYXPC{C~-eBh@FZ42~s@N*3(^L$_M_VlPm2zPW@D|!g{%r0K<3B4Gtn?Qg7JsT8U zF{Yv18Fz=Ct%`=n**?t$O+(P+%s+Rb_dAqT5UskcQs94osJgF!h{UgTEy?E1o0oz@EnU6GhqO}?up;I%Di(5m02Alv(Gqe!LSgZs6q zZ%w6@f!-tI`u@n5^oZp%vp@FUkT-aEV2dJhXc*~=7??k3{V&iPviTKezk9@=6d_t7 zWh*PeT&%XND#x@~L+`*hj{HdQO$W2LNxy|UO1!@^wO15G5pox7uHEW7G<5i@q}TYR z!E*a+4XlzqWAn*d%cDGQ!_yWe3S1-frd7KRL~3Y_J$eaF$S<@<^qbK|l;eH;;V=$S zFhq@%_U+evaWx)&!HRh1GOfeBeS@`qxH#oMp?kmdgZ=YzBN9zTxgZGatSQhohF4(g z2OVlCs2j2tT6^*x^zSWgXg5%_6T){D(ngo5Pg0aKCZn6SX2cn4?_ee3(@Jd|V%=H; z@QRHmz7h+1NHa8@^{R$>T1)%h{Tc9z+CPumLO-^irv3jg?Pp2rVA&jh?|e;ENe$uTOkKeM9$sEBS(tmT+60=hEZio9LoM%{N`Oqh=y<0lRvfNd;F8O%#X=w?5$;@f2g9xn6Ghj3Rm?6#+NzJ9ZO!(x%t zp>Ara`^1qQmDgZ@8{-+dZ2HEfK)olgp`-s;KGR4HJWn4^`W5%dExYq4H1_9zS7EtV zztfpWFAuDDo5>}0hcLfzk)LnK@5gy?-W3OW2YGUuiQa~db0;tMai%Uik|`*D2!mH0 zWzewlR;Hq|Lt{->66YzUbEenF!oTdcXzn4Ion}HVLh19IeZQPlzyF8AivvhZjZkX1vd?dW?E_{*3tx7xRdpK1nI&QN;{5c zV1!NuWgdtve%)>X{N~NP`t|Y8>14F?#pmf0)jpt=pw&>Ap|53jtU(&`v4W8Z_r+UL zuQW*IHiwOx(M*(A0ohlEp~YJT^253#pal-x2r0eSR^`#9d^<{6F;JdCgKOpJcJ8L@ z&Ei*5;1bWYI^}|&@^V_7Wpci2Rqm8ZJ2?XRZsA}ra5VUi573BJv7|L&irw0GS>8gw zsRpT`WLA!iv5qY)Cuq5ly4K%!Y(xJ+W2eH$l+P6LZXYd+9_g(s%Jfk|?=)Z?$MLqk%0@VDcW&PR+n`t-UZXY=^` zlD1)zsTbuxP>ZBgp_}#doojVhrCMq#C%2;OFu-qX|NfL%y1!v9q}cxR^=n^7 zi*re=*Ol?WwaTARte_w5?fAyj7R!b84DwEHZ*XM|ut8>~s|1ZU+!ZNC1-gM0vxrtiNO5k0L*tE3P1a)EJ;TaqJp=tMJ(O_$Rnch;X=u{TE`9_|~%*5Jb9^8=9RYmiT zZ4jW|_*)1&o7MhP`f`Bg-RR2=%A+wAO%6KH_2iSp^M8o5v^>r$vN@hcq@wD%XY6+Q z%hhrj&Lu#NhZ#~V^W4uqOx`ZpfmCZRkyXdhqTx1dDFK!{==t(e{kyA{er{V)nrI@I zmk&~3WQIK+SnJ1W?bGyn!OGEcAU5hDA>NE0^z|&L3=<3keSyo+i6|OZ`d0l&yTQ9p zSj^-M(P`d@Lal-EWu&*u#l;;l5wE6eCi{PBuf)N<`KOe{h9sh2RU(YF n4_ub|fejj<(bV&wf3*G&{uk6+NN`=400000NkvXXu0mjfeLDsJ literal 0 HcmV?d00001 diff --git a/assets/tracks/track3-6/thumb.png b/assets/tracks/track3-6/thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..7caaab1ac863c81d04524c14fbc60f3d2b4ddffd GIT binary patch literal 1402 zcmV-=1%>*FP)6@ow0Up3|7A|wOhPPR^Z^??NyjJ0u|eqSFBxHfA<1pO7V(xL zrkDU|rIYmp0dilV9SeO3IUuhy3E@C6^4Ps8Q-EX(fzJho4v<1R9^!=@!gjieP1}1w zys;=GOgcr(ogj?JK7y|hYR)-SUGmGcIPW1jazLK2VBuBf;=?lWZeB1~pZFG*D3rWg zjN62spXYm;Fvpy#9QXchF0N6y2kqHVAZB9ap{N@DmI9Un0uI6k3fZCx3B1g|OU%s? zks!q!byY0Sx3Wx^h-@z!u)D2L?I6GljF)ih7Mky?wCYOUXsmT<=4a8%-x1-yg#(z4 z(O*mVirf2;w-eI;-Q8-09CmVy*txUZq%r5Qwv+)53UsKuxf~C>8P1-PMv&S**qH}*FHVIEi=5BiMkZybopolLz$^0A6q zB|t1do@2?Uct_wggBIhOH~aGO!chjuW_M`$V_|mYFn}Fy!3+Bcq5a_F6W%YpV1ccx zQGn?LcYqgIdU6lVXRo11UY4RR&917Gx_9SqSQhb7(cjzMIZlA7k|-*tI47~DdaBuF z4TJIseLK~3D^ctsK>&iKGOT9tvgt&o8anii)SCTrmh1XlJ7QvRF3%Hn}agtVdE@Oxz zml=*F?VbQVC`n=uV%f`L(8Qh-;xPMB^|{bNbpKh=t%aV2WdXSk!(PX7Ika<%xCEpF zwEz4i(j+VX5Ec87G=reu0k-^xknpYe^5;M0I^-r-oyqitGBZa!P64Wig;^`&cK8|c zYH{Y~EVpyuIAg=)pNjXh*<fP>T(-`{Ha;+wjCcD1sPfCIeXer8zb{4rsyqvke{5h?b(^R;fh|r2y)7ynJ zZO~F|wHqeyZz_WKvoL0x%>2lGAIODt;>N%RSUk_F)t|VlBrnBFhKS-aCu7B_4Xm7h zqRGOg46G-#IhDnpc1|%t<{O|@D6VP|cxoh`_3J2G z!`+k^i?SO5BoV1ihOiBY@**MGE#6^tWaM1#dmGME<303(ztp#VcsV6$A^-pY07*qo IM6N<$g1xt@2LJ#7 literal 0 HcmV?d00001 diff --git a/assets/tracks/track4-1/thumb.png b/assets/tracks/track4-1/thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..7cc3f7029b3fd607e186a298bf68ce49b3263d22 GIT binary patch literal 1416 zcmV;31$X+1P)0AFIGODKf9K{UvBAjO;xahdsy9rH4-9Ilb&)}9zS5J3?yoB^ z7m^V?Z9mK9jYmxr-pOeq#)FxIAN}tt zAt0&`wMS^NPFQmxkEt6b@9cTr_yMf~dd7b%LxZV%~^e z*D!JbHM6V@o4|73r1OR4VT@EbSh6Iw=t8TYyp_5$C;=*{1m_EQFbygfQRO}w%JAVm zjQ&C)81hX0Qp)<)_YqRxz}L}T0Q@tM8pd@`uin6zVl8vGHA>Ri!6bxX!{g48-Ku0< z!*x?ypI%vf5ca`U{5va6s=Ovqz6k1#C^Y8B2#*cLC&Xu)KXymGE(k;xKz_3_61~<0 zVHzfE9TJ`1_z;8$dm>WWxqk5{cp31wv zvu?Q2#^I`FpxOhtLN^-lNtx5<&9> zs$I|JY0#nah=wyKO=t_Ofx|)U zp3=wUA!RewW--MzG}w6layEnGut-vI`UW6n+@!{g)Op-wi~oup!#Cce-2n%#kVz{R zQ*H#Y6bh-RHA0Pbvz(YN&c-cmiH#Ln6m@D81@f6D%}H1oBj-wraw@jTd|MRmA#rll z&(w9tP=aD-`oWpQj$ZPHyN-vQIyn-xVfLe0s2*x+hypujco+(s4AnSkSn59fm>ESB zv5k!p7u0!cQe?EyF>`J7o$NNN_gzKI&UCf98r2or8JnJxN})L&>BcZ|hLkMxytQj$ zAWLwEL!13nYPF)%sGP?Ne&dkx6%NI*x&}-M7k}2L+FbfmS0r&J(jKJc?e4Jtg#Bg6 z5i$i!3;cDY5_T$)@?$0ba!9pR^>shxcZ9d@6FVtuV>$TZg$6c4zv)*)eYN^47YH^S zLORsi8BAY({%VB8c{nlu|9-HkJ>e%-%-bNb{|nSmXqNxZ-{sN%+J@gi%T<1g*#7|i WWs%ZL)k$jr0000guY;*;>NI({bm^z+s4d6oy z#BYs@1#ZO7to(+)YN%E9BWMr)a78vj4yIw~IE6)#sDI3!3K)~L(8X)46 zyd?yN9}`k#VmbGlSo4;W3=J^wC3uKr@CRW}O`!}JrO>fA2?ZMcW9Tku1>C{`+E%FW zA7si7t63`4?@F6LMOcQy0JK(qD$B#Rer<)e7y2b)31~V|DSqY7wM{}h_4O2)Oj;^R zYKwK79pOsRYt%-0xeQca?YYhpV#gK_FKi1pd`-}}mi~zcNFf_#sH(?}#hu0vb449~ z7`K3nA-DPy6vKHKS*(}ge3zEw-nFY>Uo3BFAzRuV`w>j?5?TIA?5CxJzYDnGL}?D? zFSTC_@fuLq0C~w|z zX%Pcl9&_N8PSqf>OFA8od37w(K3z)7&U{D6YcL%Hmj$Af^^jh3La@f{$`w~7dC`(( z0>rh}G2aC(Hp|`Nds!VQ3xH0R)-v-g5BN2gXtVD>wLXK>OylpPE`qitO@BF!btf6& z3s+5C7+~d^7qeS-*()wcT$Knn!{dTRGgEO69%9H8t)Sz}z*b0skB#;(p5z%3Khc9A zF4zu}<3n7bn-&m!m;Dzq$@XHw#^&ZT+pgg$ma!`QGg;myBYEK1VTucOvN!wf_%vd= z#)TlQAK{_l%ckpv=|h$<9@H}FW-8H^Nl!#yaVz4DgjH)zS6!axW<1?b_7g=RvhHCCoUQw^qI7$~xy5Ur*N)MJT&7 zx(S}~$v~@L+ao!aNY{CO8VZQh0rv%dY7g0SeOU@Eodhr!FL8^#i#$U91em^d1$E_p zI$9}`EIw&q4TQQ or#q87g58R>hCXwLZL3+o0Y(Gn5B1+EFaQ7m07*qoM6N<$f-j!z{Qv*} literal 0 HcmV?d00001 diff --git a/assets/tracks/track4-3/thumb.png b/assets/tracks/track4-3/thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..16e2dd4410fc253f21f23aa502ea49ded633acc8 GIT binary patch literal 1333 zcmV-516^bR5H2$LDQ2T8yQ9mU>qlZs_AM6v*<}r3m9(z;Q1exNq=&> zJnk>pAG0eNYD*J8o^YqI%vqM}jCUK0kcR{5@u;s;JgL9o@sBM9a6o}4w*c{$%);|3Je_zY?-mM3^CPA=3fM4n5LHb`i{M332{1#Xoc!kY;Z&CBFaxMemgU$G6+*WCS2KX&9YG-QZCE?IR zn4EK^yguxCC&I^KvNre~G}O?{TS)ok0tC_CI}PRZ27?maFNxAaTa5KDzDBwN5pUE7 z@3w_l|D~mAg6=tv@Y{*-sM)~nIUiuKalO&IW%&Yy>w}r#tssQQOgpS8e@)Zy%GmzT z0h!u#!gaK&`}^o{5iU8Pz+R1mro#%yHXZp55!N!iZx{0o_%)Ayg+Q#+kIfXCek_Mu2L~6u#<$g)I*_jN>O8wQwOX}Z~1RV z3r+4eGFU6XDdYS_IS|1rnQLR`6QKoFd`+E;BQnJ>7GUCz%o~##YqVOnphg%sdh$uZOQ?Y1- zZc`lAfs^{#H4|_?tS6?a`6iSBpW5*x>BJL5j!CRYeutIzfsO?GU8d>JEk5SKB3Y7Q zESu8I{9UH7qqXm?wrGLyi*F!=?=`s^(0gNfXMgJ^!Mt?)|h1O!~ z_8!5{{C9#d&r*E(`o%Bz#~t>ntYmiQk*2(ZYIu{_IZN+(N;q0RHp9j!=a>JfgZINR z|C}ux+HP?Ghw{pqC%5{Hu2Zz;++ohc&vhuQvgDx5^ zqKje6iEQ2;<+lu?UaZ@L26M;B#wpg98bqyEnW8Hb%11*bq)ahlh0;aDjeBha?Z(QV z;fdRpMvN(ndSC2C0kOh$Nj&0bIoxNxuSwg2TqKI#(5Q#d?kMGy4pwSS*Ut+1C>wpz zoI!X@Owi9eIWboKAanH|qC)Jmh2tZ4QF!3JisN{KdAwG6i^2i2$BL1@Zy9c?E`ClY rKZdx)`SIKKbK4h6R?NEEf7|gNg*t1tMLYjh00000NkvXXu0mjf6atNu literal 0 HcmV?d00001 diff --git a/assets/tracks/track4-4/thumb.png b/assets/tracks/track4-4/thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..f04711b724756fbaa92cf846a31a555749aed548 GIT binary patch literal 1725 zcmV;u215CXP)9W%K zgLDv+(XcyN@->sIKqecx5t+Nl#LASbm^VBpv(!jUj3+2r8!-v7#!C*}Lw2*!U=Wg| z-5`YH$#%`jYPEd!%9?6gPS;e=a-$+_a44?3jpW?$#4<=(h5g4BPY!uBrE4Ko*afZJ z?ZgLyMRL+=jC*fBhrJ|B@plSqx)BC9M~1z0z8Z<!&9fw;--pwarjd@^K^f2IEx@l2mIKQ4`rG9s{>oUZn+ z%-Jh)A8sO9d7^sD(|S}*^tDL6ZCTS?nZCQaZdzOaHbt_#ryRId_}#iSO%isNW|vm| zFRJ@$>G)%%sr^MY_Is-E5=c-{LzuV$8pts;afH=TNyHbgPZa?hI7Tk0pR}9Qu}|$( z7*I@fF8-}bSPSll&HY5~IVJFt%xY2~4J3#BLhnAS?e9q(OW%VkBm%joT> zT2Wb9n}$QUmwp53LzRNU%p)B?}9#Ngb1=QuRL<0N!k9f19hz#+bYi2%VQT;sycCzq%zy#6- z>uU-KuNrEV`d3@V&_s9jhgxhpE}#e${YjUnYNffv9g0)PpB7PGf^2D^%7@}Z)g_w1 znq%eE(nE9Gi)*V@dc`kvoB?j=mK?mZhfC>wkAnGd*Ac?HE%9}7w_XvlC1bGlJVrMO-;DCc zV$bs1p!w$@|Wyut(;wHmue*8^Fnl%D0+=U0O8T^`Sc6FR5H` zx2HQI-aZr$IhwQ5#sY^@=B-dA`kH}eD33HIUCHbl#lT1N+5vIX-5X?BKmlksiB9wW zW-xFDn)8C_z0F^8E0CmK;Bgu#dt2#SkHMtWY7O45sdk2autn?f2x{03l7NY|5r#?Q zTlz|11}Clz>H?3kM6dofy$b;NQEfV1FLSbJ=69+OXb0P_>OYGIxdMRTk41u?u#&u< zBGrQ;I?RHg6owb*p3z4=((*@GO#&GHoaPdP3OIp1-yswQCGV)Q?Wt2ca(i9@3VTbK zHgqu%|6(mdD+NRj@&ys*+?@V?p=MZSpn_2%h2Ba#KWqhJe1vcGqvMyiSgcX2LwfhO z6m6HI9D~N&Wyt9U3x~0O5$RT1beE`lxy=FsXs4@t;y&+=y$SPr2ASwWkHTtDK7o{l5!(Dc$0PVmNVN0RaNN{aBfPlQq4} z8r?XJ!-~a7vubj?=SlxsKm%sarHu=i2;48>X6~anmR*P6`1N@hqOF)JUBM87nG?sJ zR@2z-bo(c@in{b=4NJSgP0zH{cEPy7`w;5isBsv4^zetW>DuSO7g$AcrTMr`+gLyeI6Zf@Qum+Y1O5CzQ5S!E TnDX`h00000NkvXXu0mjf2E$Fv literal 0 HcmV?d00001 diff --git a/assets/tracks/track4-5/thumb.png b/assets/tracks/track4-5/thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..9cbb4258aeeab8d3ac87044d15263b6d00391f79 GIT binary patch literal 1424 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 literal 0 HcmV?d00001 diff --git a/docs/FORMATS.md b/docs/FORMATS.md index b7f2bf4..5b91597 100644 --- a/docs/FORMATS.md +++ b/docs/FORMATS.md @@ -58,7 +58,7 @@ consumed to its first byte. ``` 0x00 7 × u32 end offset of sections 0..6 -0x1C … section 0: header for the whole file (20622 bytes, raw) +0x1C … section 0: thumbnails and palette for the whole file (20622 bytes, raw) … sections 1..6: one track each, CrunchMania-packed ``` @@ -73,8 +73,16 @@ The last 142 bytes of section 0 hold: | 98 | 12 | Unknown | | 110 | … | The name of the set of tracks, NUL-terminated | -The rest of section 0 is the track-selector thumbnails, which aren't -decoded. +The first 20480 bytes of section 0 are the track-selector thumbnails: +- **Picture:** one 640 × 64, 16-colour picture in the file's palette, + stored as four bitplanes one after another (80 bytes per row, 5120 bytes + per plane). +- **Layout:** the six tracks' thumbnails, 123 × 32 each, sit in a 3 × 2 + grid at its top left, in track order: top row tracks 1–3, bottom row + 4–6. +- **Display:** the picture is Amiga hi-res, whose pixels are half as wide + as low-res ones. So a thumbnail shows at 123 × 64 next to the low-res + track pictures, the same shape as the track. Each track section unpacks to 281240 (`0x44A98`) bytes: @@ -166,7 +174,18 @@ extra and alternative sounds (`crash1`, `crash2`, `engine`, `motor`, IFF ILBM with ByteRun1 compression. `skidtoon.iff` is the 608 × 180, 16-colour title picture; `chat2`, `mouse` and `sign` are small odds and -ends. `ball.spr` (34816 bytes) is in another format and isn't decoded. +ends. + +`ball.spr` is a bank of 64 saved hardware sprites: a spinning ball with +the Acid logo. Each 544-byte frame is: + +- **Header** (16 bytes): `u32` saved memory pointer, `u16` lines (33), + `u16` strips (4), `u16` flags (`0x8020`), `u16` bytes per strip (132), + 4 unused bytes. +- **Strips:** four 16-pixel sprites, each line two bitplane words. + +Flag bit 15 marks attached sprites. The strips pair up into two 16-colour +columns, so a frame is 32 × 33 pixels. The bank carries no colours. ## Music (`sfx/tune.crn`) @@ -202,8 +221,8 @@ first minute), and it loops at the same point. decoded; this port's versions are reconstructions. Decoding the `.set` values would be the first step towards the original handling. - **Unknown data:** the 12 bytes after each track file's palette, the bit - pattern after the gates in each track section, the thumbnails, and - `ball.spr`. + pattern after the gates in each track section, and the colours of + `ball.spr` (probably set by the executable). The engine's own editable formats, which `skids extract` converts all of the above into, are described in [assets/README.md](../assets/README.md). diff --git a/src/app.rs b/src/app.rs index bd00eb2..32b8bf4 100644 --- a/src/app.rs +++ b/src/app.rs @@ -233,8 +233,22 @@ impl App { fn thumb(&mut self, i: usize) -> Option<&Vec> { if !self.thumbs.contains_key(&i) { let t = self.data.load_track(i).ok()?; - self.thumbs - .insert(i, render::downscale(&t.rgb(), THUMB_W, THUMB_H)); + let img = match &t.thumbnail { + // The original thumbnail: Amiga hi-res pixels, so each row is + // shown twice to keep its shape. + Some(px) => { + let pal: Vec = t.palette.iter().map(|c| c.to_u32()).collect(); + let mut out = vec![0x101010u32; THUMB_W * THUMB_H]; + for y in 0..THUMB_H { + for x in 0..track::THUMB_W.min(THUMB_W) { + out[y * THUMB_W + x] = pal[px[(y / 2) * track::THUMB_W + x] as usize]; + } + } + out + } + None => render::downscale(&t.rgb(), THUMB_W, THUMB_H), + }; + self.thumbs.insert(i, img); } self.thumbs.get(&i) } diff --git a/src/data/mod.rs b/src/data/mod.rs index 5eb1be0..70a12b1 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -299,8 +299,14 @@ impl GameData { let mut t = match &entry.source { TrackSource::Asset { png, txt } => { let txt = String::from_utf8_lossy(self.files.get(txt).unwrap_or_default()); - Track::from_assets(&entry.name, self.files.get(png).unwrap_or_default(), &txt) - .map_err(|e| format!("{}: {e}", entry.name))? + let mut t = + Track::from_assets(&entry.name, self.files.get(png).unwrap_or_default(), &txt) + .map_err(|e| format!("{}: {e}", entry.name))?; + let thumb = png.replace("/track.png", "/thumb.png"); + if let Some(d) = self.files.get(&thumb) { + t.set_thumb_png(d).map_err(|e| format!("{thumb}: {e}"))?; + } + t } TrackSource::Packed { file, section } => self.track_files[*file].decode(*section)?, }; @@ -383,6 +389,16 @@ fn load_media(files: &FileSet, warnings: &mut Vec) -> Media { } } } + for (key, data) in &files.files { + if let Some(name) = stem(key, "gfx", ".spr") { + match iff::read_sprite_bank(data) { + Ok(p) => { + pictures.insert(name, p); + } + Err(e) => warnings.push(format!("{key}: {e}")), + } + } + } for (key, data) in &files.files { if let Some(name) = stem(key, "gfx", ".png") { match image::read_indexed(data, &palette::TRACK_DEFAULT) { diff --git a/src/data/track.rs b/src/data/track.rs index daf1a4e..f6733d9 100644 --- a/src/data/track.rs +++ b/src/data/track.rs @@ -3,6 +3,10 @@ //! File layout: //! 0x00 7 x BE32 end offsets. Section 0 runs from 0x1c to the first offset //! and holds the selector thumbnails; sections 1..6 are tracks. +//! Section 0 starts with the selector thumbnails: a 640x64 16-colour +//! picture, four bitplanes one after another (80 bytes per row), with +//! the six tracks' 123x32 thumbnails in a 3x2 grid at its top left. +//! Its pixels are Amiga hi-res, so each shows at 123x64 on screen. //! The last 142 bytes of section 0 are: BE16 file number, the 16 //! colour palette shared by all six tracks as 48 BE16 values (R, G, B //! per colour, 0-15 each), 12 bytes of unknown settings, then the @@ -50,11 +54,16 @@ pub fn on_screen(sx: f32, sy: f32, margin: f32) -> bool { sx >= margin && sy >= margin && sx < W as f32 - margin && sy < H as f32 - margin } +pub const THUMB_W: usize = 123; +pub const THUMB_H: usize = 32; + pub struct TrackFile { pub name: String, pub number: u32, pub palette: [Rgb; 16], pub set_name: String, + /// The six selector thumbnails, THUMB_W x THUMB_H palette indices each. + pub thumbnails: Vec>, /// Packed track sections (index 0 = first playable track). pub sections: Vec>, } @@ -75,6 +84,8 @@ pub struct Track { pub name: String, /// The set of six tracks this one belongs to. pub set_name: String, + /// The original selector thumbnail, THUMB_W x THUMB_H palette indices. + pub thumbnail: Option>, /// Palette indices, W x H. pub pixels: Vec, pub palette: [Rgb; 16], @@ -112,13 +123,16 @@ impl TrackFile { .trim_start_matches(|c: char| !c.is_ascii_digit()) .parse() .unwrap_or(u32::MAX); - let (palette, set_name) = file_header(&d[SECTIONS * 4..ends[0]]) - .unwrap_or((palette::TRACK_DEFAULT, String::new())); + let header = &d[SECTIONS * 4..ends[0]]; + let thumbnails = thumbnails(header); + let (palette, set_name) = + file_header(header).unwrap_or((palette::TRACK_DEFAULT, String::new())); Ok(TrackFile { name: name.to_string(), number, palette, set_name, + thumbnails, sections, }) } @@ -132,10 +146,38 @@ impl TrackFile { let mut t = Track::from_section(self.track_name(section), &raw)?; t.palette = self.palette; t.set_name = self.set_name.clone(); + t.thumbnail = self.thumbnails.get(section).cloned(); Ok(t) } } +/// The six thumbnails from the start of section 0. +fn thumbnails(s: &[u8]) -> Vec> { + const ROW: usize = 80; + const ROWS: usize = 64; + let plane = ROW * ROWS; + if s.len() < plane * 4 { + return Vec::new(); + } + let px = |x: usize, y: usize| -> u8 { + (0..4) + .map(|p| ((s[p * plane + y * ROW + x / 8] >> (7 - x % 8)) & 1) << p) + .sum() + }; + (0..6) + .map(|i| { + let (ox, oy) = ((i % 3) * THUMB_W, (i / 3) * THUMB_H); + let mut t = Vec::with_capacity(THUMB_W * THUMB_H); + for y in 0..THUMB_H { + for x in 0..THUMB_W { + t.push(px(ox + x, oy + y)); + } + } + t + }) + .collect() +} + /// Palette and set name from the tail of section 0. fn file_header(s: &[u8]) -> Option<([Rgb; 16], String)> { let tail = s.get(s.len().checked_sub(142)?..)?; @@ -197,6 +239,7 @@ impl Track { pixels, palette: palette::TRACK_DEFAULT, set_name: String::new(), + thumbnail: None, height, gates, start, @@ -336,6 +379,7 @@ impl Track { pixels: img.pixels, palette: pal, set_name, + thumbnail: None, height, gates, start, @@ -343,6 +387,24 @@ impl Track { }) } + /// The thumbnail as an indexed PNG in the track's palette. + pub fn thumb_png(&self) -> Option, String>> { + self.thumbnail + .as_ref() + .map(|t| image::write_indexed(THUMB_W, THUMB_H, &self.palette, false, t)) + } + + pub fn set_thumb_png(&mut self, png: &[u8]) -> Result<(), String> { + let img = image::read_indexed(png, &self.palette)?; + if (img.w, img.h) != (THUMB_W, THUMB_H) || img.pixels.iter().any(|&p| p > 15) { + return Err(format!( + "thumb.png must be {THUMB_W}x{THUMB_H} using colours 0-15" + )); + } + self.thumbnail = Some(img.pixels); + Ok(()) + } + pub fn to_png(&self) -> Result, String> { image::write_indexed(W, H, &self.palette, false, &self.pixels) } diff --git a/src/formats/iff.rs b/src/formats/iff.rs index 1c5cbe3..46e93c9 100644 --- a/src/formats/iff.rs +++ b/src/formats/iff.rs @@ -170,6 +170,74 @@ pub fn read_ilbm(d: &[u8]) -> Result { }) } +/// Reads a bank of hardware sprites saved by the game (`gfx/ball.spr`) as +/// one picture sheet, 16 frames across. +/// +/// Each frame is a 16-byte header (BE32 saved pointer, BE16 lines, BE16 +/// strip count, BE16 flags, BE16 bytes per strip, 4 unused) followed by the +/// strips: 16-pixel sprites with two bitplane words per line. With flag bit +/// 15 set, strips pair up as attached sprites (16 colours, 16 pixels per +/// pair); otherwise each strip is a 4-colour, 16-pixel column. The bank +/// carries no colours, so the sheet gets a grey ramp. +pub fn read_sprite_bank(d: &[u8]) -> Result { + if d.len() < 16 { + return Err("too short for a sprite bank".into()); + } + let lines = be16(d, 4) as usize; + let strips = be16(d, 6) as usize; + let attached = be16(d, 8) & 0x8000 != 0 && strips.is_multiple_of(2); + let per_strip = be16(d, 10) as usize; + if lines == 0 || strips == 0 || per_strip != lines * 4 { + return Err("not a sprite bank".into()); + } + let record = 16 + strips * per_strip; + let frames = d.len() / record; + let fw = if attached { + strips / 2 * 16 + } else { + strips * 16 + }; + let (cols, rows) = (frames.min(16), frames.div_ceil(16)); + let (w, h) = (cols * fw, rows * lines); + let mut pixels = vec![0u8; w * h]; + for f in 0..frames { + let r = &d[f * record..(f + 1) * record]; + if be16(r, 4) as usize != lines || be16(r, 6) as usize != strips { + return Err(format!("frame {f} has a different size")); + } + let strip_px = |s: usize, x: usize, y: usize| -> u8 { + let o = 16 + s * per_strip + y * 4; + let bit = 0x8000 >> x; + ((be16(r, o) & bit != 0) as u8) | (((be16(r, o + 2) & bit != 0) as u8) << 1) + }; + let (ox, oy) = ((f % 16) * fw, (f / 16) * lines); + for y in 0..lines { + for x in 0..fw { + let v = if attached { + let pair = x / 16; + strip_px(pair * 2, x % 16, y) | (strip_px(pair * 2 + 1, x % 16, y) << 2) + } else { + strip_px(x / 16, x % 16, y) + }; + pixels[(oy + y) * w + ox + x] = v; + } + } + } + let n = if attached { 16 } else { 4 }; + let palette = (0..n) + .map(|i| { + let v = (i * 255 / (n - 1)) as u8; + (v, v, v) + }) + .collect(); + Ok(Picture { + w, + h, + pixels, + palette, + }) +} + /// Writes 8-bit unsigned mono WAV, with the loop start in a `smpl` chunk so /// sample editors keep it. pub fn sound_to_wav(s: &Sound) -> Vec { @@ -277,6 +345,23 @@ mod tests { assert_eq!(back, s); } + #[test] + fn attached_sprite_bank() { + // One frame: 1 line, 2 attached strips; pixel 0 uses all four planes. + let mut d = vec![0u8; 16 + 2 * 4]; + d[5] = 1; + d[7] = 2; + d[8] = 0x80; + d[11] = 4; + for o in [16, 18, 20, 22] { + d[o] = 0x80; + } + let p = read_sprite_bank(&d).unwrap(); + assert_eq!((p.w, p.h), (16, 1)); + assert_eq!(p.pixels[0], 15); + assert_eq!(p.pixels[1], 0); + } + #[test] fn byterun1_picture() { // 16x1, 1 plane: run of 2 x 0xff. diff --git a/src/main.rs b/src/main.rs index a8f1b61..60e234b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,6 +3,17 @@ //! The engine reads the original Amiga data (ADF images, the LHA archives on //! them, or extracted files) at run time; no game assets are bundled. +/// Like `println!`, but a closed pipe (e.g. `skids info | head`) ends the +/// program quietly instead of panicking. +macro_rules! out { + ($($arg:tt)*) => {{ + use std::io::Write; + if writeln!(std::io::stdout(), $($arg)*).is_err() { + std::process::exit(0); + } + }}; +} + mod app; mod audio; mod data; @@ -202,24 +213,24 @@ fn main() -> ExitCode { fn info(data: &GameData, dir: Option<&Path>) { let built_in = data::embedded::files().len(); match (built_in, dir) { - (0, Some(d)) => println!("Data: {}", d.display()), - (n, Some(d)) => println!("Data: built in ({n} files), plus {}", d.display()), - (n, None) => println!("Data: built in ({n} files)"), + (0, Some(d)) => out!("Data: {}", d.display()), + (n, Some(d)) => out!("Data: built in ({n} files), plus {}", d.display()), + (n, None) => out!("Data: built in ({n} files)"), } - println!("Tracks:"); + out!("Tracks:"); for (i, entry) in data.tracks.iter().enumerate() { let from = match entry.source { data::TrackSource::Asset { .. } => "asset", data::TrackSource::Packed { .. } => "original data", }; match data.load_track(i) { - Ok(t) => println!(" {:<10} {:>2} gates ({from})", t.name, t.gates.len()), - Err(e) => println!(" {:<10} ERROR {e}", entry.name), + Ok(t) => out!(" {:<10} {:>2} gates ({from})", t.name, t.gates.len()), + Err(e) => out!(" {:<10} ERROR {e}", entry.name), } } - println!("Vehicles:"); + out!("Vehicles:"); for v in &data.vehicles { - println!(" {:<10} ({})", v.name, v.id); + out!(" {:<10} ({})", v.name, v.id); } } @@ -263,7 +274,7 @@ fn unpack(input: &Path, out: Option<&Path>) -> Result<(), String> { let dest = out.join(safe); std::fs::create_dir_all(dest.parent().unwrap()).map_err(|e| e.to_string())?; std::fs::write(&dest, &data).map_err(|e| format!("{}: {e}", dest.display()))?; - println!("{:>8} {}", data.len(), path); + out!("{:>8} {}", data.len(), path); } if failed > 0 { return Err(format!("{failed} files could not be extracted")); @@ -299,7 +310,10 @@ fn extract(data: &GameData, out: Option<&Path>) -> Result<(), String> { } write(dir.join("track.png"), &png)?; write(dir.join("track.txt"), txt.as_bytes())?; - println!("wrote {}", dir.display()); + if let Some(thumb) = t.thumb_png() { + write(dir.join("thumb.png"), &thumb?)?; + } + out!("wrote {}", dir.display()); } for v in &data.vehicles { let (png, txt) = (v.sheet_png()?, v.settings_text()); @@ -313,7 +327,7 @@ fn extract(data: &GameData, out: Option<&Path>) -> Result<(), String> { if let Some(menu) = v.menu_png() { write(dir.join(format!("{}-menu.png", v.id)), &menu?)?; } - println!("wrote {}", dir.join(&v.id).display()); + out!("wrote {}", dir.join(&v.id).display()); } for (name, s) in &data.sounds { let wav = formats::iff::sound_to_wav(s); @@ -323,13 +337,13 @@ fn extract(data: &GameData, out: Option<&Path>) -> Result<(), String> { } let path = out.join("sounds").join(format!("{name}.wav")); write(path.clone(), &wav)?; - println!("wrote {}", path.display()); + out!("wrote {}", path.display()); } if let Some((name, med)) = &data.music { formats::med::parse(med).map_err(|e| format!("music {name}: {e}"))?; let path = out.join("music").join(format!("{name}.med")); write(path.clone(), med)?; - println!("wrote {}", path.display()); + out!("wrote {}", path.display()); } for (name, p) in &data.pictures { let pal: Vec = p @@ -343,7 +357,7 @@ fn extract(data: &GameData, out: Option<&Path>) -> Result<(), String> { } let path = out.join("gfx").join(format!("{name}.png")); write(path.clone(), &png)?; - println!("wrote {}", path.display()); + out!("wrote {}", path.display()); } Ok(()) } @@ -394,7 +408,7 @@ fn render_shot(data: GameData, args: &Args) -> Result<(), String> { let n = race.track.gates.len(); for &i in &race.standings() { let e = &race.entrants[i]; - println!( + out!( "{:<10} laps {} gates {} best {:?}", e.name, e.laps_done(n), @@ -402,7 +416,7 @@ fn render_shot(data: GameData, args: &Args) -> Result<(), String> { e.best_lap ); } - println!("wrote {}", out.display()); + out!("wrote {}", out.display()); Ok(()) } @@ -415,9 +429,13 @@ fn bench(data: GameData, args: &Args) -> Result<(), String> { }; app.settings.caravans = args.caravans; let frames = (args.seconds / game::DT) as usize; - println!( + out!( "{:<10} {:>5} {:>8} {:>9} {:>8}", - "TRACK", "GATES", "LAPS", "BEST LAP", "ON ROAD" + "TRACK", + "GATES", + "LAPS", + "BEST LAP", + "ON ROAD" ); for ti in 0..app.data.tracks.len() { let mut race = app.build_race(ti, 0, [0, 0], 4, 99, ti as u32 + 1)?; @@ -445,7 +463,7 @@ fn bench(data: GameData, args: &Args) -> Result<(), String> { } else { app::fmt_time(best) }; - println!( + out!( "{:<10} {:>5} {:>8} {:>9} {:>7.0}%", race.track.name, n, @@ -494,7 +512,7 @@ fn render_music(data: &GameData, args: &Args) -> Result<(), String> { let n = (wav.len() - 8) as u32; wav[4..8].copy_from_slice(&n.to_le_bytes()); std::fs::write(&out, wav).map_err(|e| e.to_string())?; - println!( + out!( "wrote {} ({:.0} s, peak {:.2}, position {})", out.display(), args.seconds, @@ -502,7 +520,7 @@ fn render_music(data: &GameData, args: &Args) -> Result<(), String> { player.position() ); if let Some(t) = wrapped_at { - println!( + out!( "the song loops back to the start after {}:{:04.1}", (t / 60.0) as u32, t % 60.0 @@ -556,7 +574,7 @@ fn trace(data: GameData, args: &Args) -> Result<(), String> { } let n = race.track.gates.len(); for e in &race.entrants { - println!( + out!( "{:<8} laps {} next gate {}", e.name, e.laps_done(n),