-&`^%NX?S-|HI0-9T=gkLB)Os^KI4dS-fuDp}u-)61$k~>G8<(PN5~njy-zfiX
zhB!iF`c^yvIt!b6x-d
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/RecoveryCheck32px.bmp b/tests/screenshots/src/app/wallet/glyphs/RecoveryCheck32px.bmp
new file mode 100755
index 0000000000000000000000000000000000000000..75c88d6c8c05ad9acc0162a162af4bc3e57f91ef
GIT binary patch
literal 3126
zcmeH@Ne;p=3`N5pbaM_3wEzB<%;W+~FNzKgVdkzF&
zk_^>OkwMf2C#!1MNd^@{DkCyJ_9l={L}=NN?_ZI@?FFp1H$?Y{kY7*sVCku&N>%K2
z5!$h6mCy<)*)Y^0+!`x<0oqQ_FTpL79&gu-ZMGSVhZJnXKc7-XG?>2K~#7F?VEvd
zn=lxKUws|GnV@VCkH82XffYP~?Fpb0*qy*5zy`Pz$en<60?N-pDii|(34~<6yLVV8
zAbL+oKu!QFD=*DAsIe`ytMp!#y=q(IL5+_}#G~5Y3;n6KcdmXm_J!g?`9ef(2`~eG
zAqF5{sHklLCV(#K5I}B;LNte=5?-McKyH~B)aRe_1PnlKsTdSO*urk}wNi{64mOSX
zK7|-a0Kjykb<4*97+|!qQ;0#505IOzskDa#G0Zn~D#fB%@m+vs%M5Pw2JYQqBUJ9-
zJ`Im+079h)_j%_Fo32VndE*NkydFRJf-^(F^Sn|LbiNlb*mYkoRT$41W2NYK&;G0{
zfXtK$Rr56xA+W9h7HziA{D2TXv33%q@gB#q?6{X^@x6|Yo|o+X#uZ>B0BYcFw=-ql
zYybmi?1r}6ZJws7s(Et(jF_>d%x76PG|c$}pi6$_jNNN~#BiEFfWEOyCfkm^-EKO~
zn+c#V1nS{uAzUt(yp6FFsj&cthR|g`2m+OMmF
zr^$RH0P&xqt?Br_UrLoENlFog85BJ(eeC&s#_4o|23i+}w5C@4;amrF9{#-4qhpR!
z)F;MYhOXBu4u?bMJ{o)e3yXLgjH0MC`HU&e6Jh`l7=lcSAxvbxv7qWZB&jDZRl3eo
zn>`+nQ}%r4iZX_YZ+|k{@Ap%lpLO`RaxKtM#k}Fg%mJnl1NaN~`OO!^0Pb*~2VZ!_
z02&_G1rkCfRU~ki1j2K`vq#~daF<`QJ2KdHZ&!W*67d0;Zm@0z7)S)bbfa}C!U*V&
z;KHLZgTl819$t94yzL>R(75D>E+L!dga{fxnc)er{Ew!5%s_zUeKsex5?n;*hm#)u
wFSPWydeuwu!TtWI%Cx1|T4NthuB;M1&07*qoM6N<$g6;@uX#fBK
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/Tron32px.bmp b/tests/screenshots/src/app/wallet/glyphs/Tron32px.bmp
new file mode 100755
index 0000000000000000000000000000000000000000..1096b61f4c1bd6de12a0cf8e3a4b42f4806971ce
GIT binary patch
literal 3126
zcmd6kL2klO3`EmCV%=qzoPYzc=hPgmb#ePNvcFA~{76I!%pcD@Cvdx8Ps{VV+ULdY
zufO|a`7~vh%^zpK-B_0I_qgnF4t)@R-Gf&A|CYbsN32l(^c4Z_$BAz-*xZ4CFi|z)~E-EWs&)(IeTvWM@?vscBCiK)q;
f$sjC%eklSiA72tIkX!4Wioc%FB!M@Xw;lZe{WNzo
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/ada_32px.png b/tests/screenshots/src/app/wallet/glyphs/ada_32px.png
new file mode 100755
index 0000000000000000000000000000000000000000..2e883fc59f9fcb07ea4459932276afa228badf82
GIT binary patch
literal 568
zcmV-80>}M{P)T6C2I2;xqfALhZV=x9bAoUKCMQVf=#r8yX?rK-N7I4H7acBqThr~%h)Mc&NGiwgbx?%
z8~OyH2+R-$?`$MvF4ZOR9@Utk0l4=8e!4Yp#Hk)4vCDmcBf)L?%2~&2S~xV^!GFL)
z7Z;xEBLXpH$Y_bNl{3#Ln}29-)8Tb%`~xf+xc?ooSNsCTfNVcqF)wNW0000qlgyg?Zu8RxvCY-)mVnnD5~gahPivXc^rY-)K1n<}bDU2Ie;gX-QBYgc@0VN^qTV
zy}8*a$_`oEB|5}W%h@`YfgIrno><#GIt0P5#gPq!MHSe$UVBQ*7I96HTB(Y}`cStF
zLNr6D_U5UzTWMKXozn85!lqzx=aK!MO=oXK*H(R>h?}B
zOymnkmp4ZybnmE$7gPuD5C{9avbuv~!#l~&(S6z9m9^*lA9MtQ;Wy4wCiF9y;$P#R
zb`N%(FJGw3c`WBlev!t3@&n~YKD9xR(51Mpv2EaaZjFw`7{LTT6}nHyzgL3_h8|?V
zgzGGkGsnG`2yKOA@0HNKA^bcKY@8J6WM;C!{4uV(nVfNjl4T#D9tbAz6E1|#UAhyF
zbD}Z{ZX53@s9M%#r^9vrGvtMC~rw=Egk6
z0%qBHPAf!BFd3l|n+1HL=4(mRO?=!$zUjw&;N-Lv>mx-?#GD0{b;jfyvttnM0A3dN
zXs>W-0&)WA{F@{vYUSuh1-gg%CMmP;{6zDR$Z+AnBBTg+{k&v;V-NygU@#pVI({W+
j_SZ>c5c*o{@nrY|>I$HfWJj`B00000NkvXXu0mjf^xqlR
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/bnb_32px.png b/tests/screenshots/src/app/wallet/glyphs/bnb_32px.png
new file mode 100755
index 0000000000000000000000000000000000000000..c3e48554e0ed931d2f2f55eb3f7ad527708b0a75
GIT binary patch
literal 337
zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz&H|6fVg?3oVGw3ym^DWND9BhG
zd@T(<=m^Jf0I%~zW
z*no-Sq+sCd8-ACM2t88y-#Ou)^3FQO|6iUS5@xIa;4}Ti2l4YEou)ObTzKtV4>j>m
zU2$-}jkBuBkE!V^TG-A@YB{pSOjeK)F?ukGB{AJDdRqdwQljq1HLC9p-C{akIHh&-
zzuZIRt0#ZjT@-%B&m(uer%Dd@be``W
z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBITlYB$B+ufw=?eYHXHD?-1WO4DACya=5M?e
zlW5QcEsqW7|2&bi(A0NPuXieY6!GMuvPW{{lvxM&rCxN@D+rDZnUHOvbwW&rbqV`*
zQ6sMFtU|53MK3jP-n7fX?Sa_MyA$LY^tgUboSe#VNASF2q*A~!;Y)MWcL`t6s-E@b
zfaTA4fvpYB$`_`5acB)*9ad_ib=$_-R`JB%#V(9H)}%B4oVFwG!S^-CL*MGpdTe;G
zf6`Xxf4P%quxbIDFD>SwCw`ONV9Ry)5c`FpKBb$PO_X64x?rh@6F+;O{?
zGQ@137f^l3RdM&BRK=C?Wmoc(U%h1dTGLi_$dz+@M^(emjv)JE4K~x1olo}w!-v7s
L)z4*}Q$iB}66KJ#
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/btc_32px.png b/tests/screenshots/src/app/wallet/glyphs/btc_32px.png
new file mode 100755
index 0000000000000000000000000000000000000000..5abf8ab1861940d078aa3184cabef6eae94c0269
GIT binary patch
literal 656
zcmV;B0&o3^P)F*H7)FMwy9eA1FC9;YU$h`#BsHuJwUAXeN70k`^fWvrB%ue9KVf*x
zoIJ5z(75GRYFxbYG);AuWs7zZZ`^opc&&giXu85k0d~0xTm&s2S`b<<1w)FJkOi)X0wrcFPlqeptJ*SJQga~VHge!#&N96vK$COUYM}B){1Bp7m)z&0h1H`eWg?*$U;yZut~3-
zfOisNyjiX1LF_fn97awrjL&~u3ye+eUeOE^a#RBd55PD?JRl$;
z2gC=HN5io!NQlyNVF^p}gxC{H?tF}dgl|eStm%ml6AMLc+4u;lyE%btai5P<0NpF1
q_5~)S;m7Hfem_hvH1XE^FYyC;Po6m{Gz)V80000Da^0I0yZ6)0nb0ZIIXZ)P#J
z!7L}~P7?6uG;kknQ6y@Kl&E>Uz7rLqKcZB$#tVr=Q_)sbIw}{%IQ)sH!DuBOrY`9z
zMyC%jX6q9VK#8&^m@~?7M>G`3N!=Z0$B$v!7*M~Hl&2l!I>Hc<$0E1K%96i(wj9c)J
zcp9D68O%crR$;TUd1=Npah36S?E2iTZLS?+&Wuz8o4tCUhHTh99G_25_;xfHApkRS
z4a?>7ONze^qe^8WNn(bfCHn^2#rPqtyb&g`ew7K
z{X7Q*2a2IOU>qo_2Gd)g=EIrS>bTUm>@>tt>_KW@xVnsF`MNeaU>{=>ayCQNl{$}l
zoYy&a>W8M*W?5GHz(41)B#7$_MvhN3Dl%9ygZJ`yJDM^oZFs|T5jeq6fgG^@F^{$Lbcz7NOf5xbIbIQ|lia0Nz%kuqt+
zR1VhRJNXI6l;^eVWEEkZWh8yRXUTclNfchQt58iV3Z5vu@zzJx&CT#&
z6b?D+USkQ43!-PCjz)72Q>^ohSOiZVwTZ59Sl3iH(Yb+M;6H4lzm&hR1n#V<1`a;}
O0000}M{P)T6C2I2;xqfALhZV=x9bAoUKCMQVf=#r8yX?rK-N7I4H7acBqThr~%h)Mc&NGiwgbx?%
z8~OyH2+R-$?`$MvF4ZOR9@Utk0l4=8e!4Yp#Hk)4vCDmcBf)L?%2~&2S~xV^!GFL)
z7Z;xEBLXpH$Y_bNl{3#Ln}29-)8Tb%`~xf+xc?ooSNsCTfNVcqF)wNW0000E)%&~$@tPoP$*R>fK%5;4(0s{?C|2t=fTR$qOSykI=U`t#gd
zvQ}{COwHP|Bmxn*f0O?a&y>)iH=}TZCnuhf7Rh>p9Vs>WCjt=&k3g3l>?l~IjzE`<
zF;-D1k#pL1I_3h1|;-)S5b%PAJ#FB~_B*bM<>$O56TqbQ1kjaoi;z--G
z*OL;z93&aK_Nt&`y{^1EM8N)iy+?oJ)j-c=jYv{`X$`&9ORdv6=A%acYWzja-6MjO
zWf3!5@GMg1%%C8HLCYeYcIGxHG)jBL8cP_o;M2+_3oc<$u%wNacmYm}aqhfs|B0#4
z?`9YqVu(&Pnn8ncepZXAbfLdls>H!dTPcIVXeGA4J{$79L)-!%^Lp%%V}XAI-y`l&
zs>CdVmIanF$mTrD0!tV);2GSo;1UKarTqgFNxj`_5ja4ExictBq#^CEh#*x4AE2L0
zjMiy^{226y^-6jEh>>fNsRl1FzM)tR&qI!#H6n4D6m)PeWw6BoUwy1`pqEEttiqmz
o43Y-ZkuAUVdPn5_%Za1q8%y#RMb7mZl>h($07*qoM6N<$f;~XdZvX%Q
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/dot_32px.png b/tests/screenshots/src/app/wallet/glyphs/dot_32px.png
new file mode 100755
index 0000000000000000000000000000000000000000..c3ac4cfa1e18e8b3fadb4daacaef8ba627ef1bc8
GIT binary patch
literal 494
zcmV
z#4r$rAIpW~mM|r32|jX62ZRb(DoE&n4zev!K|}@6!RE*O|Wg@^F#%~uW20B90EdcxU7qM33bsv%@2YQ%5-3ub|#3DIs-mQ_Yg3E
zUt0q?hm4P5&5)2!E>ZLW*LIfpNt4iI9GA(+YDj7Kg?)aRcKZGKL4pF{r$ZV)SQha~%_e5EDc+
zZs8vvTnFu95OZzk%OG0#X)~d$gZxq21lu0E{3$XD0vI(8wMVh$QMwsD_#|~}uKp*u
kKs|0-68!qU2K}Sv6X(*$Rn_SZwg3PC07*qoM6N<$f}<(PWdHyG
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/dot_40px.png b/tests/screenshots/src/app/wallet/glyphs/dot_40px.png
new file mode 100755
index 0000000000000000000000000000000000000000..b65750c3885f9b83cbaa75d55cad849f1d8355bf
GIT binary patch
literal 582
zcmV-M0=fN(P)`XqjOoX5by`%Mv4S2#_5jnFNfT
z_I`)N*8YO6B)V}%F_qc>3;3^IVRcA%g8}1n?U-=Yxu0pN1jopk*UtFTF>Z}Wmf%mO
ziyUU93hR_>8*9d9i9i(iEvpevBiJp5GW3af){t}N8Lm^D<9HBUq&T=0?qzoFi_jsE
zo-!pxkgimDR(*vDV}xUYwA#A2HaMQ)K?V2IMW=&+IZ7X81-ElUX8$SN2S)iCn8U=|
zCPXO$1(UWKJLiLcV+OPAwEPO!L&1bmP~$*KH+@JdTtCflCGRZFgfy&F%-@lYLO^5S
za!{%i6ep=$EydaoJjfw;$SJF+F4)d}4K=utc8jAJ%21D5u&WSA1^}Xc2pGG-NX#K`b#j~y^#L=t(!TUi$@+##>Z5+Zd~oyp0(+-N
U;v>!2Gynhq07*qoM6N<$f_akzDgXcg
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/eth_32px.png b/tests/screenshots/src/app/wallet/glyphs/eth_32px.png
new file mode 100755
index 0000000000000000000000000000000000000000..32e97efdf96c52cfd99153fe0dcf22bc3574375f
GIT binary patch
literal 872
zcmV-u1DE`XP)A^r2PW*58w-gg(j|mxG?dN_y^z*4D$s-45KD4aOWhN
zOs_QY6=4Ac8kloCV4$Jts$!CvlXR%QRNZ^}K8xrdBS=%=@bED9{{C(}Jw5#*MFdC@
z2L}g6Fc?f}nwAZR!#XKKnZWGqY^vMs8d&O~P$)%;P$Iythw<2eD6z7#Vv+)+3G`j>
zmaB$Bn!teThrlwGP)HFtIyy34u6IK^6v713>9igQ1hRhnWJ4iLU}0fltn1y74TTT^
zPCO`1kT`@xAw&R)S0G7DN
zW`6hITdfv7JUozPSyZdl9NGQ-z4HrWr0Y7(&CNOMPXn43xw*Ny^|BG731HXbOPsI8
zz;O&SiE*%Pn_gdEo%Q_uyhDbG;5=7O#LI@~G~D0cPr<#L<#L(s?(PUvk2;;s$Uc_4
z(QG!UTCMg&^VKj6?eX!kIL?lz2MUG4mwLTU(|`#;$48}7`S9A~nS|%(=LxuP5ziSZ
z0%vpWb~}Rbosuj8OuX>M#s*zqU-RT!gy;u}qljny?d>hTyu7SOqtQ*0C4lU=I0;dP
zStfFIb+rXCKNk5IMl!B1EiGAVYin6lK_1hy=ygD(49i&RP?Sb;o$c-IS*xq7F}!oT
zHMk7nAq(pazBp%^rf%EKZK!s7fNi%U@S>$efN^|$%EcsOf$Q?yE%SmBIpYS
zO7Ik2j|L1o9kA2s#xE}~bCdM@66lfOB-;Kw~&sLqs1rL>=JAp$)T
zJP*vF!yuB+f4AIn;I>ry86g1(k7IPeI2#<4^=5=QEF%kX-y%T>6X4`SCye{;1F5Y^
zNvBMJv9q&Ngn^{@4lRSEyCn%oUrIrO6LO>}X&U83l5T1O3^bOZeBijZTI~!eK#4#y
ynfyQ*a-#8&;q|&q-2WPIO;4cR%G?$IKl}mVM|Cy;5w@oQ0000t<=7E#A0Ft!`r#fyGGMS7F9K;2X
ztaaUzlF4KdI4Cxt!&j?BE-x?5pJ5p0y|c43^GJY_B*F0TunFvv(Qdb2G#U*fsBfln%WW-13nKu!uA7iv
zLIAkCyEEYkl3*QCC95fy7a%}^5hQD|lcEiFa&lrG{bWbbOX+kPMn*==pQV|DgM%_e
z1+>=Mz4P;PnL7517Av4FisabX7)4gc%gI4j548B=;zB+?J_g<*`yU=2;Qsy|Mn^|w
zyw&h)%QQ%`*({A<${9JRElUJ3pAG!pHs+`r)Rt+Qxw^V)OiWCC!qXr4VP#NTicA35
z`%b6x2HSmB6h)F{IgNwur{6n)5yXPDh#&m2yu4hZYmBCh2NgSd>($;bf)p{#Vb!qa
zqv`2axMafXYPCKK0~82o&w+G~pjr^92VfaSJz1|4wn+ec@oJWwY
zKQ9VG>FMc7bAXxy>GbrJp5<*YigYq57NM`W6|`!#s6_bh?{rAnoi2S$aeX0X(vDC*98R2x!`((cX%%jI4b+U-`;4TsmdD5e6JH>W~H
zkVJz9H%mu&`mC-a_??}PlGigZ3xv*O(4cTDbVW^8cDr5AC|CuB~dYl)UTv2E^3T(oz{AwdYR+tx~D%LQHY_`wbyg_`S4Q>g~qKJEyq)1&ovUEKWa1
qn0HuIbVE(Xe@(c5$yim+|D#`*!or@2b3@Po0000``W
z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eBxo4g(jv*C{Z)ZQuZ86|!jkiA7`#^A4WIpqr
zME4gQvI|9p{MYuiSsdKSa8CVce9OC9_ats60|_3s=Da<+$|}Vl)~B{iKJelGX%5wg
zhBX?TD?cczJUJ8})nxidRdq_+{wXcDK1`pm!Tirk)rwa8r7Z71ET0r0^{-fR`h)=C
zxM_t|oLe7uJ~$%utxdg2*lWi@bsNDc-ZysL3l8|vqRzCN<)|Zw(X@x<=s_@J3s58w
zB(jyoRsEUlcjliCT-84^C8Ig&R97Uv`y+jK_2K(L>6~Tlx;FnqyIXcA^11GucH;l5
zKlh@m4^_oJSIJR~&=(cwjP~_g+rs&B?q0Vhg}ssQCMCT5;HBzw;`%os&Ze53YbFI;
zx)-GyacH`SzzykTOIpHh`lq`U&%T(%sZnz~+HtX}LsU)^*ZdDI7czuZbWbff1a!EG
zR`ZfAf>rH%lee@?e>!WY<&+CgA{)1g&Pxzqx}i0F!%2hfp4vIB5BE;%N~q0#u2S;Y
z@J}3P)0byu7Qk@ZD#Y^hQ<((k&JRn4SibSC-_7FqzkQK;iQ1RsLZM$rIXM4j^|aJk
zdO7WvQ(wsMG2w!HipLA*AeAqMmO{T4sBqSXOlz^z@pY1)uD;NIhQdPonTiYTXGsBL
c>fIlP#nM*yUheuL0!#r6p00i_>zopr01AToegFUf
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/ic_asset_btc_64.png b/tests/screenshots/src/app/wallet/glyphs/ic_asset_btc_64.png
new file mode 100755
index 0000000000000000000000000000000000000000..321c0f1bcfc81a102ffc9df93b8fd98246f2d135
GIT binary patch
literal 1257
zcmV=P)NpgJ|9LY-x{xlV%XE)29o|8*1IP-(4)Ao-C0PONfTWua(xn4dV6p;2m(EW=;5=b0
zBU@MCoX2<0e|#`9_Px52t|SATIPpjG7yQ#uYHsv%bL6=oQ*OS|htjLkRB4QF3JvTk
zjg%HjiG%nX9qc-+mV`#ug8%_Mba;=Ck4eg8GWj~+Y&H|+9Ep1@AT&C}0uQsJ(P;GL02K*4=NF4b5=D{g
zeI{_E=kfqS5agb3G#ZJ{?d|Q?cQibu)9FY!J3>VQSK~<>$H{OwTp7dg=64Knozzj;
z^J#fURc;D>mm9NI^Poehy!PMicDKs8+&`lsN)ou;yihV%?7IdT?K$*~c{UUn3p2TxlO_)Z~#sWo}+}G8+dZU2o{Lf
zzTUg*@O-u`!(AUGvN&dmdz(S%!vV|#@-U6_Oq5Jc(6h#h3X2@j_2GcLcFuFC5H$k%
z!B#);BjbP;gi*B&RiiuSUteEQ1%YAlr~V=90IOl540J#qa;9sDzyv8SazG+2+i~6o!TG^q9AFy%tH2yVC?ni*z(4Q^
zm%Z{B3Iy0358?P5igto{w0mjyJpO>;Q^lf1KRq5~gMkI99~+ccj&jBN^4ni4ZKIgD}3OJCiUL?9FFSlC~!8RR*nXi`%afo(D>
zfF+LtH`~hY(%m5*%TADljPIiaq3aE8_y6c5Dm#ksGn5IAAszU8P+t;%aWVpsd=o|RLkX?lon
zK!A0pG=jU){0(-x;#>FR5PzH`A=dlyE&Tq|WRNK{$L}@C+2%OioI7#igdctZW9R-L
T&6THJ00000NkvXXu0mjf>fu?z
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/ic_asset_cardano_64.png b/tests/screenshots/src/app/wallet/glyphs/ic_asset_cardano_64.png
new file mode 100755
index 0000000000000000000000000000000000000000..4944aa136edc9dbde8af6c0b2c079c3b64fb3851
GIT binary patch
literal 1041
zcmV+s1n&EZP)28t@ZU)_=tSSHa)hfNswn*YSUe08J3+T$X^uJ
ze>`_aANA==zF>
z(1o|1P9h-NARIOBjC!_+k1)~X41+!PpnRQvKsP8(Q;!Tq)Ker+=(M(gyWVWfE{i`!)&$^c~BU%KgH?r3*`Q#tVWZN7_^mZA#%(KrXVr&FZzXk#^n&`
z!Xt;WB@V+HzX+*+mpY}eF|N-RE?M&e>F^TSc6z22-D5v=a(YBM6P+#&Ver;7D^KFk
z;hCPF%dR~#%G1UR3Uiu$*mHG!0r%kgo#R~~e{R&Hth^^=AlE{u3EY#v*xOtUY4S^;
zjiTuH_B$M&@F@7F0LwKvoc}IwPJuRxfY3Qemo>QNxHM#@j{JqEoe45p!v~xX(rKfU
z#NeTy+w{jF&oX@&oMRYlU&qZb!@f58K1FMUL+6=V$Jt=7%_d8pe2(9)`TT--W}Z5H
zYXj1G5}DvZ^1mVmqS
zv0=?eGa}D?Zm$G;KSD@=grwYNq*J}dGahxb}uz};t7XjcCbs!YJ$
z`IH@`Yjwy2yvjO%41_M!u}0YW3FCPP(^SnmX1JljyAar)8g@wIYjelQ(p@D%ava3X5)Sh-vPJ00000
LNkvXXu0mjfiM#JG
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/ic_asset_doge_64.png b/tests/screenshots/src/app/wallet/glyphs/ic_asset_doge_64.png
new file mode 100755
index 0000000000000000000000000000000000000000..caf0c2ca129f8f846c27bf93c5019db6feafeb75
GIT binary patch
literal 1318
zcmV+>1=;$EP)2lJwO*FP>0C=w`lD8{UY6A|(=tifLS-t*5{hYg6-=NA@&)Mo+x_0b
z?(R9~xw|{};B(J!`~1h{Ip_PF=lAyf48t?eJo79%8v_a*WEPkPt_N3wVIXxN3Gg7O
zaF9*lEwBPS1PZ{lU<619L);eN84z%gKfrI`FxU^Ozz3iN%mi1sEx?PQH_nJKyTJyq
z2;2xR1!;*QK!0jL8JNyvk#GW>1_!~%UzdLjuRbW@10K@k|sNRgB%0tn|RLOoaj
zvcceiAb@!BoeXDXHmn8jfcLK06#K|8x_ME$rNQWSO`7>4SZ3vb(?vF<}h1Kx{w73H>nZIL7RnBd0I^(p`fs?~Jtph#18;!Qx(SfR$RRKH4@HQy
zz8r&}8HKbGAc@&t5%Y}G7QUc?e#57e03o{tyk_AGLe-r!JbX;x<
zW221#`&jACj__M6@h%6;CJv!tODExD#JKxBS1Twn!iN&trhR`Y-cEL
z%nNLoMtTOR9*?uT9Fsi^TEnob(`E5a3eg$`-!sIi#O~qXY4C$ZS}cBGeXIf$#j(Rv
z#XA5M0sLSo*lP1X%XJlC7~5c55>)^npY01z0EY#b3tE_3hUipqf*mYYPDR01R&c65
z;7OLojtX!a8w~w9WV2{9bAc@pertuq>~V^h9ClG4lnZQ#Re+0`b@gX;6kXxmD9DcD
zazp<1CFUAtC2N6aEwGpEc`XHy)>wr_S4gVF
z9bmDJ0t}(E+y>5A)FYoTifJc+kNHBCVSQC5M`Wgs0!W{4zJ&wqWuM?VFhM5)2C-Lg
zJNpey79p^apZ`dm1dx}Pi_~IJ$80mw`hW9yOfwvs7jR1eKQq&fbaG}_=Jjl@!I!4tXm9-H;Q15dDVs*L~zT(^eZBdhi03z2K}%nMlnsRv|$;%Xx}IxF{qyDZw^
zp;mYuizL6{;6!OW1Q6#XgOHD~;tMGB6Pg$uW*82eod9w(Tf)yUOA)!smieAL8RJXA
zW+OOm6Hw?u$ZhdEOYcu)=pH&F3e*cRvF8`xjL4dQ2`=zW8QU`(n@gUUquQvMTh9yZ#P6PqYbJwLc
z_qGw7GxQm8G`9sf!ASEI9jEMkitYlmv&AJ%4jHYLTcPXt@^Lpe1gK|=yU^iIlak4(
zTBU)rsbqjM>+=B9;XJo?Jv=X81$IbdUBBUi82llYAgNh{XXbD=Tbx-m_>H
zQIMn#JRN?>@K1^i(=$xvXB_$4Ugioe|vmh!-1?diOYJP^~^G<~zN_T)@&2I@6H%n3-
zAW`^yQPDk;<^Z2APbk;d*Yxo4KyO59_o#~`;Rs*ocF)hxbar+|r>Cd({LRe`RjXA>
zBofru*GC;4q1VLi9;HbF21$U04?nzVORLdv7yj-7!>G5n*LDWahmbxzJS4L>fdVsLQKP7g00LCjMGB1HfTA3D6>-R~UQ?$zeJZiS!-XoMqyBZ4EtORZKLMif{i
zNTdjW@%GJ5P=3n
z!VgDr(#_-Jva;C%0)3)9%YOc9VW5Msv1$CG@sLjI4v
z0uj)FG7Qf`3@ao7;S^X~TbpwlA2tUE2iE@nzOuByaV<>Js03-R@spF|=G4@b@H0i>
z2-w`*OuW3je8E+(K#U5@%gdH`Jt88Hb)mP$VG+Q2?u7eR)iby-O*4VY!%Y%%cXxM~
z1d%Zi`eq-cV{Rf
zQW_#?gH7|*cfWH_kna(=L)0LYC9Gqi{Lpv|&T~Z1u-AAF_sPkL9E|7t4fEgQ6Yd0G
z3M2t(We};rVgA1-z)jME%jPFUK-I6TO-)UpxoW4DPY=*Y@9gZ9NdnRwz#tX)>dpF|
z2#!Enq#M(s5zceO6sL*lzlBkN@p+PjR0l9{0#}8=QDKlixMdBwoeA2PLqAH%^Fw-D
z97gcCJg^zS`oJ$saP@GPah}&|&)=IF&&Nb6N$CT6ksbkzw=_0AInv_e$G^xeYt4{R
z^88TpXl!wDk$>ptuCelD0@1L(m3NSjiSgbd44E!KsKALqEQlgpaLh(
zL>cOaq4Wg_q(T{!_auy6^2($T2PGxX4=oPh
zK@SwaWg&Zgt!Gu{y%%k7iaMq7L)dm)^7i)jN~8&`FH55|&NVBIs!N_9S{)Ep8j&W`
zx1kn>^qTAm_z@Wa&Ch=L%Pfd
z&Y^IFnUX)ejiG+U7chm#rj|?yr0Tp2n6`J&sphsMz+lewVB)}KK(WDbWPpJFR
zmf?zkF=*n8peNS(*_L4wK*B~b9!)$EbcDJTmb-yXfEcSP&Gd$A;t5%q5>pBwXvRCB
zi93{(De)$x@JKY_j*L0Sg)$`$%>gmd_&XF*HiVC+M4z=lEHrM%e9A;6g#a7ubBM<7
z;7}fIoza%K9VrCpw#q|9Fs1c*XzPr=(_<50>JO74BJ@9-h~>O#$!rxsGaX+Ma`n8i
zQ&S2c_=un_d9lNOZ%VWY@Ju{hBA7@q=7R)~aM8pgM1)k%2Mqz?7jf|sA=PZZw`JU7
zr)KzWuqT{Ogs+YLyvUp4yws%!@@q0uU%sZkdm|J^mrlLyiw~3Lwk9!bC7AKO+$W&@nB#I__r#_9n$
z7j6d<&MS(tML3Y*jWI=;A|!o3=n_R)B1l_8R+~{!yyF)oJ7lt%^X{-=<_D>c6?vmU
zu2oSWYtjmFY_Cy7uiKEut%xhcZ?3>`6EZW)INcHMw4Adhaj56~)09y0+Kb&S>%Kky
m<^Mk9zCE5;SXfwCNXtL&ekQT%#M;gP0000JJATKjm84)xf{20)k&e^=q-RN@}g7*BM!$GaxN4#RVu&sB(r
zN5m~;go`DB8+t&0<*u+SQ-yPE3Ig;MyIYL+k|a}wOO3|=9C{kjSdJ{h8HK#!vk7Mi
z{#Bhuda??49C{i|iL~$vfiV;O6_t>NRKkOAgGW!}d&rYocyI!0eC&Nm2+fKzweX+>
z+;|))$UyLqu2jN<65tDv5CCr5nFkjRWq(9
zIrC_kS7@Xph4o2>FX!mIM&iQ`KuEOu)Hwp0K{#S$8p|5rKw(
z1Q6DHA5T67rab!2A)N8uwiP0n`=(bT~
zCWPgSn|s+oh=bry=qz#wmwJq@JZ;=yCR-Hls1Z5*fo%$B6aVhHM2VT>geIU%Vml&X{m}x>3272>
z!t%S%q98QWr$)GFzE&e)kBe>@9e~jE9@mH~drYnKt{%8;wEXuT8{aNpC=?2XLSfzf
Y0}Iv1ko7$zdjJ3c07*qoM6N<$f=Xh2cmMzZ
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/ltc_40px.png b/tests/screenshots/src/app/wallet/glyphs/ltc_40px.png
new file mode 100755
index 0000000000000000000000000000000000000000..27822a459e2115b9ef947c83a7574a11ab4e25ce
GIT binary patch
literal 606
zcmV-k0-^nhP)jK~#7F&6x2~
z!Y~-cpPc`T6Y!0|2;D$7kPUPLa01w%I)OSty8&*HG66aP=CjlbF{R0uwDx!Ra+g3K
zuSpux03+uLam=+ct(g{SO;7EgT9wvbtJM0yfMi-5t%KIlQ%WmGeG|RGy;TH+YEv&k
zHwl8-S|I_Xwd)~Vqa7l8lTgo{W6x3$grtGOi9_}|te1w{iukDA4AT|llvaI#IGooj$nN&d;T5|I9BjjR
zgxS*aqR4P(4@)z|8RV2!JwqBYE}DP@8NAP{thRg9`@z^fAaT(IoI%EmqwPuS7InxN
zh>IrR6yb1yvZehI@k4!MRtkhIq`VBcZt4}X2cqqFetR%vICuzoev#v|*!9oki;E`U
z-v7Fvzcq!wJ(w~aQ*kjdE1uBl`=!->hqP2?C3K
zb%J}zF=6iq_))>Jj&9+9camj^0{mSg)oF)RGTA^M4xA1WX>wm&S>C(
zop(oP!Hkf@~Y9lK)
zFeo3)K9|~XfUf&zgI5nBo7xS6gU(nu3$-v-1%u`L1`9O8SSpx(*#(peX0i(?70h1t
z)T!`blK1iw!Li1ov?$y0lpgozNfftHaKMOlHf8EI2xhqZQM;g2F!1<9wQJ^*!P9WJ
zW}Y6xF;cOCejpjdUMIzx+aZhvhwghk-pd{CqgYK195J68J1DVOzD5T5Lum~KxnsGZ
pLJt(cXX8Q&WBDikNA#T?6@Q;7{F!tXk#hh5002ovPDHLkV1hA8xd8wG
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/stellar_40px.png b/tests/screenshots/src/app/wallet/glyphs/stellar_40px.png
new file mode 100755
index 0000000000000000000000000000000000000000..5c8fc69669b2024c15c13c40c9dfbf906599e59a
GIT binary patch
literal 567
zcmV-70?7S|P)jt^6o1*PLfN2}u|46a+7wh{<`e>RHa
zgcz4KI?g689@!)OVrNrthX|C_(3U!xPJIu2HS*kMY81)QzgSRN59p&pdkAS1usd4~jR%eR^BS#hL}gIGzz;KYBnev3?;BLRNzl))i|;_H(21apZoC5Ds8OOa8GQJA?~qLsv4a&=5&T2YMI@Lv;!p+<9fCAfEwXrCyTX?{uLrnkfpMMdm(3m3x)^xsRMp+zvp6V7Q0aB4&$*`
z9~Ba=Gv;0%5xO174&4N>FORM|KG9twy&>b}(ZSX3S)SU4O~
zIUsU|NvObKSwisvnH;`tyDy|U1i9UQEjH=j6LsAw|E{;iHLh4;uO0a%yJ*?1@=u&b
zw`}zJrB3KSoj2!D?8;fr=gl85H?0*2-{H9ARrKltO^)>i@f;Hi!Z|K}Fzt8~{!oo0
zZ~jTvokuFp$SpkNE1<^CIp1l^jP+r&=bLV@*~EG0h@$1Qt8d-vlJbl0Ha689*ulI<
zde1NJoz4lO5&s2$#`Za^`tX+}BV6E`(VpWwq@(WSIXmhco;`JAuStvV4vQlf74NVF
z9uC^-zQer5WruM~$`7@UOw}h{VSc}2t}oS!?)<5GHL?G@TZq73#suSqb?h|{i+~}-
N;OXk;vd$@?2>=itksbg5
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/app/wallet/glyphs/xrp_40px.png b/tests/screenshots/src/app/wallet/glyphs/xrp_40px.png
new file mode 100755
index 0000000000000000000000000000000000000000..9abe42c062f807d1faf891449005a98698befa4c
GIT binary patch
literal 433
zcmV;i0Z#sjP)-Jss2Y*036Hh>KvFaAaPN#XUeKOl0FrM61ti1y
zr!+!mlK&J+WA>EGJ+TtPXJzu|EIpq`7B=@Cc7(%{UR6DQ1IF*ds)C_%9k&$>Bhwoz
z(+zRv{^2+?o%fm$Ds`az6d!2F+`L#4MZ>6vTxchIZre#X(MP83$5pas(iSSm`WAA{
zBrSFn!Aa6`ff9@*J+?c+NYZol6c$Pf>+HovIja9yft2LRhlTOTl<<7%3)7qL`7fi|
b#)btSAou)mq2(U-00000NkvXXu0mjfFUh%N
literal 0
HcmV?d00001
diff --git a/tests/screenshots/src/main/json_scenario.c b/tests/screenshots/src/main/json_scenario.c
new file mode 100644
index 000000000..ed9002b93
--- /dev/null
+++ b/tests/screenshots/src/main/json_scenario.c
@@ -0,0 +1,910 @@
+/**
+ * @file json_scenario.h
+ * @brief screenshots engine implementation
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include
+#include
+#include
+#include
+
+// You may have to install packet libjson-c-dev
+#include
+
+#include "nbgl_use_case.h"
+#include "json_scenario.h"
+#include "nbgl_touch.h"
+#include "nbgl_driver.h"
+#include "os_registry.h"
+#include "os_settings.h"
+#include "os_id.h"
+#include "apps_api.h"
+
+Event_t appEvents[] = {
+ {"BTC_SIGN", BTC_SIGN },
+ {"CARDANO_SIGN", CARDANO_SIGN },
+ {"STELLAR_SIGN", STELLAR_SIGN },
+ {"STELLAR_STREAM_SIGN", STELLAR_STREAM_SIGN},
+ {"BTC_VERIFY_ADDR", BTC_VERIFY_ADDR },
+ {"CARDANO_VERIFY_ADDR", CARDANO_VERIFY_ADDR},
+ {"BTC_OPEN", BTC_OPEN },
+ {"ETH_OPEN", ETH_OPEN },
+ {"MONERO_OPEN", MONERO_OPEN },
+ {"ETH_MESSAGE_SKIP", ETH_MESSAGE_SKIP },
+ {"ETH_SIGN", ETH_SIGN },
+ {"RECOV_OPEN", RECOV_OPEN },
+ {"DOGE_SIGN", DOGE_SIGN }
+};
+
+// =--------------------------------------------------------------------------=
+// Variables
+// =--------------------------------------------------------------------------=
+static uint32_t nb_pages;
+static ScenarioPage_t pages[NB_MAX_PAGES];
+ScenarioPage_t *current_scenario_page;
+char scenario_name[100];
+static char *productName;
+
+static char *jsonName;
+extern char *dirName;
+extern char *propertiesName;
+
+// Set this flag to true if you want a verbose output
+bool verbose = false;
+
+const char *control_names[]
+ = {"BOTTOM_BUTTON", "LEFT_BUTTON", "RIGHT_BUTTON", "WHOLE_SCREEN", "TOP_RIGHT_BUTTON",
+ "BACK_BUTTON", "SINGLE_BUTTON", "EXTRA_BUTTON", "CHOICE_1", "CHOICE_2",
+ "CHOICE_3", "KEYPAD", "KEYBOARD", "ENTERED_TEXT", "VALUE_BUTTON_1",
+ "VALUE_BUTTON_2", "VALUE_BUTTON_3", "LONG_PRESS_BUTTON", "TIP_BOX", "CONTROLS"};
+
+extern void delete_app_storage(unsigned int app_idx);
+
+// =--------------------------------------------------------------------------=
+// Return 0 if both strings are identicall, up to % character in str1
+
+uint8_t strxcmp(char *str1, char *str2)
+{
+ char c1, c2;
+
+ if (!str1 || !str2) {
+ return 1;
+ }
+
+ do {
+ c1 = *str1++;
+ c2 = *str2++;
+ if (c1 == '%') {
+ return 0;
+ }
+ if (c1 != c2) {
+ return 1;
+ }
+ } while (c1);
+
+ return 0;
+}
+
+// =--------------------------------------------------------------------------=
+static int add_name(ScenarioPage_t *page, char *text)
+{
+ page->name = malloc(strlen(text) + 1);
+ if (page->name == NULL) {
+ fprintf(stdout, "No enough memory to store the page name \"%s\"!\n", text);
+ return 22;
+ }
+ strcpy(page->name, text);
+
+ return 0;
+}
+
+#ifdef HAVE_SE_TOUCH
+static uint8_t control_name_to_id(char *control_name)
+{
+ uint8_t i;
+ for (i = 0; i < (NB_CONTROL_IDS - 1); i++) {
+ if (!memcmp(control_names[i], control_name, strlen(control_names[i]))) {
+ return i + 1; // ids start at 1
+ }
+ }
+ return 0;
+}
+static char *getControlName(uint8_t objTouchId, char objTouchSubId)
+{
+ static char controlName[100];
+ if (objTouchId == KEYBOARD_ID) {
+ sprintf(controlName, "KEYBOARD_key[%c]", objTouchSubId);
+ }
+ else if (objTouchId == KEYPAD_ID) {
+ sprintf(controlName, "KEYPAD_key[%c]", objTouchSubId);
+ }
+ else if (objTouchId >= CONTROLS_ID) {
+ sprintf(controlName, "GENERIC_CONTROL[%c]", objTouchSubId);
+ }
+ else if (objTouchId >= 1) {
+ sprintf(controlName, "%s", control_names[objTouchId - 1]);
+ }
+ else {
+ sprintf(controlName, "NO_CONTROL");
+ }
+ return controlName;
+}
+#endif // HAVE_SE_TOUCH
+
+static int add_targets(ScenarioPage_t *page, struct json_object *value)
+{
+ // Strings are stored in an array
+ if (json_object_get_type(value) != json_type_array) {
+ return 23;
+ }
+ // fprintf(stdout, "Will handle %d steps...\n", (int)json_object_array_length(value));
+ page->steps = malloc((int) json_object_array_length(value) * sizeof(PageStep_t));
+ page->nbSteps = 0;
+ for (int i = 0; i < (int) json_object_array_length(value); i++) {
+ bool notConcerned = false;
+ char *name = NULL;
+ PageStep_t *target = &page->steps[page->nbSteps];
+
+ struct json_object *page_info = json_object_array_get_idx(value, i);
+
+ target->wait = 0;
+ target->wait_initial = 0;
+#ifdef HAVE_FAST_HOLD_TO_APPROVE
+ target->long_press_wait = 1500;
+#else
+ target->long_press_wait = 1200;
+#endif
+#ifdef HAVE_SE_TOUCH
+ target->x = 0;
+ target->y = 0;
+ target->objTouchId = 0;
+#endif // HAVE_SE_TOUCH
+ target->power_press = 0;
+ target->forced_key_press = 0;
+ target->ble_event_idx = 0xFF; // not valid
+ target->batt_level = DEFAULT_BATT_LEVEL;
+ target->auto_transition = false;
+ target->batt_event_idx = 0xFF; // not valid
+ target->ux_event_idx = 0xFF; // not valid
+ target->app_event_idx = 0xFF; // not valid
+ target->param = 0;
+ target->reset = false;
+ target->os_flags = 0;
+ target->seed_algorithm = 0xFF;
+ json_object_object_foreach(page_info, k, v)
+ {
+ if (!strcmp(k, "page")) {
+ name = (char *) json_object_get_string(v);
+ target->name = malloc(strlen(name) + 1);
+ strcpy(target->name, name);
+ }
+ else if (!strcmp(k, "product")) { // to restrict a target to a specific product
+ name = (char *) json_object_get_string(v);
+ if (strcmp(name, productName)) {
+ notConcerned = true;
+ continue;
+ }
+ }
+#ifdef HAVE_SE_TOUCH
+ else if (!strcmp(k, "touch")) {
+ name = (char *) json_object_get_string(v);
+ char *comma = strchr(name, ',');
+ target->x = atoi(name);
+ target->y = atoi(comma + 1);
+ }
+ else if (!strcmp(k, "object")) {
+ name = (char *) json_object_get_string(v);
+ char *comma = strchr(name, ',');
+ target->objTouchId = control_name_to_id(name);
+ if (comma) {
+ target->objTouchSubId = comma[1];
+ }
+ }
+#else // HAVE_SE_TOUCH
+ else if (!strcmp(k, "key")) {
+ name = (char *) json_object_get_string(v);
+ if (strcmp("left", name) == 0) {
+ target->keyState = BUTTON_LEFT;
+ }
+ else if (strcmp("right", name) == 0) {
+ target->keyState = BUTTON_RIGHT;
+ }
+ else if (strcmp("both", name) == 0) {
+ target->keyState = BUTTON_LEFT | BUTTON_RIGHT;
+ }
+ }
+#endif // HAVE_SE_TOUCH
+ else if (!strcmp(k, "wait")) {
+ name = (char *) json_object_get_string(v);
+ target->wait = atoi(name);
+ target->wait_initial = target->wait;
+ }
+ else if (!strcmp(k, "forced_key_press")) {
+ name = (char *) json_object_get_string(v);
+ target->forced_key_press = atoi(name);
+ }
+ else if (!strcmp(k, "param")) {
+ name = (char *) json_object_get_string(v);
+ target->param = atoi(name);
+ }
+ else if (!strcmp(k, "auto_transition")) {
+ name = (char *) json_object_get_string(v);
+ target->auto_transition = (strcmp("true", name) == 0) ? true : false;
+ }
+ else if (!strcmp(k, "reset")) {
+ name = (char *) json_object_get_string(v);
+ target->reset = (strcmp("true", name) == 0) ? true : false;
+ }
+ else if (!strcmp(k, "os_flags")) {
+ name = (char *) json_object_get_string(v);
+ target->os_flags = atoi(name);
+ }
+ else if (!strcmp(k, "seed_algorithm")) {
+ name = (char *) json_object_get_string(v);
+ target->seed_algorithm = atoi(name);
+ }
+ else if (!strcmp(k, "app_event")) {
+ name = (char *) json_object_get_string(v);
+ for (uint32_t j = 0; j < sizeof(appEvents) / sizeof(Event_t); j++) {
+ if (strcmp(appEvents[j].name, name) == 0) {
+ target->app_event_idx = j;
+ break;
+ }
+ }
+ }
+ }
+ if (!notConcerned) {
+ page->nbSteps++;
+ }
+ }
+ page->currentStep = 0;
+ page->saved = false;
+
+ return 0;
+}
+
+// =--------------------------------------------------------------------------=
+
+static char *read_json_data(char *path, char *filename)
+{
+ FILE *json_file;
+ size_t size;
+ char *buffer;
+ char fullname[MAX_PATH];
+
+ snprintf(fullname, sizeof(fullname), "%s%s", path, filename);
+
+ if ((json_file = fopen(fullname, "r")) == NULL) {
+ fprintf(stdout, "Unable to read flow JSON file %s!\n", fullname);
+ return NULL;
+ }
+
+ // Determine file size
+ if (fseek(json_file, 0, SEEK_END) != 0) {
+ fprintf(stdout, "Error seeking to the end of JSON file %s!\n", fullname);
+ fclose(json_file);
+ return NULL;
+ }
+
+ if ((size = ftell(json_file)) == 0) {
+ fprintf(stdout, "Error getting size of JSON file %s!\n", fullname);
+ fclose(json_file);
+ return NULL;
+ }
+
+ if (fseek(json_file, 0, SEEK_SET) != 0) {
+ fprintf(stdout, "Error seeking to the start of JSON file %s!\n", fullname);
+ fclose(json_file);
+ return NULL;
+ }
+
+ // Allocate memory and read the file
+ if ((buffer = malloc(size + 1)) == NULL) {
+ fprintf(stdout, "Error allocating %lu bytes to read JSON file %s!\n", size, fullname);
+ fclose(json_file);
+ return NULL;
+ }
+ if (fread(buffer, 1, size, json_file) != size) {
+ fprintf(stdout, "Error reading %lu bytes from JSON file %s!\n", size, fullname);
+ free(buffer);
+ fclose(json_file);
+ return NULL;
+ }
+ buffer[size] = 0;
+ fclose(json_file);
+
+ return buffer;
+}
+
+// =--------------------------------------------------------------------------=
+
+void scenario_parse_args(int argc, char **argv)
+{
+ int i = 1;
+ if (argc < 7) {
+ fprintf(stderr, "wrong number of args command line \n");
+ exit(-1);
+ }
+ while (i < argc) {
+ if (!strcmp("-j", argv[i])) {
+ jsonName = argv[i + 1];
+ i += 2;
+ }
+ else if (!strcmp("-d", argv[i])) {
+ dirName = argv[i + 1];
+ i += 2;
+ }
+ else if (!strcmp("-p", argv[i])) {
+ propertiesName = argv[i + 1];
+ i += 2;
+ }
+ else if (!strcmp("-n", argv[i])) {
+ productName = argv[i + 1];
+ i += 2;
+ }
+ else if (!strcmp("-v", argv[i])) {
+ verbose = true;
+ i += 1;
+ }
+ else {
+ printf("Unknown arg [%s] in command line \n", argv[i]);
+ exit(-1);
+ }
+ }
+}
+
+int scenario_parse_json(void)
+{
+ int error_code;
+ char *buffer;
+ struct json_object *json_root;
+
+ // Read the JSON file
+ if ((buffer = read_json_data("", jsonName)) == NULL) {
+ // Error message is displayed in the called function
+ return 10;
+ }
+ if ((json_root = json_tokener_parse(buffer)) == NULL) {
+ fprintf(stdout, "An error occurred while parsing JSON file %s!\n", jsonName);
+ free(buffer);
+ return 11;
+ }
+
+ if (json_type_object != json_object_get_type(json_root)) {
+ fprintf(stdout, "There is no valid entry in file %s!\n", jsonName);
+ json_object_put(json_root);
+ free(buffer);
+ return 12;
+ }
+ // Next line is a macro that iterate through keys/values of that JSON object
+ json_object_object_foreach(json_root, key, value)
+ {
+ if (!strcmp(key, "title")) {
+ char *title = (char *) json_object_get_string(value);
+ strcpy(scenario_name, title);
+ }
+ else if (!strcmp(key, "product")) {
+ char *name = (char *) json_object_get_string(value);
+ if (strcmp(name, productName)) {
+ break;
+ }
+ }
+ else {
+ // Strings are stored in an array
+ if (json_object_get_type(value) != json_type_array) {
+ continue;
+ }
+ for (int i = 0; i < (int) json_object_array_length(value); i++) {
+ bool notConcerned = false;
+ char *name = NULL;
+ ScenarioPage_t *page = &pages[nb_pages];
+
+ struct json_object *page_info = json_object_array_get_idx(value, i);
+ page->optional = false; // by default
+ page->nb_sub_pages = 1; // by default
+ page->cur_sub_page = 0; // by default
+ json_object_object_foreach(page_info, k, v)
+ {
+ if (!strcmp(k, "name")) {
+ name = (char *) json_object_get_string(v);
+ if ((error_code = add_name(page, name)) != 0) {
+ json_object_put(json_root);
+ free(buffer);
+ return -error_code;
+ }
+ }
+ else if (!strcmp(k, "product")) { // to restrict a page to a specific product
+ name = (char *) json_object_get_string(v);
+ if (strcmp(name, productName)) {
+ notConcerned = true;
+ continue;
+ }
+ }
+ else if (!strcmp(k, "optional")) {
+ name = (char *) json_object_get_string(v);
+ page->optional = (strcmp("true", name) == 0) ? true : false;
+ }
+ else if (!strcmp(k, "targets")) {
+ if ((error_code = add_targets(page, v)) != 0) {
+ json_object_put(json_root);
+ free(buffer);
+ return -error_code;
+ }
+ }
+ else {
+ fprintf(
+ stdout, "WARNING: for page #%d - Ignoring unknown Key \"%s\"!\n", i, k);
+ }
+ }
+ if (!notConcerned) {
+ nb_pages++;
+ }
+ }
+ }
+ }
+ json_object_put(json_root);
+ free(buffer);
+ current_scenario_page = &pages[0];
+
+ return 0;
+}
+
+static ScenarioPage_t *scenario_get_page_from_name(char *page_name)
+{
+ // Next line is a macro that iterate through keys/values of that JSON object
+ for (uint32_t i = 0; i < nb_pages; i++) {
+ if (!strcmp(pages[i].name, page_name)) {
+ return &pages[i];
+ }
+ }
+ printf("ERROR: scenario_get_page_from_name() impossible to find page %s\n", page_name);
+ return NULL;
+}
+
+static bool get_next_page(const char *eventType, const char *eventName)
+{
+ ScenarioPage_t *next_page;
+ PageStep_t *cur_target;
+
+ cur_target = ¤t_scenario_page->steps[current_scenario_page->currentStep];
+
+ // print transition if to a different page
+ if (verbose && strcmp(current_scenario_page->name, cur_target->name)) {
+ printf("\t%s --> %s: %s[%s]\n",
+ current_scenario_page->name,
+ cur_target->name,
+ eventType,
+ eventName);
+ }
+ // get the page corresponding to the current step in current page
+ next_page = scenario_get_page_from_name(cur_target->name);
+ // if this page is NULL, it means that name is malformed.
+ if (next_page == NULL) {
+ fprintf(stderr, "ERROR: Unknown page name %s\n", cur_target->name);
+ current_scenario_page = NULL;
+ return false;
+ }
+ if ((next_page != current_scenario_page) && (current_scenario_page->saved != true)) {
+ if ((current_scenario_page->optional == false)
+ && (strcmp(current_scenario_page->name, "Entry"))) {
+ fprintf(stderr,
+ "ERROR: current page [%s] not saved before moving to a new one [%s]\n",
+ current_scenario_page->name,
+ cur_target->name);
+ current_scenario_page = NULL;
+ return false;
+ }
+ //
+ }
+ // if the reset param was set for the current step in current page, it means that we will
+ // restart at step 0 in current_scenario_page
+ if (cur_target->reset) {
+ next_page->currentStep = 0;
+ for (uint32_t i = 0; i < next_page->nbSteps; i++) {
+ next_page->steps[i].wait = next_page->steps[i].wait_initial;
+ }
+ }
+
+ // printf("get_next_page: batt_level %d%%, next_page = %s, pos =
+ // [%d,%d]\n",current_scenario_page->steps[current_scenario_page->currentStep].batt_level,
+ // current_scenario_page->name,cur_page->steps[cur_page->currentStep].x,cur_page->steps[cur_page->currentStep].y);
+ // update step in current page
+ current_scenario_page->currentStep++;
+ // update current page
+ current_scenario_page = next_page;
+ current_scenario_page->cur_sub_page = 0;
+ return true;
+}
+
+static int scenario_get_action(void)
+{
+ // if the last target of the current page has been consumed, stop the simulation
+ if (current_scenario_page->currentStep >= current_scenario_page->nbSteps) {
+ return 1;
+ }
+ if ((current_scenario_page->currentStep < current_scenario_page->nbSteps)
+ && (current_scenario_page->nb_sub_pages > 1)
+ && (current_scenario_page->cur_sub_page < current_scenario_page->nb_sub_pages)) {
+ // if multi-page text, automatically press "right" to parse all pages
+ return 3;
+ }
+ // if it's a wait step, simply decrease counter
+ if (current_scenario_page->steps[current_scenario_page->currentStep].wait > 0) {
+ // printf("scenario_get_action wait %d
+ // ms\n",current_scenario_page->steps[current_scenario_page->currentStep].wait);
+ current_scenario_page->steps[current_scenario_page->currentStep].wait -= 100;
+ }
+ // if it's a App event, get value
+ else if (current_scenario_page->steps[current_scenario_page->currentStep].app_event_idx
+ != 0xFF) {
+ uint8_t app_event_idx
+ = current_scenario_page->steps[current_scenario_page->currentStep].app_event_idx;
+ uint32_t param = current_scenario_page->steps[current_scenario_page->currentStep].param;
+
+ if (!get_next_page("APP", appEvents[app_event_idx].name)) {
+ return -1;
+ }
+ // printf("scenario_get_action app_event %d\n",app_event_idx);
+ switch (appEvents[app_event_idx].id) {
+ case BTC_SIGN:
+ app_bitcoinSignTransaction();
+ break;
+#ifdef SCREEN_SIZE_WALLET
+ case CARDANO_SIGN:
+ app_cardanoSignTransaction();
+ break;
+ case STELLAR_SIGN:
+ app_stellarSignTransaction();
+ break;
+ case STELLAR_STREAM_SIGN:
+ app_stellarSignStreamedTransaction();
+ break;
+ case BTC_VERIFY_ADDR:
+ app_bitcoinVerifyAddress();
+ break;
+ case CARDANO_VERIFY_ADDR:
+ app_cardanoVerifyAddress();
+ break;
+ case BTC_OPEN:
+ app_fullBitcoin();
+ break;
+ case ETH_OPEN:
+ app_fullEthereum();
+ break;
+ case MONERO_OPEN:
+ app_fullMonero();
+ break;
+ case ETH_MESSAGE_SKIP:
+ app_ethereumSignForwardOnlyMessage();
+ break;
+ case ETH_SIGN:
+ app_ethereumSignMessage();
+ break;
+ case RECOV_OPEN:
+ app_fullRecoveryCheck();
+ break;
+ case DOGE_SIGN:
+ app_dogecoinSignTransaction(param & 1, param & 2, param & 4, param & 8, param & 16);
+ break;
+#endif // SCREEN_SIZE_WALLET
+ default:
+ // error
+ return -1;
+ }
+ }
+ else {
+ // not consumed
+ return 2;
+ }
+ // consumed
+ return 0;
+}
+
+#ifdef HAVE_SE_TOUCH
+int scenario_get_position(nbgl_touchStatePosition_t *touchStatePosition, bool previousState)
+{
+ // printf("scenario_get_position: %s, %d/%d, batt_level = %d%%\n",
+ // current_scenario_page->name,
+ // current_scenario_page->currentStep,
+ // current_scenario_page->nbSteps,
+ // current_scenario_page->steps[current_scenario_page->currentStep].batt_level);
+ touchStatePosition->state = previousState;
+
+ int ret = scenario_get_action();
+ // if not consumed by non-touch action, it's a touch action
+ if (ret == 2) {
+ uint8_t id = current_scenario_page->steps[current_scenario_page->currentStep].objTouchId;
+ if (id != 0) {
+ if (id == CONTROLS_ID) {
+ uint8_t index
+ = current_scenario_page->steps[current_scenario_page->currentStep].objTouchSubId
+ - '0';
+ id += index;
+ }
+ nbgl_obj_t *obj = nbgl_touchGetObjectFromId(nbgl_screenGetTop(), id);
+ if (obj == NULL) {
+ printf("scenario_get_position: in %s, Impossible to find object with ID = %d\n",
+ current_scenario_page->name,
+ id);
+ return -1;
+ }
+ // if obj is Keypad or Keyboard, use subObjectId to get the position of the key inside
+ if (obj->type == KEYBOARD) {
+ if (!nbgl_keyboardGetPosition(
+ (nbgl_keyboard_t *) obj,
+ current_scenario_page->steps[current_scenario_page->currentStep]
+ .objTouchSubId,
+ (uint16_t *) ¤t_scenario_page
+ ->steps[current_scenario_page->currentStep]
+ .x,
+ (uint16_t *) ¤t_scenario_page
+ ->steps[current_scenario_page->currentStep]
+ .y)) {
+ printf("scenario_get_position: in %s, Impossible to find key with ID = %d\n",
+ current_scenario_page->name,
+ id);
+ return -1;
+ }
+ }
+ else if (obj->type == KEYPAD) {
+ char key = current_scenario_page->steps[current_scenario_page->currentStep]
+ .objTouchSubId;
+ if (key == 'b') {
+ key = BACKSPACE_KEY;
+ }
+ else if (key == 'v') {
+ key = VALIDATE_KEY;
+ }
+ if (!nbgl_keypadGetPosition((nbgl_keypad_t *) obj,
+ key,
+ (uint16_t *) ¤t_scenario_page
+ ->steps[current_scenario_page->currentStep]
+ .x,
+ (uint16_t *) ¤t_scenario_page
+ ->steps[current_scenario_page->currentStep]
+ .y)) {
+ printf("scenario_get_position: in %s, Impossible to find key with ID = %d\n",
+ current_scenario_page->name,
+ id);
+ return -1;
+ }
+ // printf("scenario_get_position: %s, x = %d, y = %d\n",current_scenario_page->name,
+ // touchStatePosition->x, touchStatePosition->y);
+ }
+ else {
+ // center of the object
+ current_scenario_page->steps[current_scenario_page->currentStep].x
+ = obj->area.x0 + obj->area.width / 2;
+ current_scenario_page->steps[current_scenario_page->currentStep].y
+ = obj->area.y0 + obj->area.height / 2;
+ // printf("scenario_get_position: %s, x = %d, y = %d, type =
+ // %d\n",current_scenario_page->name, touchStatePosition->x, touchStatePosition->y,
+ // obj->type);
+ }
+ }
+ touchStatePosition->x = current_scenario_page->steps[current_scenario_page->currentStep].x;
+ touchStatePosition->y = current_scenario_page->steps[current_scenario_page->currentStep].y;
+
+ touchStatePosition->x += (FULL_SCREEN_WIDTH - SCREEN_WIDTH);
+ if (current_scenario_page->steps[current_scenario_page->currentStep].objTouchId
+ != LONG_PRESS_BUTTON_ID) {
+ touchStatePosition->state = (previousState == PRESSED) ? RELEASED : PRESSED;
+ }
+ else {
+ touchStatePosition->state = PRESSED;
+ if (current_scenario_page->steps[current_scenario_page->currentStep].long_press_wait
+ > 0) {
+ current_scenario_page->steps[current_scenario_page->currentStep].long_press_wait
+ -= 100;
+ }
+ }
+
+ if ((touchStatePosition->state == RELEASED)
+ || ((current_scenario_page->steps[current_scenario_page->currentStep].objTouchId
+ == LONG_PRESS_BUTTON_ID)
+ && (current_scenario_page->steps[current_scenario_page->currentStep].long_press_wait
+ == 0))) {
+ if (!get_next_page(
+ "TOUCH",
+ getControlName(
+ current_scenario_page->steps[current_scenario_page->currentStep].objTouchId,
+ current_scenario_page->steps[current_scenario_page->currentStep]
+ .objTouchSubId))) {
+ return -1;
+ }
+ }
+ return 0;
+ }
+ else {
+ return ret;
+ }
+}
+#else // HAVE_SE_TOUCH
+
+int scenario_get_keys(uint8_t *state, uint8_t previousState)
+{
+ // printf("scenario_get_keys: %s, %d/%d, batt_level =
+ // %d%%\n",current_scenario_page->name,current_scenario_page->currentStep,current_scenario_page->nbSteps,current_scenario_page->steps[current_scenario_page->currentStep].batt_level);
+ *state = previousState;
+
+ int ret = scenario_get_action();
+ // if not consumed by non-key action, it's a key action
+ if (ret == 2) {
+ if (current_scenario_page->steps[current_scenario_page->currentStep].forced_key_press
+ == 0) {
+ *state
+ = (previousState != 0)
+ ? 0
+ : current_scenario_page->steps[current_scenario_page->currentStep].keyState;
+ if ((*state == 0) && (!get_next_page("KEY", ""))) {
+ return -1;
+ }
+ }
+ else if (current_scenario_page->steps[current_scenario_page->currentStep].forced_key_press
+ == 1) { // release
+ *state = 0;
+ if (!get_next_page("KEY", "")) {
+ return -1;
+ }
+ }
+ else if (current_scenario_page->steps[current_scenario_page->currentStep].forced_key_press
+ == 2) { // press
+ *state = current_scenario_page->steps[current_scenario_page->currentStep].keyState;
+ if (!get_next_page("KEY", "")) {
+ return -1;
+ }
+ }
+
+ return 0;
+ }
+ else if (ret == 3) {
+ // navigation across the sub-pages of a string, for Nano
+ *state = (previousState != 0) ? 0 : BUTTON_RIGHT;
+ return 0;
+ }
+ else {
+ return ret;
+ }
+}
+#endif // HAVE_SE_TOUCH
+
+int scenario_save_screen(char *framebuffer, nbgl_area_t *area)
+{
+ if (current_scenario_page != NULL) {
+ char fullPath[256];
+ if (verbose) {
+ fprintf(stdout,
+ "scenario_name=%s, current_scenario_page->name=%s, area->width=%d, "
+ "area->height=%d\n",
+ scenario_name,
+ current_scenario_page->name,
+ area->width,
+ area->height);
+ }
+ if (current_scenario_page->name) {
+ if (current_scenario_page->cur_sub_page < current_scenario_page->nb_sub_pages) {
+ current_scenario_page->cur_sub_page++;
+ }
+ snprintf(fullPath,
+ sizeof(fullPath),
+#ifdef SCREEN_SIZE_WALLET
+ "%s/%s/%s.png",
+#else // SCREEN_SIZE_WALLET
+ "%s/%s/%s.%d.png",
+#endif // SCREEN_SIZE_WALLET
+ dirName,
+ scenario_name,
+ current_scenario_page->name
+#ifdef SCREEN_SIZE_NANO
+ ,
+ current_scenario_page->cur_sub_page
+#endif // SCREEN_SIZE_NANO
+ );
+ if (framebuffer && verbose) {
+ fprintf(stdout, "(saving %s)\n", fullPath);
+ }
+#ifndef NO_SCREENSHOTS_PNG
+ save_png((char *) framebuffer, fullPath, FULL_SCREEN_WIDTH, SCREEN_HEIGHT);
+#endif // NO_SCREENSHOTS_PNG
+ current_scenario_page->saved = true;
+ }
+ // special case when a screen is transient and automatically closed, without user action
+ if ((current_scenario_page->currentStep < current_scenario_page->nbSteps)
+ && (current_scenario_page->steps[current_scenario_page->currentStep].auto_transition)
+ && (current_scenario_page->cur_sub_page == current_scenario_page->nb_sub_pages)) {
+ ScenarioPage_t *cur_page = current_scenario_page;
+ current_scenario_page = scenario_get_page_from_name(
+ current_scenario_page->steps[current_scenario_page->currentStep].name);
+ if (verbose) {
+ printf("\t%s --> %s: AUTOMATIC\n", cur_page->name, current_scenario_page->name);
+ }
+ cur_page->currentStep++;
+ }
+ }
+ return 0;
+}
+
+char *scenario_get_current_page(void)
+{
+ return current_scenario_page->name;
+}
+
+void scenario_save_json(void)
+{
+ char fullPath[256];
+ uint32_t i, j;
+ bool first = true;
+
+ // this is the end of the scenario so reset current_scenario_page to avoid
+ // adding string_ids in this page
+ current_scenario_page = NULL;
+ snprintf(fullPath, sizeof(fullPath), "%s/%s_flow.json", dirName, scenario_name);
+ FILE *fptr = fopen(fullPath, "w");
+ if (!fptr) {
+ fprintf(stderr, "Error creating file %s.\n", fullPath);
+ return;
+ }
+
+ fprintf(fptr, "{\n");
+ fprintf(fptr, "\t\"name\":\"%s\",\n", scenario_name);
+ fprintf(fptr, "\t\"pages\":[\n");
+ for (i = 0; i < nb_pages; i++) {
+ for (uint32_t k = 1; k <= pages[i].nb_sub_pages; k++) {
+ fprintf(fptr, "\t\t{\n");
+#ifdef SCREEN_SIZE_WALLET
+ fprintf(fptr, "\t\t\t\"name\": \"%s\",\n", pages[i].name);
+ fprintf(fptr, "\t\t\t\"image\": \"%s/%s.png\",\n", scenario_name, pages[i].name);
+#else // SCREEN_SIZE_WALLET
+ fprintf(fptr, "\t\t\t\"name\": \"%s.%d\",\n", pages[i].name, k);
+ fprintf(fptr, "\t\t\t\"image\": \"%s/%s.%d.png\",\n", scenario_name, pages[i].name, k);
+#endif // SCREEN_SIZE_WALLET
+ fprintf(fptr, "\t\t\t\"transitions\": [\n");
+#ifdef SCREEN_SIZE_WALLET
+ first = true;
+ for (j = 0; j < pages[i].nbSteps; j++) {
+ PageStep_t *step = &pages[i].steps[j];
+ if (step->objTouchId > 0) {
+ if (first) {
+ first = false;
+ }
+ else {
+ fprintf(fptr, ",");
+ }
+ fprintf(fptr, "\n\t\t\t\t{\"dest_page\":\"%s\",", step->name);
+ fprintf(fptr,
+ "\n\t\t\t\t \"objectId\":\"%s\",",
+ getControlName(step->objTouchId, step->objTouchSubId));
+ fprintf(fptr, "\n\t\t\t\t \"coords\":\"%d,%d\"}", step->x, step->y);
+ }
+ }
+#endif // SCREEN_SIZE_WALLET
+ fprintf(fptr, "\n\t\t\t]\n");
+ fprintf(fptr, "\t\t}");
+ if (k < pages[i].nb_sub_pages) {
+ fprintf(fptr, ",\n");
+ }
+ else {
+ fprintf(fptr, "\n");
+ }
+ }
+
+ if (i < (nb_pages - 1)) {
+ fprintf(fptr, ",\n");
+ }
+ else {
+ fprintf(fptr, "\n");
+ }
+ }
+ fprintf(fptr, "\t]\n}\n");
+ fclose(fptr);
+
+ snprintf(fullPath, sizeof(fullPath), "%s/%s_strings.json", dirName, scenario_name);
+ fptr = fopen(fullPath, "w");
+ fprintf(fptr, "{\n");
+ fprintf(fptr, "\t\"flow\":\"%s\",\n", scenario_name);
+ fclose(fptr);
+}
diff --git a/tests/screenshots/src/main/json_scenario.h b/tests/screenshots/src/main/json_scenario.h
new file mode 100644
index 000000000..7e591622b
--- /dev/null
+++ b/tests/screenshots/src/main/json_scenario.h
@@ -0,0 +1,147 @@
+#pragma once
+#include
+#include
+#include
+#include
+#include
+#include "nbgl_touch.h"
+
+/**********************
+ * DEFINE
+ **********************/
+#ifndef MAX_MATH
+#define MAX_PATH 255
+#endif // MAX_PATH
+
+#define NB_MAX_PAGES 1024
+
+#define NB_MAX_STRINGS_PER_PAGE 20
+
+#define DEFAULT_BATT_LEVEL 100
+#define NB_MAX_PAGE_IDS 256
+
+#ifdef SCREEN_SIZE_WALLET
+#define INVALID_STRING_ID INVALID_LOC_STRING
+#else // SCREEN_SIZE_WALLET
+#define INVALID_STRING_ID INVALID_ID
+#endif // SCREEN_SIZE_WALLET
+
+#define MAX_TEXT_ISSUES (NB_MAX_PAGES * NB_MAX_STRINGS_PER_PAGE)
+
+/**********************
+ * ENUMS
+ **********************/
+
+// Battery events
+enum {
+ NO_BATT_EVENT = 0,
+ CHARGING_ISSUE_EVENT,
+ NO_CHARGING_ISSUE_EVENT,
+ CHARGING_START,
+ CHARGING_TEMP_TOO_LOW,
+ NO_CHARGING_TEMP_TOO_LOW,
+ CHARGING_TEMP_TOO_HIGH,
+ NO_CHARGING_TEMP_TOO_HIGH,
+ BATT_TEMP_TOO_CRITICAL
+};
+
+// Application events
+enum {
+ BTC_SIGN = 0,
+ CARDANO_SIGN,
+ STELLAR_SIGN,
+ STELLAR_STREAM_SIGN,
+ BTC_VERIFY_ADDR,
+ CARDANO_VERIFY_ADDR,
+ BTC_OPEN,
+ ETH_OPEN,
+ MONERO_OPEN,
+ ETH_MESSAGE_SKIP,
+ ETH_SIGN,
+ RECOV_OPEN,
+ DOGE_SIGN,
+};
+
+/**********************
+ * TYPEDEF
+ **********************/
+
+typedef struct {
+ const char *name;
+ uint8_t id;
+} Event_t;
+
+// list of page indexes (in pages[] variable) for a given string Id (in BOLOS_UX_LOC_STRINGS enum)
+typedef struct {
+ uint32_t nbPages;
+ uint32_t Ids[NB_MAX_PAGE_IDS];
+} PageIds_t;
+
+typedef struct {
+#ifdef HAVE_SE_TOUCH
+ uint16_t x;
+ uint16_t y;
+ uint8_t objTouchId;
+ char objTouchSubId;
+#else // HAVE_SE_TOUCH
+ uint8_t keyState;
+#endif // HAVE_SE_TOUCH
+ char *name;
+ uint16_t wait;
+ uint16_t wait_initial;
+ uint8_t power_press;
+ uint8_t forced_key_press;
+ uint8_t ble_event_idx;
+ uint8_t batt_level;
+ bool auto_transition;
+ bool reset; // if true, reset the counter of parent
+ uint8_t batt_event_idx;
+ uint8_t ux_event_idx;
+ uint8_t app_event_idx;
+ uint8_t seed_algorithm;
+ uint32_t param;
+ uint32_t os_flags;
+ uint16_t long_press_wait;
+} PageStep_t;
+
+typedef struct {
+ void *next_issue;
+ char *name; // name of this scenario page
+ bool optional; // if set to true, it means it's a page only existing for some langs, so saving
+ // is not mandatory
+ uint32_t nbSteps; // number of steps for this scenario page
+ PageStep_t *steps; // dynamic array of steps for this scenario page
+ uint32_t currentStep; // current step in steps[] array
+ bool saved; // set to true once the page is saved (.png file created)
+ uint32_t nb_sub_pages; // for Nanos, when a string is plit on several sub pages
+ uint32_t cur_sub_page;
+} ScenarioPage_t;
+
+typedef struct {
+ uint16_t id;
+ const char *text;
+} StringDigits_t;
+
+/**********************
+ * GLOBAL VARIABLES
+ **********************/
+extern ScenarioPage_t *current_scenario_page;
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+#define store_string_infos(text, font_id, area, wrapping, nb_lines, nb_pages, bold)
+
+uint8_t strxcmp(char *str1, char *str2);
+bool same_string(uint16_t string_id, char *text);
+
+void scenario_parse_args(int argc, char **argv);
+int scenario_parse_json(void);
+int scenario_get_position(nbgl_touchStatePosition_t *touchStatePosition, bool previousState);
+int scenario_get_keys(uint8_t *state, uint8_t previousState);
+int scenario_get_features(void);
+void scenario_save_json(void);
+char *scenario_get_current_page(void);
+
+int save_png(char *framebuffer, char *fullPath, uint16_t width, uint16_t height);
+int scenario_save_screen(char *framebuffer, nbgl_area_t *area);
diff --git a/tests/screenshots/src/main/main.c b/tests/screenshots/src/main/main.c
new file mode 100644
index 000000000..1f1d18e8e
--- /dev/null
+++ b/tests/screenshots/src/main/main.c
@@ -0,0 +1,160 @@
+
+/**
+ * @file main
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#define _DEFAULT_SOURCE /* needed for usleep() */
+#include
+#include
+#include
+#define SDL_MAIN_HANDLED /*To fix SDL's "undefined reference to WinMain" issue*/
+#include
+#include "properties.h"
+#include "apps_api.h"
+#include "glyphs.h"
+#include "os_settings.h"
+#include "os_helpers.h"
+#include "json_scenario.h"
+#include "app_icons.h"
+#include "nbgl_debug.h"
+#include "nbgl_driver.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * VARIABLES
+ **********************/
+
+extern nbgl_touchStatePosition_t gTouchStatePosition;
+extern uint32_t G_interval_ms;
+
+char *dirName;
+char *propertiesName;
+
+unsigned long gLogger = 0
+ // | (1<= (FULL_SCREEN_WIDTH - SCREEN_WIDTH)) {
+ gTouchStatePosition.x -= FULL_SCREEN_WIDTH - SCREEN_WIDTH;
+ }
+ else {
+ gTouchStatePosition.state = RELEASED;
+ }
+
+ if (gTouchStatePosition.state != previousState) {
+ nbgl_touchHandler(false, &gTouchStatePosition, currentTime);
+ nbgl_refresh();
+ }
+ previousState = gTouchStatePosition.state;
+#else // HAVE_SE_TOUCH
+ if ((previousKeyState != keyState) || (keyState != 0)) {
+ nbgl_buttonsHandler(keyState, currentTime);
+ nbgl_refresh();
+ }
+ previousKeyState = keyState;
+#endif // HAVE_SE_TOUCH
+
+ currentTime += G_interval_ms;
+ }
+
+ return 0;
+}
diff --git a/tests/screenshots/src/main/nbgl_driver.c b/tests/screenshots/src/main/nbgl_driver.c
new file mode 100644
index 000000000..832483ec1
--- /dev/null
+++ b/tests/screenshots/src/main/nbgl_driver.c
@@ -0,0 +1,1114 @@
+
+/**
+ * @file nbgl_low.c
+ * @brief Low-Level driver, to draw elementary forms
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "bolos_target.h"
+#include "nbgl_driver.h"
+#include "nbgl_debug.h"
+#include "nbgl_image_utils.h"
+#ifndef BUILD_SCREENSHOTS
+#include "monitor.h"
+#endif // BUILD_SCREENSHOTS
+#include "uzlib.h"
+#include "os_helpers.h"
+#include "os_screen.h"
+#include "json_scenario.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+typedef struct KeepOutArea_s {
+ uint16_t x_start;
+ uint16_t x_end;
+ uint16_t y_start;
+ uint16_t y_end;
+} KeepOutArea_t;
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+#ifdef SCREEN_SIZE_NANO
+static KeepOutArea_t keepOutArea;
+#endif
+
+/**********************
+ * MACROS
+ **********************/
+#ifdef SCREEN_SIZE_WALLET
+#define CHECK_PARAMS() \
+ { \
+ if (area->height & 0x3) { \
+ LOG_FATAL(LOW_LOGGER, "%s: Bad height %d\n", __FUNCTION__, area->height); \
+ } \
+ }
+#define CHECK_PARAMS_ROTATED() \
+ { \
+ if (area->width & 0x3) { \
+ LOG_FATAL(LOW_LOGGER, "%s: Bad width %d\n", __FUNCTION__, area->width); \
+ } \
+ }
+
+#define IS_NOT_KEEP_OUT(x, y) (true)
+#define IS_IN_SCREEN(x, y) \
+ (((y * FULL_SCREEN_WIDTH + x) >= 0) \
+ && ((y * FULL_SCREEN_WIDTH + x) < (FULL_SCREEN_WIDTH * SCREEN_HEIGHT)))
+#else // SCREEN_SIZE_WALLET
+#define CHECK_PARAMS()
+#define CHECK_PARAMS_ROTATED()
+
+// ensure the pixel is not in the keepout area, and not above height limit
+#define IS_NOT_KEEP_OUT(x, y) \
+ (((x < keepOutArea.x_start) || (x >= keepOutArea.x_end) || (y < keepOutArea.y_start) \
+ || (y >= keepOutArea.y_end)))
+
+#define IS_IN_SCREEN(x, y) \
+ (((y * FULL_SCREEN_WIDTH + x) >= 0) \
+ && ((y * FULL_SCREEN_WIDTH + x) < (FULL_SCREEN_WIDTH * SCREEN_HEIGHT)))
+
+#endif // SCREEN_SIZE_WALLET
+
+// expands a 2BPP color into a 4BPP color
+#define EXPAND_TO_4BPP(__color2bpp) (((__color2bpp) << 2) | (__color2bpp))
+
+/**********************
+ * VARIABLES
+ **********************/
+static uint8_t framebuffer[FULL_SCREEN_WIDTH * SCREEN_HEIGHT];
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**
+ * @brief Draws a plain rectangle with the given parameters
+ *
+ * @param area position, size and color of the rectangle to draw
+ */
+void nbgl_driver_drawRect(nbgl_area_t *area)
+{
+ int16_t x, y;
+ // LOG_DEBUG(LOW_LOGGER,"nbgl_driver_drawRect: x0 = %d, y0=%d, width=%d, height=%d,
+ // color=%d\n",area->x0, area->y0, area->width, area->height, area->backgroundColor);
+ CHECK_PARAMS();
+
+ for (y = MAX(0, area->y0); y < MIN(SCREEN_HEIGHT, area->y0 + area->height); y++) {
+ for (x = area->x0; x < (area->x0 + area->width); x++) {
+ if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) {
+ framebuffer[y * FULL_SCREEN_WIDTH + x] = EXPAND_TO_4BPP(area->backgroundColor);
+ }
+ }
+ }
+}
+
+/**
+ * @brief Draws a horizontal line with the given parameters
+ *
+ * @note height is fixed to 4 pixels, and the mask provides the real thickness of the line
+ *
+ * @param area position, size and color of the line to draw
+ * @param mask bit mask to tell which pixels (in vertical axis) are to be painted in lineColor.
+ * bit[0] is the upper line on 4, and bit[3] is the bottom line
+ * @param lineColor color to be applied to the line
+ */
+void nbgl_driver_drawHorizontalLine(nbgl_area_t *area, uint8_t mask, color_t lineColor)
+{
+ CHECK_PARAMS();
+ if (area->height & 0x3) {
+ LOG_DEBUG(LOW_LOGGER, "Forbidden height: %d\n", area->height);
+ return;
+ }
+ // LOG_DEBUG(LOW_LOGGER,"nbgl_ll_drawHorizontalLine: area->x0 = %d\n",area->x0);
+ if (area->height == 4) {
+ uint8_t bit;
+ for (bit = 0; bit < 4; bit++) {
+ if (((mask >> bit) & 0x1) == 1) {
+ memset(&framebuffer[(area->y0 + bit) * FULL_SCREEN_WIDTH + area->x0],
+ EXPAND_TO_4BPP(lineColor),
+ area->width);
+ }
+ else {
+ memset(&framebuffer[(area->y0 + bit) * FULL_SCREEN_WIDTH + area->x0],
+ EXPAND_TO_4BPP(area->backgroundColor),
+ area->width);
+ }
+ }
+ }
+}
+
+/**
+ * @brief Draws the given 1 BPP bitmap with the given parameters
+ *
+ * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap
+ *
+ * @param area position, size and color of the bitmap to draw
+ * @param buffer bitmap buffer
+ * @param foreColor color to be applied to the 1's in bitmap
+ */
+static void nbgl_driver_draw1BPPImage(nbgl_area_t *area, uint8_t *buffer, color_t foreColor)
+{
+ int16_t x, y;
+ uint8_t shift = 0;
+ uint8_t *end = buffer + ((area->width * area->height + 7) / 8);
+ CHECK_PARAMS();
+ x = area->x0 + area->width - 1;
+ y = area->y0;
+ while (buffer < end) {
+ uint8_t pixel_val = (*buffer >> (7 - shift)) & 0x1;
+ if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) {
+ framebuffer[y * FULL_SCREEN_WIDTH + x]
+ = EXPAND_TO_4BPP(pixel_val ? foreColor : area->backgroundColor);
+ }
+
+ shift++;
+ if (shift == 8) {
+ shift = 0;
+ buffer++;
+ }
+ if (y < (area->y0 + area->height - 1)) {
+ y++;
+ }
+ else {
+ y = area->y0;
+ if (x == area->x0) {
+ return;
+ }
+ x--;
+ }
+ }
+}
+
+/**
+ * @brief Draws the given 1 BPP bitmap with the given parameters, but reading pixels from right to
+ * left
+ *
+ * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap
+ *
+ * @param area position, size and color of the bitmap to draw
+ * @param buffer bitmap buffer
+ * @param foreColor color to be applied to the 1's in bitmap
+ */
+static void nbgl_driver_draw1BPPImageVerticalMirror(nbgl_area_t *area,
+ uint8_t *buffer,
+ color_t foreColor)
+{
+ int16_t x, y;
+ uint8_t shift = 0;
+ uint8_t *end = buffer + (area->width * area->height / 8);
+ CHECK_PARAMS();
+ x = area->x0;
+ y = area->y0;
+ while (buffer < end) {
+ uint8_t pixel_val = (*buffer >> (7 - shift)) & 0x1;
+ if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) {
+ framebuffer[y * FULL_SCREEN_WIDTH + x]
+ = EXPAND_TO_4BPP(pixel_val ? foreColor : area->backgroundColor);
+ }
+ shift++;
+ if (shift == 8) {
+ shift = 0;
+ buffer++;
+ }
+ if (y < (area->y0 + area->height - 1)) {
+ y++;
+ }
+ else {
+ y = area->y0;
+ x++;
+ }
+ }
+}
+
+/**
+ * @brief Draws the given 1 BPP bitmap with the given parameters, but reading pixels from top right
+ * to bottom left (rotation of 90 degrees, clockwise)
+ *
+ * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap
+ *
+ * @param area position, size and color of the bitmap to draw
+ * @param buffer bitmap buffer
+ * @param foreColor color to be applied to the 1's in bitmap
+ */
+static void nbgl_driver_draw1BPPImageRotate90(nbgl_area_t *area, uint8_t *buffer, color_t foreColor)
+{
+ int16_t x, y;
+ uint8_t shift = 0;
+ uint8_t *end = buffer + (area->width * area->height / 8);
+ CHECK_PARAMS_ROTATED();
+
+ x = area->x0 + area->height - 1;
+ y = area->y0 + area->width - 1;
+ while (buffer < end) {
+ uint8_t pixel_val = (*buffer >> (7 - shift)) & 0x1;
+ if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) {
+ framebuffer[y * FULL_SCREEN_WIDTH + x]
+ = EXPAND_TO_4BPP(pixel_val ? foreColor : area->backgroundColor);
+ }
+
+ shift++;
+ if (shift == 8) {
+ shift = 0;
+ buffer++;
+ }
+ if (x > area->x0) {
+ x--;
+ }
+ else {
+ x = area->x0 + area->height - 1;
+ y--;
+ }
+ }
+}
+
+/**
+ * @brief Uncompress a 1BPP RLE-encoded glyph
+ *
+ * 1BPP RLE Encoder:
+ *
+ * compressed bytes contains ZZZZOOOO nibbles, with
+ * - ZZZZ: number of consecutives zeros (from 0 to 15)
+ * - OOOO: number of consecutives ones (from 0 to 15)
+ *
+ * @param area
+ * @param buffer buffer of RLE-encoded data
+ * @param buffer_len length of buffer
+ */
+static void nbgl_driver_draw1BPPImageRle(nbgl_area_t *area,
+ uint8_t *buffer,
+ uint32_t buffer_len,
+ color_t foreColor,
+ uint8_t nb_skipped_bytes)
+{
+ int16_t x, y;
+ uint8_t pixel;
+ size_t index = 0;
+ // Set the initial number of transparent pixels
+ size_t nb_zeros = (size_t) nb_skipped_bytes * 8;
+ size_t nb_ones = 0;
+ size_t remaining_pixels = area->width * area->height;
+
+ CHECK_PARAMS();
+ x = area->x0 + area->width - 1;
+ y = area->y0;
+ while (remaining_pixels && (index < buffer_len || nb_zeros || nb_ones)) {
+ // Reload nb_zeros & nb_ones if needed
+ while (!nb_zeros && !nb_ones && index < buffer_len) {
+ uint8_t byte = buffer[index++];
+ nb_ones = byte & 0x0F;
+ nb_zeros = byte >> 4;
+ }
+ // Get next pixel
+ if (nb_zeros) {
+ --nb_zeros;
+ pixel = 0;
+ }
+ else if (nb_ones) {
+ --nb_ones;
+ pixel = 1;
+ }
+ --remaining_pixels;
+
+ if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) {
+ framebuffer[y * FULL_SCREEN_WIDTH + x]
+ = EXPAND_TO_4BPP(pixel ? foreColor : area->backgroundColor);
+ }
+ if (y < (area->y0 + area->height - 1)) {
+ y++;
+ }
+ else {
+ y = area->y0;
+ x--;
+ }
+ }
+}
+
+/**
+ * @brief Draws the given 2 BPP bitmap with the given parameters. The colorMap is applied if not
+ * null.
+ *
+ * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap.
+ *
+ * @param area position, size and color of the bitmap to draw
+ * @param buffer bitmap buffer
+ * @param colorMap color map to be applied on given 2BPP pixels, that may be different from desired
+ * colors
+ */
+static void nbgl_driver_draw2BPPImage(nbgl_area_t *area, uint8_t *buffer, nbgl_color_map_t colorMap)
+{
+ int16_t x, y;
+ uint8_t shift = 0;
+ uint8_t *end = buffer + (area->width * area->height / 4);
+ CHECK_PARAMS();
+ x = area->x0 + area->width - 1;
+ y = area->y0;
+ while (buffer < end) {
+ uint8_t pixel_val = (*buffer >> (6 - shift)) & 0x3;
+ if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) {
+ if (colorMap == INVALID_COLOR_MAP) {
+ framebuffer[y * FULL_SCREEN_WIDTH + x] = EXPAND_TO_4BPP(pixel_val);
+ }
+ else {
+ framebuffer[y * FULL_SCREEN_WIDTH + x]
+ = EXPAND_TO_4BPP(GET_COLOR_MAP(colorMap, pixel_val));
+ }
+ }
+ shift += 2;
+ if (shift == 8) {
+ shift = 0;
+ buffer++;
+ }
+ if (y < (area->y0 + area->height - 1)) {
+ y++;
+ }
+ else {
+ y = area->y0;
+ x--;
+ }
+ }
+}
+
+/**
+ * @brief Draws the given 2 BPP bitmap with the given parameters, but from right to left. The
+ * colorMap is applied if not null.
+ *
+ * @note x0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap.
+ *
+ * @param area position, size and color of the bitmap to draw
+ * @param buffer bitmap buffer
+ * @param colorMap color map to be applied on given 2BPP pixels, that may be different from desired
+ * colors
+ */
+static void nbgl_driver_draw2BPPImageVerticalMirror(nbgl_area_t *area,
+ uint8_t *buffer,
+ nbgl_color_map_t colorMap)
+{
+ int16_t x, y;
+ uint8_t shift = 0;
+ uint8_t *end = buffer + (area->width * area->height / 4);
+ CHECK_PARAMS();
+ x = area->x0;
+ y = area->y0;
+ while (buffer < end) {
+ uint8_t pixel_val = (*buffer >> (6 - shift)) & 0x3;
+ if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) {
+ if (colorMap == INVALID_COLOR_MAP) {
+ framebuffer[y * FULL_SCREEN_WIDTH + x] = EXPAND_TO_4BPP(pixel_val);
+ }
+ else {
+ framebuffer[y * FULL_SCREEN_WIDTH + x]
+ = EXPAND_TO_4BPP(GET_COLOR_MAP(colorMap, pixel_val));
+ }
+ }
+ shift += 2;
+ if (shift == 8) {
+ shift = 0;
+ buffer++;
+ }
+ if (y < (area->y0 + area->height - 1)) {
+ y++;
+ }
+ else {
+ y = area->y0;
+ x++;
+ }
+ }
+}
+
+// 4BPP Color maps
+
+// Extract a color from color_map, if color_map is not NULL
+#define GET_MAPPED_COLOR(color_map, color_index) \
+ ((color_map == NULL) ? (color_index) : color_map[(color_index)])
+
+// Return the 4BPP value corresponding to input color
+static uint8_t get_4bpp_value(color_t color)
+{
+ switch (color) {
+ case BLACK:
+ return BLACK_4BPP;
+ case WHITE:
+ return WHITE_4BPP;
+ case LIGHT_GRAY:
+ return LIGHT_GRAY_4BPP;
+ case DARK_GRAY:
+ return DARK_GRAY_4BPP;
+ default:
+ return WHITE_4BPP;
+ }
+}
+
+// Size of 4BPP color maps
+#define COLOR_MAP_SIZE_4BPP 16
+
+// Compute a default color map, starting from input color to area background color.
+// The values are evenly distributed whenever possible.
+// The map is written to output_map.
+static void compute_default_color_map(uint8_t output_map[COLOR_MAP_SIZE_4BPP],
+ color_t color,
+ nbgl_area_t *area)
+{
+ uint8_t start_val = get_4bpp_value(color);
+ uint8_t end_val = get_4bpp_value(area->backgroundColor);
+
+ int nb_values; // Number of different values to be written in the output map
+ int8_t incr;
+ if (end_val >= start_val) {
+ nb_values = (end_val - start_val + 1);
+ incr = -1;
+ }
+ else {
+ nb_values = (start_val - end_val + 1);
+ incr = 1;
+ }
+
+ // Whenever (cnt << 8) reaches next_step value
+ // we increment the value written in the output map.
+ uint32_t cnt_step = ((COLOR_MAP_SIZE_4BPP << 8) / nb_values);
+ uint32_t next_step = cnt_step;
+
+ // The output map is filled from the end to the start
+ output_map[COLOR_MAP_SIZE_4BPP - 1] = end_val;
+ for (uint32_t cnt = 1; cnt < COLOR_MAP_SIZE_4BPP; cnt++) {
+ uint8_t arr_index = COLOR_MAP_SIZE_4BPP - 1 - cnt;
+ if ((cnt << 8) >= next_step) {
+ next_step += cnt_step;
+ output_map[arr_index] = output_map[arr_index + 1] + incr;
+ }
+ else {
+ output_map[arr_index] = output_map[arr_index + 1];
+ }
+ }
+}
+
+// These hardcoded color maps have been manually tuned with the designer.
+
+static const uint8_t WHITE_ON_BLACK[]
+ = {WHITE_4BPP, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, BLACK_4BPP};
+
+static const uint8_t DARK_GRAY_ON_WHITE[COLOR_MAP_SIZE_4BPP]
+ = {DARK_GRAY_4BPP, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 11, 12, 13, 14, WHITE_4BPP};
+
+static const uint8_t LIGHT_GRAY_ON_WHITE[COLOR_MAP_SIZE_4BPP]
+ = {LIGHT_GRAY_4BPP, 10, 11, 11, 12, 12, 13, 13, 13, 14, 14, 14, 15, 15, 15, WHITE_4BPP};
+
+static const uint8_t LIGHT_GRAY_ON_BLACK[COLOR_MAP_SIZE_4BPP]
+ = {LIGHT_GRAY_4BPP, 9, 8, 7, 6, 5, 4, 4, 3, 3, 2, 2, 1, 1, 0, BLACK_4BPP};
+
+static const uint8_t *DARK_GRAY_ON_BLACK = LIGHT_GRAY_ON_BLACK;
+
+// Buffer where non-hardcoded color maps are computed
+
+static uint8_t default_color_map[16];
+
+// Return a pointer to the color map corresponding to input area and color
+static const uint8_t *get_color_map_array(nbgl_area_t *area, color_t color)
+{
+ if ((color == BLACK) && (area->backgroundColor == WHITE)) {
+ return NULL; // No color map to apply
+ }
+ else if ((color == WHITE) && (area->backgroundColor == BLACK)) {
+ return WHITE_ON_BLACK;
+ }
+ else if ((color == DARK_GRAY) && (area->backgroundColor == WHITE)) {
+ return DARK_GRAY_ON_WHITE;
+ }
+ else if ((color == LIGHT_GRAY) && (area->backgroundColor == WHITE)) {
+ return LIGHT_GRAY_ON_WHITE;
+ }
+ else if ((color == LIGHT_GRAY) && (area->backgroundColor == BLACK)) {
+ return LIGHT_GRAY_ON_BLACK;
+ }
+ else if ((color == DARK_GRAY) && (area->backgroundColor == BLACK)) {
+ return DARK_GRAY_ON_BLACK;
+ }
+
+ compute_default_color_map(default_color_map, color, area);
+ return default_color_map;
+}
+
+/**
+ * @brief Draws the given 4 BPP bitmap with the given parameters. The colorMap is applied if not
+ * null.
+ *
+ * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap.
+ *
+ * @param area position, size and color of the bitmap to draw
+ * @param buffer bitmap buffer
+ */
+static void nbgl_driver_draw4BPPImage(nbgl_area_t *area, uint8_t *buffer, color_t color)
+{
+ int16_t x, y;
+ uint8_t shift = 0;
+ uint8_t *end = buffer + (area->width * area->height / 2);
+ CHECK_PARAMS();
+ x = area->x0 + area->width - 1;
+ y = area->y0;
+ const uint8_t *color_map = get_color_map_array(area, color);
+ while (buffer < end) {
+ uint8_t color_index = ((*buffer) >> (4 - shift)) & 0x0F;
+ uint8_t pixel_val = GET_MAPPED_COLOR(color_map, color_index);
+
+ if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) {
+ framebuffer[y * FULL_SCREEN_WIDTH + x] = pixel_val;
+ }
+
+ shift += 4;
+ if (shift == 8) {
+ shift = 0;
+ buffer++;
+ }
+ if (y < (area->y0 + area->height - 1)) {
+ y++;
+ }
+ else {
+ y = area->y0;
+ x--;
+ }
+ }
+}
+
+/**
+ * @brief Draws the given 1 BPP compressed bitmap with the given parameters. The colorMap is applied
+ * if not null.
+ *
+ * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap.
+ *
+ * @param area position, size and color of the bitmap to draw
+ * @param buffer bitmap buffer
+ */
+static void nbgl_ll_draw1BPPCompressedImage(nbgl_area_t *area,
+ uint8_t *buffer,
+ uint32_t bufferLen,
+ uint8_t *uzlibChunkBuffer,
+ color_t foreColor)
+{
+ int16_t x, y;
+ uint8_t shift = 0;
+ unsigned int dlen;
+ struct uzlib_uncomp d;
+ int res;
+ CHECK_PARAMS();
+ x = area->x0 + area->width - 1;
+ y = area->y0;
+
+ uzlib_init();
+ while (bufferLen > 0) {
+ uint16_t compressed_chunk_len;
+
+ // read length of compressed chunk on 2 bytes
+ compressed_chunk_len = 256 * buffer[1] + buffer[0];
+ buffer += 2;
+ /* -- get decompressed length -- */
+ dlen = buffer[compressed_chunk_len - 1];
+ dlen = 256 * dlen + buffer[compressed_chunk_len - 2];
+ dlen = 256 * dlen + buffer[compressed_chunk_len - 3];
+ dlen = 256 * dlen + buffer[compressed_chunk_len - 4];
+ bufferLen -= compressed_chunk_len + 2;
+
+ /* there can be mismatch between length in the trailer and actual
+ data stream; to avoid buffer overruns on overlong streams, reserve
+ one extra byte */
+ dlen++;
+
+ /* -- decompress data -- */
+ uzlib_uncompress_init(&d, NULL, 0);
+
+ /* all 3 fields below must be initialized by user */
+ d.source = buffer;
+ d.source_limit = buffer + compressed_chunk_len - 4;
+ d.source_read_cb = NULL;
+
+ // position buffer to the end of the current compressed chunk
+ buffer += compressed_chunk_len;
+
+ res = uzlib_gzip_parse_header(&d);
+ if (res != TINF_OK) {
+ printf("Error parsing header: %d\n", res);
+ exit(1);
+ }
+
+ d.dest_start = d.dest = uzlibChunkBuffer;
+
+ d.dest_limit = d.dest + dlen;
+ res = uzlib_uncompress_chksum(&d);
+ if (res != TINF_DONE) {
+ printf("error: %d\n", res);
+ break;
+ }
+ unsigned int i = 0;
+
+ // write pixels
+ while (i < (dlen - 1)) {
+ uint8_t pixel_val = (uzlibChunkBuffer[i] >> (7 - shift)) & 0x1;
+ if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) {
+ framebuffer[y * FULL_SCREEN_WIDTH + x]
+ = EXPAND_TO_4BPP(pixel_val ? foreColor : area->backgroundColor);
+ }
+ shift++;
+ if (shift == 8) {
+ shift = 0;
+ i++;
+ }
+ if (y < (area->y0 + area->height - 1)) {
+ y++;
+ }
+ else {
+ y = area->y0;
+ x--;
+ }
+ }
+ }
+}
+
+/**
+ * @brief Draws the given 4 BPP compressed bitmap with the given parameters. The colorMap is applied
+ * if not null.
+ *
+ * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in bitmap.
+ *
+ * @param area position, size and color of the bitmap to draw
+ * @param buffer bitmap buffer
+ */
+static void nbgl_ll_draw4BPPCompressedImage(nbgl_area_t *area,
+ uint8_t *buffer,
+ uint32_t bufferLen,
+ uint8_t *uzlibChunkBuffer,
+ color_t foreColor)
+{
+ int16_t x, y;
+ uint8_t shift = 0;
+ unsigned int dlen;
+ struct uzlib_uncomp d;
+ int res;
+ CHECK_PARAMS();
+ x = area->x0 + area->width - 1;
+ y = area->y0;
+ const uint8_t *color_map = get_color_map_array(area, foreColor);
+
+ uzlib_init();
+ while (bufferLen > 0) {
+ uint16_t compressed_chunk_len;
+
+ // read length of compressed chunk on 2 bytes
+ compressed_chunk_len = 256 * buffer[1] + buffer[0];
+ buffer += 2;
+ /* -- get decompressed length -- */
+ dlen = buffer[compressed_chunk_len - 1];
+ dlen = 256 * dlen + buffer[compressed_chunk_len - 2];
+ dlen = 256 * dlen + buffer[compressed_chunk_len - 3];
+ dlen = 256 * dlen + buffer[compressed_chunk_len - 4];
+ bufferLen -= compressed_chunk_len + 2;
+
+ /* there can be mismatch between length in the trailer and actual
+ data stream; to avoid buffer overruns on overlong streams, reserve
+ one extra byte */
+ dlen++;
+
+ /* -- decompress data -- */
+ uzlib_uncompress_init(&d, NULL, 0);
+
+ /* all 3 fields below must be initialized by user */
+ d.source = buffer;
+ d.source_limit = buffer + compressed_chunk_len - 4;
+ d.source_read_cb = NULL;
+
+ // position buffer to the end of the current compressed chunk
+ buffer += compressed_chunk_len;
+
+ res = uzlib_gzip_parse_header(&d);
+ if (res != TINF_OK) {
+ printf("Error parsing header: %d\n", res);
+ exit(1);
+ }
+
+ d.dest_start = d.dest = uzlibChunkBuffer;
+
+ d.dest_limit = d.dest + dlen;
+ res = uzlib_uncompress_chksum(&d);
+ if (res != TINF_DONE) {
+ printf("error: %d\n", res);
+ break;
+ }
+ unsigned int i = 0;
+ // write pixels
+ while (i < (dlen - 1)) {
+ uint8_t pixel_val = (uzlibChunkBuffer[i] >> (4 - shift)) & 0xF;
+ if (IS_IN_SCREEN(x, y) && IS_NOT_KEEP_OUT(x, y)) {
+ framebuffer[y * FULL_SCREEN_WIDTH + x] = GET_MAPPED_COLOR(color_map, pixel_val);
+ }
+ shift += 4;
+ if (shift == 8) {
+ shift = 0;
+ i++;
+ }
+ if (y < (area->y0 + area->height - 1)) {
+ y++;
+ }
+ else {
+ y = area->y0;
+ x--;
+ }
+ }
+ }
+}
+
+/**
+ * @brief Draws the given given bitmap with the given parameters. The colorMap is applied on 2BPP
+ * image if not @ref INVALID_COLOR_MAP.
+ *
+ * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in 1BPP
+ * bitmap.
+ *
+ * @param area position, size and color of the bitmap to draw
+ * @param buffer bitmap buffer
+ * @param transformation transformatin to be applied. This is a bit field, filled with @ref
+ * VERTICAL_MIRROR for example
+ * @param colorMap color map to be applied on given image, for 2 BPP image. For 1BPP, only the
+ * color[0] is used, for foreground color
+ */
+void nbgl_driver_drawImage(nbgl_area_t *area,
+ uint8_t *buffer,
+ nbgl_transformation_t transformation,
+ nbgl_color_map_t colorMap)
+{
+ if (area->bpp == NBGL_BPP_1) {
+ if (transformation == NO_TRANSFORMATION) {
+ nbgl_driver_draw1BPPImage(area, buffer, GET_COLOR_MAP(colorMap, 0));
+ }
+ else if (transformation == VERTICAL_MIRROR) {
+ nbgl_driver_draw1BPPImageVerticalMirror(area, buffer, GET_COLOR_MAP(colorMap, 0));
+ }
+ else if (transformation == ROTATE_90_CLOCKWISE) {
+ nbgl_driver_draw1BPPImageRotate90(area, buffer, GET_COLOR_MAP(colorMap, 0));
+ }
+ }
+ else if (area->bpp == NBGL_BPP_2) {
+ if (transformation == NO_TRANSFORMATION) {
+ nbgl_driver_draw2BPPImage(area, buffer, colorMap);
+ }
+ else if (transformation == VERTICAL_MIRROR) {
+ nbgl_driver_draw2BPPImageVerticalMirror(area, buffer, colorMap);
+ }
+ }
+ else if (area->bpp == NBGL_BPP_4) {
+ nbgl_driver_draw4BPPImage(area, buffer, colorMap);
+ }
+ else {
+ LOG_FATAL(LOW_LOGGER, "Forbidden bpp: %d\n", area->bpp);
+ }
+}
+
+/////// RLE functions
+
+uint8_t rle_4bpp_uncompress_buffer[SCREEN_WIDTH * SCREEN_HEIGHT / 2];
+
+// Write nb_pix 4BPP pixels of the same color to the display
+static inline void fill_4bpp_pixels_color(uint8_t color,
+ uint8_t nb_pix,
+ uint32_t *pix_cnt,
+ uint8_t *remaining,
+ uint32_t max_pix_cnt)
+{
+ // Check max
+ if ((*pix_cnt + nb_pix) > max_pix_cnt) {
+ return;
+ }
+
+ uint8_t double_pix = (color << 4) | color;
+ bool first_non_aligned = (*pix_cnt % 2);
+ bool last_non_aligned = (*pix_cnt + nb_pix) % 2;
+
+ // If first pixel to write is non aligned
+ if (first_non_aligned) {
+ *remaining |= (0x0F & color);
+ // Remaining byte is now full: send it
+ // spi_queue_byte(*remaining);
+ rle_4bpp_uncompress_buffer[(*pix_cnt) / 2] = *remaining;
+ *remaining = 0;
+ (*pix_cnt)++;
+ nb_pix--;
+ }
+
+ // Write pixels 2 by 2
+ for (uint32_t i = 0; i < nb_pix / 2; i++) {
+ rle_4bpp_uncompress_buffer[(*pix_cnt) / 2] = double_pix;
+ (*pix_cnt) += 2;
+ }
+
+ // If last pixel to write is non aligned
+ if (last_non_aligned) {
+ // Save remaining pixel
+ *remaining = color << 4;
+ (*pix_cnt)++;
+ }
+}
+
+// Handle 'Copy white' RLE instruction
+static uint32_t handle_4bpp_repeat_white(uint8_t byte_in,
+ uint32_t *pix_cnt,
+ const uint8_t *color_map,
+ uint8_t *remaining_byte,
+ uint32_t max_pix_cnt)
+{
+ uint8_t nb_pix = (byte_in & 0x3F) + 1;
+ uint8_t color = GET_MAPPED_COLOR(color_map, 0x0F);
+ fill_4bpp_pixels_color(color, nb_pix, pix_cnt, remaining_byte, max_pix_cnt);
+ return 1;
+}
+
+// Handle 'Repeat color' RLE instruction
+static uint32_t handle_4bpp_repeat_color(uint8_t byte_in,
+ uint32_t *pix_cnt,
+ const uint8_t *color_map,
+ uint8_t *remaining_byte,
+ uint32_t max_pix_cnt)
+{
+ uint8_t nb_pix = ((byte_in & 0x70) >> 4) + 1;
+ uint8_t color = GET_MAPPED_COLOR(color_map, byte_in & 0x0F);
+ fill_4bpp_pixels_color(color, nb_pix, pix_cnt, remaining_byte, max_pix_cnt);
+ return 1;
+}
+
+// Handle 'Copy' RLE instruction
+static uint32_t handle_4bpp_copy(uint8_t *bytes_in,
+ uint32_t bytes_in_len,
+ uint32_t *pix_cnt,
+ const uint8_t *color_map,
+ uint8_t *remaining_byte,
+ uint32_t max_pix_cnt)
+{
+ uint8_t nb_pix = ((bytes_in[0] & 0x30) >> 4) + 3;
+ uint8_t nb_bytes_read = (nb_pix / 2) + 1;
+
+ // Do not read outside of bytes_in
+ if (nb_bytes_read > bytes_in_len) {
+ return nb_bytes_read;
+ }
+
+ for (uint8_t i = 0; i < nb_pix; i++) {
+ // Write pix by pix
+ uint8_t index = (i + 1) / 2;
+ uint8_t color_index;
+ if ((i % 2) == 0) {
+ color_index = bytes_in[index] & 0x0F;
+ }
+ else {
+ color_index = bytes_in[index] >> 4;
+ }
+ uint8_t color = GET_MAPPED_COLOR(color_map, color_index);
+ fill_4bpp_pixels_color(color, 1, pix_cnt, remaining_byte, max_pix_cnt);
+ }
+
+ return nb_bytes_read;
+}
+
+/**
+ * @brief Uncompress a 4BPP RLE-encoded glyph
+ *
+ * 4BPP RLE Encoder:
+ *
+ * 'Repeat white' byte
+ * - [11][number of whites to write - 1 (6 bits)]
+ *
+ * 'Copy' byte
+ * - [10][number of nibbles to copy - 3 (2)][nib0 (4 bits)][nib1 (4 bits)]...
+ *
+ * 'Repeat color' byte
+ * - [0][number of bytes to write - 1 (3 bits)][color index (4 bits)]
+ *
+ * @param area
+ * @param buffer buffer of RLE-encoded data
+ * @param len length of buffer
+ */
+static void nbgl_driver_draw4BPPImageRle(nbgl_area_t *area,
+ uint8_t *buffer,
+ uint32_t buffer_len,
+ color_t fore_color,
+ uint8_t nb_skipped_bytes)
+{
+ CHECK_PARAMS();
+
+ if (area->bpp != NBGL_BPP_4) {
+ return;
+ }
+
+ const uint8_t *color_map = get_color_map_array(area, fore_color);
+ // Fill buffer with background color
+ uint8_t background = GET_MAPPED_COLOR(color_map, 0xF);
+ background |= background << 4;
+ memset(rle_4bpp_uncompress_buffer, background, sizeof(rle_4bpp_uncompress_buffer));
+ uint32_t max_pix_cnt = (area->width * area->height);
+ // 'transparent' bytes was set (with memset), not need to write them again
+ uint32_t pix_cnt = nb_skipped_bytes * 2;
+ uint32_t read_cnt = 0;
+ uint8_t remaining = 0;
+
+ while (read_cnt < buffer_len) {
+ uint8_t byte = buffer[read_cnt];
+ if (byte & 0x80) {
+ if (byte & 0x40) {
+ read_cnt
+ += handle_4bpp_repeat_white(byte, &pix_cnt, color_map, &remaining, max_pix_cnt);
+ }
+ else {
+ uint8_t *bytes_in = buffer + read_cnt;
+ uint32_t max_len = buffer_len - read_cnt;
+ read_cnt += handle_4bpp_copy(
+ bytes_in, max_len, &pix_cnt, color_map, &remaining, max_pix_cnt);
+ }
+ }
+ else {
+ read_cnt
+ += handle_4bpp_repeat_color(byte, &pix_cnt, color_map, &remaining, max_pix_cnt);
+ }
+ }
+
+ if ((pix_cnt % 2) != 0) {
+ rle_4bpp_uncompress_buffer[(pix_cnt) / 2] = remaining;
+ }
+
+ // Now send it as if it was a 4BPP uncompressed image
+ color_t background_color = area->backgroundColor;
+ area->backgroundColor = WHITE;
+ nbgl_driver_draw4BPPImage(area, rle_4bpp_uncompress_buffer, BLACK);
+ area->backgroundColor = background_color;
+}
+
+/**
+ * @brief Draws the given bitmap file with the given parameters. The colorMap is applied on 2BPP
+ * image if not @ref INVALID_COLOR_MAP.
+ *
+ * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in 1BPP
+ * bitmap.
+ *
+ * @param area position and background color of the bitmap to draw
+ * @param buffer bitmap file buffer, with ledger format
+ * @param colorMap color map to be applied on given image, for 2 BPP image. For 1BPP, only the
+ * color[0] is used, for foreground color
+ * @param uzlib_chunk_buffer Work buffer of size GZLIB_UNCOMPRESSED_CHUNK
+ */
+void nbgl_driver_drawImageFile(nbgl_area_t *area,
+ uint8_t *buffer,
+ nbgl_color_map_t colorMap,
+ uint8_t *uzlib_chunk_buffer)
+{
+ // uzlib_chunk_buffer is ignored in simulation
+ uint8_t compression = GET_IMAGE_FILE_COMPRESSION(buffer);
+
+ area->width = GET_IMAGE_FILE_WIDTH(buffer);
+ area->height = GET_IMAGE_FILE_HEIGHT(buffer);
+ area->bpp = GET_IMAGE_FILE_BPP(buffer);
+ if (compression == NBGL_NO_COMPRESSION) {
+ nbgl_driver_drawImage(area, GET_IMAGE_FILE_BUFFER(buffer), NO_TRANSFORMATION, colorMap);
+ }
+ else if (compression == NBGL_GZLIB_COMPRESSION) {
+ // TODO: add support of compression at least for 2BPP
+ if (area->bpp == NBGL_BPP_1) {
+ nbgl_ll_draw1BPPCompressedImage(area,
+ GET_IMAGE_FILE_BUFFER(buffer),
+ GET_IMAGE_FILE_BUFFER_LEN(buffer),
+ uzlib_chunk_buffer,
+ GET_COLOR_MAP(colorMap, 0));
+ }
+ else if (area->bpp == NBGL_BPP_4) {
+ nbgl_ll_draw4BPPCompressedImage(area,
+ GET_IMAGE_FILE_BUFFER(buffer),
+ GET_IMAGE_FILE_BUFFER_LEN(buffer),
+ uzlib_chunk_buffer,
+ (color_t) GET_COLOR_MAP(colorMap, 0));
+ }
+ }
+ else if (compression == NBGL_RLE_COMPRESSION) {
+ nbgl_driver_drawImageRle(area,
+ GET_IMAGE_FILE_BUFFER(buffer),
+ GET_IMAGE_FILE_BUFFER_LEN(buffer),
+ (color_t) GET_COLOR_MAP(colorMap, 0),
+ 0);
+ }
+}
+
+/**
+ * @brief function to actually refresh the given area on screen
+ *
+ * @param area the area to refresh
+ */
+void nbgl_driver_refreshArea(nbgl_area_t *area,
+ nbgl_refresh_mode_t mode,
+ nbgl_post_refresh_t post_refresh)
+{
+ UNUSED(mode);
+ UNUSED(post_refresh);
+ // LOG_WARN(LOW_LOGGER,"nbgl_driver_refreshArea(): x0:%d,y0:%d,width:%d,height:%d\n", area->x0,
+ // area->y0, area->width, area->height);
+ if ((area->width == 0) || (area->height == 0)) {
+ return;
+ }
+ // LOG_DEBUG(LOW_LOGGER,"nbgl_driver_refreshArea(): \n");
+#ifndef BUILD_SCREENSHOTS
+ monitor_flush(area, framebuffer);
+#else // BUILD_SCREENSHOTS
+ scenario_save_screen((char *) framebuffer, area);
+#endif // BUILD_SCREENSHOTS
+}
+
+void nbgl_driver_drawImageRle(nbgl_area_t *area,
+ uint8_t *buffer,
+ uint32_t buffer_len,
+ color_t fore_color,
+ uint8_t nb_skipped_bytes)
+{
+ if (area->bpp == NBGL_BPP_4) {
+ nbgl_driver_draw4BPPImageRle(area, buffer, buffer_len, fore_color, nb_skipped_bytes);
+ }
+ else if (area->bpp == NBGL_BPP_1) {
+ nbgl_driver_draw1BPPImageRle(area, buffer, buffer_len, fore_color, nb_skipped_bytes);
+ }
+}
+
+#ifdef HAVE_SE_SCREEN
+void screen_set_keepout(unsigned int x, unsigned int y, unsigned int width, unsigned int height)
+{
+ if (!width || !height) {
+ keepOutArea.x_start = 0xFFFE;
+ keepOutArea.x_end = 0xFFFF;
+ keepOutArea.y_start = 0xFFFE;
+ keepOutArea.y_end = 0xFFFF;
+ return;
+ }
+
+ if (x > SCREEN_WIDTH) {
+ x = SCREEN_WIDTH;
+ }
+ if (y > SCREEN_HEIGHT) {
+ y = SCREEN_HEIGHT;
+ }
+ if (width > SCREEN_WIDTH) {
+ width = SCREEN_WIDTH;
+ }
+ if (height > SCREEN_HEIGHT) {
+ height = SCREEN_HEIGHT;
+ }
+
+ keepOutArea.x_start = x;
+ keepOutArea.x_end = x + width;
+ keepOutArea.y_start = y;
+ keepOutArea.y_end = y + height;
+}
+#endif // HAVE_SE_SCREEN
diff --git a/tests/screenshots/src/main/nbgl_driver.h b/tests/screenshots/src/main/nbgl_driver.h
new file mode 100644
index 000000000..5ff047a26
--- /dev/null
+++ b/tests/screenshots/src/main/nbgl_driver.h
@@ -0,0 +1,101 @@
+/**
+ * @file nbgl_driver.h
+ * @brief Low-Level driver API, to draw elementary forms
+ *
+ */
+
+#ifndef NBGL_DRIVER_H
+#define NBGL_DRIVER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "bolos_target.h"
+#include "nbgl_types.h"
+
+/*********************
+ * DEFINES
+ *********************/
+/**
+ * Width of the full screen in pixels, including side, front and unusable edge
+ */
+#ifdef TARGET_STAX
+#define FULL_SCREEN_WIDTH 496
+#endif // TARGET_STAX
+#ifdef TARGET_FLEX
+#define FULL_SCREEN_WIDTH 480
+#endif // TARGET_FLEX
+#ifndef SCREEN_SIZE_WALLET
+#define FULL_SCREEN_WIDTH 128
+#endif // SCREEN_SIZE_WALLET
+
+/**
+ * @brief macro to get the color from color_map for the __code__ input
+ * @param color_map u8 representing the color map
+ * @param color input code (from @ref color_t)
+ */
+#define GET_COLOR_MAP(color_map, color) ((color_map >> (color * 2)) & 0x3)
+
+/**
+ * Value on 4BPP of WHITE
+ *
+ */
+#define WHITE_4BPP 0xF
+
+/**
+ * Value on 4BPP of LIGHT_GRAY
+ *
+ */
+#define LIGHT_GRAY_4BPP 0xA
+
+/**
+ * Value on 4BPP of DARK_GRAY
+ *
+ */
+#define DARK_GRAY_4BPP 0x5
+
+/**
+ * Value on 4BPP of BLACK
+ *
+ */
+#define BLACK_4BPP 0x0
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+void nbgl_driver_drawRect(nbgl_area_t *area);
+void nbgl_driver_drawHorizontalLine(nbgl_area_t *area, uint8_t mask, color_t lineColor);
+void nbgl_driver_drawImage(nbgl_area_t *area,
+ uint8_t *buffer,
+ nbgl_transformation_t transformation,
+ nbgl_color_map_t colorMap);
+void nbgl_driver_drawImageFile(nbgl_area_t *area,
+ uint8_t *buffer,
+ nbgl_color_map_t colorMap,
+ uint8_t *uzlib_chunk_buffer);
+void nbgl_driver_drawImageRle(nbgl_area_t *area,
+ uint8_t *buffer,
+ uint32_t buffer_len,
+ color_t fore_color,
+ uint8_t nb_skipped_bytes);
+void nbgl_driver_refreshArea(nbgl_area_t *area,
+ nbgl_refresh_mode_t mode,
+ nbgl_post_refresh_t post_refresh);
+
+/**********************
+ * MACROS
+ **********************/
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* NBGL_DRIVER_H */
diff --git a/tests/screenshots/src/main/nbgl_front.c b/tests/screenshots/src/main/nbgl_front.c
new file mode 100644
index 000000000..0f982da76
--- /dev/null
+++ b/tests/screenshots/src/main/nbgl_front.c
@@ -0,0 +1,141 @@
+
+/**
+ * @file nbgl_front.c
+ * @brief Low-Level driver for front screen, to draw elementary shapes
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include "nbgl_driver.h"
+#include "nbgl_front.h"
+#include "nbgl_debug.h"
+#include "os_helpers.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * STATIC VARIABLES
+ **********************/
+
+/**********************
+ * VARIABLES
+ **********************/
+
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/**
+ * @brief Draws a plain rectangle with the given parameters
+ *
+ * @param area position, size and color of the rectangle to draw
+ */
+void nbgl_frontDrawRect(const nbgl_area_t *area)
+{
+ ((nbgl_area_t *) area)->x0 += FULL_SCREEN_WIDTH - SCREEN_WIDTH;
+ nbgl_driver_drawRect((nbgl_area_t *) area);
+ ((nbgl_area_t *) area)->x0 -= FULL_SCREEN_WIDTH - SCREEN_WIDTH;
+}
+
+/**
+ * @brief Draws a horizontal line with the given parameters
+ *
+ * @note height is fixed to 4 pixels, and the mask provides the real thickness of the line
+ *
+ * @param area position, size and color of the line to draw
+ * @param mask bit mask to tell which pixels (in vertical axis) are to be painted in lineColor.
+ * bit[0] is the upper line on 4, and bit[3] is the bottom line
+ * @param lineColor color to be applied to the line
+ */
+void nbgl_frontDrawHorizontalLine(const nbgl_area_t *area, uint8_t mask, color_t lineColor)
+{
+ ((nbgl_area_t *) area)->x0 += FULL_SCREEN_WIDTH - SCREEN_WIDTH;
+ nbgl_driver_drawHorizontalLine((nbgl_area_t *) area, mask, lineColor);
+ ((nbgl_area_t *) area)->x0 -= FULL_SCREEN_WIDTH - SCREEN_WIDTH;
+}
+
+/**
+ * @brief Draws the given given bitmap with the given parameters. The colorMap is applied on 2BPP
+ * image if not @ref INVALID_COLOR_MAP.
+ *
+ * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in 1BPP
+ * bitmap.
+ *
+ * @param area position, size and color of the bitmap to draw
+ * @param buffer bitmap buffer
+ * @param transformation transformatin to be applied. This is a bit field, filled with @ref
+ * VERTICAL_MIRROR for example
+ * @param colorMap color map to be applied on given image, for 2 BPP image. For 1BPP, only the
+ * color[0] is used, for foreground color
+ */
+void nbgl_frontDrawImage(const nbgl_area_t *area,
+ const uint8_t *buffer,
+ nbgl_transformation_t transformation,
+ nbgl_color_map_t colorMap)
+{
+ ((nbgl_area_t *) area)->x0 += FULL_SCREEN_WIDTH - SCREEN_WIDTH;
+ nbgl_driver_drawImage((nbgl_area_t *) area, (uint8_t *) buffer, transformation, colorMap);
+ ((nbgl_area_t *) area)->x0 -= FULL_SCREEN_WIDTH - SCREEN_WIDTH;
+}
+
+/**
+ * @brief Draws the given bitmap file with the given parameters. The colorMap is applied on 2BPP
+ * image if not @ref INVALID_COLOR_MAP.
+ *
+ * @note y0 and height must be multiple 4 pixels, and background color is applied to 0's in 1BPP
+ * bitmap.
+ *
+ * @param area position and background color of the bitmap to draw
+ * @param buffer bitmap file buffer, with ledger format
+ * @param colorMap color map to be applied on given image, for 2 BPP image. For 1BPP, only the
+ * color[0] is used, for foreground color
+ * @param uzlib_chunk_buffer Work buffer of size GZLIB_UNCOMPRESSED_CHUNK
+ */
+void nbgl_frontDrawImageFile(const nbgl_area_t *area,
+ const uint8_t *buffer,
+ nbgl_color_map_t colorMap,
+ const uint8_t *uzlib_chunk_buffer)
+{
+ UNUSED(uzlib_chunk_buffer);
+ ((nbgl_area_t *) area)->x0 += FULL_SCREEN_WIDTH - SCREEN_WIDTH;
+ nbgl_driver_drawImageFile(
+ (nbgl_area_t *) area, (uint8_t *) buffer, colorMap, (uint8_t *) uzlib_chunk_buffer);
+ ((nbgl_area_t *) area)->x0 -= FULL_SCREEN_WIDTH - SCREEN_WIDTH;
+}
+/**
+ * @brief function to actually refresh the given area on screen
+ *
+ * @param area the area to refresh
+ */
+void nbgl_frontRefreshArea(const nbgl_area_t *area,
+ nbgl_refresh_mode_t mode,
+ nbgl_post_refresh_t post_refresh)
+{
+ ((nbgl_area_t *) area)->x0 += FULL_SCREEN_WIDTH - SCREEN_WIDTH;
+ nbgl_driver_refreshArea((nbgl_area_t *) area, mode, post_refresh);
+ ((nbgl_area_t *) area)->x0 -= FULL_SCREEN_WIDTH - SCREEN_WIDTH;
+}
+
+void nbgl_frontDrawImageRle(const nbgl_area_t *area,
+ const uint8_t *buffer,
+ uint32_t buffer_len,
+ color_t fore_color,
+ uint8_t nb_skipped_bytes)
+{
+ ((nbgl_area_t *) area)->x0 += FULL_SCREEN_WIDTH - SCREEN_WIDTH;
+ nbgl_driver_drawImageRle(
+ (nbgl_area_t *) area, (uint8_t *) buffer, buffer_len, fore_color, nb_skipped_bytes);
+ ((nbgl_area_t *) area)->x0 -= FULL_SCREEN_WIDTH - SCREEN_WIDTH;
+}
diff --git a/tests/screenshots/src/main/png.c b/tests/screenshots/src/main/png.c
new file mode 100644
index 000000000..f6246f28d
--- /dev/null
+++ b/tests/screenshots/src/main/png.c
@@ -0,0 +1,164 @@
+#include
+#include
+#include
+#include
+#include "json_scenario.h"
+
+/* A coloured pixel. */
+
+typedef struct {
+ uint8_t red;
+ uint8_t green;
+ uint8_t blue;
+} pixel_t;
+
+/* A picture. */
+
+typedef struct {
+ pixel_t *pixels;
+ size_t width;
+ size_t height;
+} bitmap_t;
+
+/* Given "bitmap", this returns the pixel of bitmap at the point
+ ("x", "y"). */
+
+static pixel_t *pixel_at(bitmap_t *bitmap, int x, int y)
+{
+ return bitmap->pixels + bitmap->width * y + x;
+}
+
+/* Write "bitmap" to a PNG file specified by "path"; returns 0 on
+ success, non-zero on error. */
+
+static int save_png_to_file(bitmap_t *bitmap, const char *path)
+{
+ FILE *fp;
+ png_structp png_ptr = NULL;
+ png_infop info_ptr = NULL;
+ size_t x, y;
+ png_byte **row_pointers = NULL;
+ /* "status" contains the return value of this function. At first
+ it is set to a value which means 'failure'. When the routine
+ has finished its work, it is set to a value which means
+ 'success'. */
+ int status = -1;
+ /* The following number is set by trial and error only. I cannot
+ see where it it is documented in the libpng manual.
+ */
+ int pixel_size = 3;
+ int depth = 8;
+
+ fp = fopen(path, "wb");
+ if (!fp) {
+ goto fopen_failed;
+ }
+
+ png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
+ if (png_ptr == NULL) {
+ goto png_create_write_struct_failed;
+ }
+
+ info_ptr = png_create_info_struct(png_ptr);
+ if (info_ptr == NULL) {
+ goto png_create_info_struct_failed;
+ }
+
+ /* Set up error handling. */
+
+ if (setjmp(png_jmpbuf(png_ptr))) {
+ goto png_failure;
+ }
+
+ /* Set image attributes. */
+
+ png_set_IHDR(png_ptr,
+ info_ptr,
+ bitmap->width,
+ bitmap->height,
+ depth,
+ PNG_COLOR_TYPE_RGB,
+ PNG_INTERLACE_NONE,
+ PNG_COMPRESSION_TYPE_DEFAULT,
+ PNG_FILTER_TYPE_DEFAULT);
+
+ /* Initialize rows of PNG. */
+
+ row_pointers = png_malloc(png_ptr, bitmap->height * sizeof(png_byte *));
+ for (y = 0; y < bitmap->height; y++) {
+ png_byte *row = png_malloc(png_ptr, sizeof(uint8_t) * bitmap->width * pixel_size);
+ row_pointers[y] = row;
+ for (x = 0; x < bitmap->width; x++) {
+ pixel_t *pixel = pixel_at(bitmap, x, y);
+ *row++ = pixel->red;
+ *row++ = pixel->green;
+ *row++ = pixel->blue;
+ }
+ }
+
+ /* Write the image data to "fp". */
+
+ png_init_io(png_ptr, fp);
+ png_set_rows(png_ptr, info_ptr, row_pointers);
+ png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
+
+ /* The routine has successfully written the file, so we set
+ "status" to a value which indicates success. */
+
+ status = 0;
+
+ for (y = 0; y < bitmap->height; y++) {
+ png_free(png_ptr, row_pointers[y]);
+ }
+ png_free(png_ptr, row_pointers);
+
+png_failure:
+png_create_info_struct_failed:
+ png_destroy_write_struct(&png_ptr, &info_ptr);
+png_create_write_struct_failed:
+ fclose(fp);
+fopen_failed:
+ return status;
+}
+
+int save_png(char *framebuffer, char *fullPath, uint16_t width, uint16_t height)
+{
+ bitmap_t bitmap;
+ uint32_t x;
+ uint32_t y;
+ int status;
+
+ status = 0;
+
+ /* Create an image. */
+
+ bitmap.width = width;
+ bitmap.height = height;
+
+ bitmap.pixels = calloc(bitmap.width * bitmap.height, sizeof(pixel_t));
+
+ if (!bitmap.pixels) {
+ return -1;
+ }
+
+ for (y = 0; y < bitmap.height; y++) {
+ for (x = 0; x < bitmap.width; x++) {
+ pixel_t *pixel = pixel_at(&bitmap, x, y);
+ pixel->red
+ = framebuffer[(y * bitmap.width) + x] + (framebuffer[(y * bitmap.width) + x] << 4);
+ pixel->green = pixel->red;
+ pixel->blue = pixel->red;
+ }
+ }
+
+ /* Write the image to a file 'bitmap.png'. */
+ // printf("saving to %s\n",fullPath);
+ if (save_png_to_file(&bitmap, fullPath)) {
+ fprintf(stderr, "Error writing file %s.\n", fullPath);
+ status = -1;
+ }
+
+ free(bitmap.pixels);
+
+ return status;
+}
diff --git a/tests/screenshots/src/main/properties.c b/tests/screenshots/src/main/properties.c
new file mode 100644
index 000000000..077f24f7b
--- /dev/null
+++ b/tests/screenshots/src/main/properties.c
@@ -0,0 +1,171 @@
+/**
+ * @file properties.c
+ * @brief read properties from a file.properties file, in JSON format
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#include
+#include
+#include
+#include "properties.h"
+// You may have to install packet libjson-c-dev
+#include
+
+static char *buffer = NULL;
+
+void properties_init(char *filename)
+{
+ FILE *json_file;
+ size_t size;
+
+ if ((json_file = fopen(filename, "r")) == NULL) {
+ fprintf(stdout, "Unable to read properties JSON file %s!\n", filename);
+ return;
+ }
+
+ // Determine file size
+ if (fseek(json_file, 0, SEEK_END) != 0) {
+ fprintf(stdout, "Error seeking to the end of JSON file %s!\n", filename);
+ fclose(json_file);
+ return;
+ }
+
+ if ((size = ftell(json_file)) == 0) {
+ fprintf(stdout, "Error getting size of JSON file %s!\n", filename);
+ fclose(json_file);
+ return;
+ }
+
+ if (fseek(json_file, 0, SEEK_SET) != 0) {
+ fprintf(stdout, "Error seeking to the start of JSON file %s!\n", filename);
+ fclose(json_file);
+ return;
+ }
+
+ // Allocate memory and read the file
+ if ((buffer = malloc(size + 1)) == NULL) {
+ fprintf(stdout, "Error allocating %lu bytes to read JSON file %s!\n", size, filename);
+ fclose(json_file);
+ return;
+ }
+ if (fread(buffer, 1, size, json_file) != size) {
+ fprintf(stdout, "Error reading %lu bytes from JSON file %s!\n", size, filename);
+ free(buffer);
+ fclose(json_file);
+ return;
+ }
+ buffer[size] = 0;
+ fclose(json_file);
+}
+
+static bool search_string_property(char *prop_name, char **val)
+{
+ struct json_object *json_root;
+
+ if ((json_root = json_tokener_parse(buffer)) == NULL) {
+ fprintf(stdout, "An error occurred while parsing properties JSON file!\n");
+ return false;
+ }
+
+ if (json_type_object != json_object_get_type(json_root)) {
+ fprintf(stdout, "There is no valid entry in file!\n");
+ json_object_put(json_root);
+ return false;
+ }
+ // Next line is a macro that iterate through keys/values of that JSON object
+ json_object_object_foreach(json_root, key, value)
+ {
+ if (!strcmp(key, prop_name)) {
+ *val = (char *) json_object_get_string(value);
+ return true;
+ }
+ }
+ json_object_put(json_root);
+ return false;
+}
+
+static bool search_int_property(char *prop_name, long *val)
+{
+ struct json_object *json_root;
+
+ if ((json_root = json_tokener_parse(buffer)) == NULL) {
+ fprintf(stdout, "An error occurred while parsing properties JSON file!\n");
+ return false;
+ }
+
+ if (json_type_object != json_object_get_type(json_root)) {
+ fprintf(stdout, "There is no valid entry in file!\n");
+ json_object_put(json_root);
+ return false;
+ }
+ // Next line is a macro that iterate through keys/values of that JSON object
+ json_object_object_foreach(json_root, key, value)
+ {
+ if (!strcmp(key, prop_name)) {
+ *val = json_object_get_int(value);
+ return true;
+ }
+ }
+ json_object_put(json_root);
+ return false;
+}
+
+long properties_read_int(char *name, long defaultValue)
+{
+ long val;
+ bool found;
+
+ // search property name
+ found = search_int_property(name, &val);
+ if (!found) {
+ return defaultValue;
+ }
+ return val;
+}
+
+char *properties_read_string(char *name, char *defaultValue)
+{
+ bool found;
+ char *ret, *val;
+
+ // search property name
+ found = search_string_property(name, &val);
+ if (found == false) {
+ return defaultValue;
+ }
+ ret = (char *) malloc(strlen(val) + 1);
+ memcpy(ret, val, strlen(val) + 1);
+
+ return ret;
+}
+
+bool properties_read_bool(char *name, bool defaultValue)
+{
+ bool ret;
+ struct json_object *json_root;
+
+ if ((json_root = json_tokener_parse(buffer)) == NULL) {
+ fprintf(stdout, "An error occurred while parsing properties JSON file!\n");
+ return NULL;
+ }
+
+ if (json_type_object != json_object_get_type(json_root)) {
+ fprintf(stdout, "There is no valid entry in file!\n");
+ json_object_put(json_root);
+ return NULL;
+ }
+ // Next line is a macro that iterate through keys/values of that JSON object
+ json_object_object_foreach(json_root, key, value)
+ {
+ if (!strcmp(key, name)) {
+ ret = (json_object_get_boolean(value) != 0);
+ return ret;
+ }
+ }
+ json_object_put(json_root);
+
+ return defaultValue;
+}
diff --git a/tests/screenshots/src/main/properties.h b/tests/screenshots/src/main/properties.h
new file mode 100644
index 000000000..36eeb1d2b
--- /dev/null
+++ b/tests/screenshots/src/main/properties.h
@@ -0,0 +1,24 @@
+/**
+ * @file properties.h
+ *
+ */
+
+#ifndef PROPERTIES_H
+#define PROPERTIES_H
+
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+void properties_init(char *filename);
+long properties_read_int(char *name, long defaultValue);
+char *properties_read_string(char *name, char *defaultValue);
+bool properties_read_bool(char *name, bool defaultValue);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PROPERTIES_H */
diff --git a/tests/screenshots/src/main/stubs.c b/tests/screenshots/src/main/stubs.c
new file mode 100644
index 000000000..8fd56359d
--- /dev/null
+++ b/tests/screenshots/src/main/stubs.c
@@ -0,0 +1,294 @@
+
+/**
+ * @file stubs.c
+ * @brief bolos functions stubs
+ *
+ */
+
+/*********************
+ * INCLUDES
+ *********************/
+#define _DEFAULT_SOURCE /* needed for usleep() */
+#include
+#include
+#include "properties.h"
+#include "nbgl_driver.h"
+#include "nbgl_side.h"
+#include "nbgl_draw.h"
+#include "nbgl_screen.h"
+#include "nbgl_fonts.h"
+#include "nbgl_debug.h"
+#include "uzlib.h"
+#include "glyphs.h"
+#include "os_pin.h"
+#include "os_endorsement.h"
+#include "os_settings.h"
+#include "os_seed.h"
+#include "os_id.h"
+#include "os_nvm.h"
+#include "os_pic.h"
+#include "os_app.h"
+#include "os_halt.h"
+#include "os_io_seproxyhal.h"
+#include "os_memory.h"
+#include "os_registry.h"
+#include "os_screen.h"
+#include "os_task.h"
+#include "os_helpers.h"
+#include "appflags.h"
+#include "cx_errors.h"
+#include "lcx_rng.h"
+#include "ux_loc.h"
+#include "sha256.h"
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+
+/*********************
+ * DEFINES
+ *********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * VARIABLES
+ **********************/
+#ifdef HAVE_SE_TOUCH
+nbgl_touchStatePosition_t gTouchStatePosition;
+#endif
+unsigned char G_io_seproxyhal_spi_buffer[IO_SEPROXYHAL_BUFFER_SIZE_B];
+extern bool verbose;
+
+bool globalError = false;
+extern char *dirName;
+
+unsigned char G_io_apdu_buffer[IO_APDU_BUFFER_SIZE];
+
+uint32_t G_interval_ms = 100;
+
+#ifdef HAVE_SE_TOUCH
+bool touchEnabled = true;
+#endif
+/**********************
+ * STATIC PROTOTYPES
+ **********************/
+
+/**********************
+ * GLOBAL FUNCTIONS
+ **********************/
+///////////////////////////////////////////////////////////////////////////////////////////
+// fake implementation of os functions
+///////////////////////////////////////////////////////////////////////////////////////////
+
+#ifdef HAVE_BLE
+void io_seph_ble_enable(unsigned char enable)
+{
+ UNUSED(enable);
+}
+void io_seph_ble_clear_bond_db(void) {}
+void io_seph_ble_name_changed(void) {}
+
+void io_seph_ux_accept_pairing(unsigned char status)
+{
+ UNUSED(status);
+}
+#endif // HAVE_BLE
+
+unsigned int io_button_read(void);
+
+unsigned int io_button_read(void)
+{
+ return 0;
+}
+
+int bytes_to_hex(char *out, size_t outl, const void *value, size_t len)
+{
+ const uint8_t *bytes = (const uint8_t *) value;
+ const char *hex = "0123456789ABCDEF";
+
+ if (outl < 2 * len + 1) {
+ *out = '\0';
+ return -1;
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ *out++ = hex[(bytes[i] >> 4) & 0xf];
+ *out++ = hex[bytes[i] & 0xf];
+ }
+ *out = 0;
+ return 0;
+}
+
+unsigned short io_seph_recv(unsigned char *buffer, unsigned short maxlength, unsigned int flags)
+{
+ UNUSED(buffer);
+ UNUSED(flags);
+ return maxlength;
+}
+
+void io_seph_send(const unsigned char *buffer, unsigned short length)
+{
+ UNUSED(buffer);
+ UNUSED(length);
+}
+
+#ifdef HAVE_PIEZO_SOUND
+void io_seproxyhal_play_tune(tune_index_e tune_index)
+{
+ UNUSED(tune_index);
+}
+#endif // HAVE_PIEZO_SOUND
+
+#ifdef HAVE_SERIALIZED_NBGL
+void io_seproxyhal_send_nbgl_serialized(nbgl_serialized_event_type_e event, nbgl_obj_t *obj)
+{
+ UNUSED(event);
+ UNUSED(obj);
+}
+#endif // HAVE_SERIALIZED_NBGL
+
+void io_seproxyhal_se_reset(void)
+{
+ if (verbose) {
+ printf("io_seproxyhal_se_reset\n");
+ }
+}
+
+void io_seproxyhal_disable_io(void) {}
+
+void io_seproxyhal_general_status(void) {}
+
+#ifdef HAVE_NFC
+void io_seproxyhal_nfc_power(bool forceInit)
+{
+ UNUSED(forceInit);
+}
+#endif // HAVE_NFC
+
+#ifdef HAVE_BLE
+void os_ux_set_status(unsigned int ux_id, unsigned int status)
+{
+ UNUSED(ux_id);
+ UNUSED(status);
+}
+#endif // HAVE_BLE
+
+void io_seph_ux_redisplay(void)
+{
+ nbgl_screenRedraw();
+}
+
+void halt(void) {}
+
+void cx_rng_no_throw(uint8_t *buffer, size_t len)
+{
+ size_t i;
+ for (i = 0; i < len; i++) {
+#ifdef BUILD_SCREENSHOTS
+ buffer[i] = 0;
+#else // BUILD_SCREENSHOTS
+ buffer[i] = rand() & 0xFF;
+#endif // BUILD_SCREENSHOTS
+ }
+}
+
+uint32_t cx_rng_u32_range_func(uint32_t a, uint32_t b, cx_rng_u32_range_randfunc_t randfunc)
+{
+ uint32_t range = b - a;
+ uint32_t r;
+
+ if ((range & (range - 1)) == 0) { // special case: range is a power of 2
+ r = randfunc();
+ return a + r % range;
+ }
+
+ uint32_t chunk_size = UINT32_MAX / range;
+ uint32_t last_chunk_value = chunk_size * range;
+ r = randfunc();
+ while (r >= last_chunk_value) {
+ r = randfunc();
+ }
+ return a + r / chunk_size;
+}
+
+void os_get_memory_info(meminfo_t *meminfo)
+{
+ UNUSED(meminfo);
+}
+
+#ifdef HAVE_SE_TOUCH
+void touch_get_last_info(io_touch_info_t *info)
+{
+ info->state = (gTouchStatePosition.state == PRESSED) ? true : false;
+ info->x = gTouchStatePosition.x;
+ info->y = gTouchStatePosition.y;
+}
+#endif // HAVE_SE_TOUCH
+
+void os_sched_exit(bolos_task_status_t status_code)
+{
+ UNUSED(status_code);
+ exit(-1);
+}
+
+unsigned int os_sched_current_task(void)
+{
+ return TASK_USER;
+}
+
+void nbgl_screen_reinit(void) {}
+
+#ifdef HAVE_STAX_CONFIG_DISPLAY_FAST_MODE
+void nbgl_screen_config_fast_mode(uint8_t setting)
+{
+ UNUSED(setting);
+}
+#endif // HAVE_STAX_CONFIG_DISPLAY_FAST_MODE
+
+#ifdef HAVE_STAX_DISPLAY_FAST_MODE
+void nbgl_screen_update_temperature(uint8_t temp_degrees)
+{
+ UNUSED(temp_degrees);
+}
+#endif // HAVE_STAX_DISPLAY_FAST_MODE
+
+#ifdef HAVE_SE_EINK_DISPLAY
+void nbgl_wait_pipeline(void) {}
+#endif
+
+#ifdef HAVE_SE_TOUCH
+void touch_set_state(bool enable)
+{
+ if (enable == touchEnabled) {
+ return;
+ }
+ if (verbose) {
+ printf("touch_set_state %s\n", enable ? "ENABLED" : "DISABLED");
+ }
+ touchEnabled = enable;
+}
+
+uint8_t touch_exclude_borders(uint8_t excluded_borders)
+{
+ return excluded_borders;
+}
+#endif // HAVE_SE_TOUCH
+
+#ifdef HAVE_SE_SCREEN
+void screen_set_brightness(unsigned int percent)
+{
+ UNUSED(percent);
+}
+#endif // HAVE_SE_SCREEN
+
+void *pic(void *linked_address)
+{
+ return linked_address;
+}
diff --git a/tests/screenshots/src/uzlib/adler32.c b/tests/screenshots/src/uzlib/adler32.c
new file mode 100644
index 000000000..bc984269e
--- /dev/null
+++ b/tests/screenshots/src/uzlib/adler32.c
@@ -0,0 +1,103 @@
+/*
+ * Adler-32 checksum
+ *
+ * Copyright (c) 2003 by Joergen Ibsen / Jibz
+ * All Rights Reserved
+ *
+ * http://www.ibsensoftware.com/
+ *
+ * This software is provided 'as-is', without any express
+ * or implied warranty. In no event will the authors be
+ * held liable for any damages arising from the use of
+ * this software.
+ *
+ * Permission is granted to anyone to use this software
+ * for any purpose, including commercial applications,
+ * and to alter it and redistribute it freely, subject to
+ * the following restrictions:
+ *
+ * 1. The origin of this software must not be
+ * misrepresented; you must not claim that you
+ * wrote the original software. If you use this
+ * software in a product, an acknowledgment in
+ * the product documentation would be appreciated
+ * but is not required.
+ *
+ * 2. Altered source versions must be plainly marked
+ * as such, and must not be misrepresented as
+ * being the original software.
+ *
+ * 3. This notice may not be removed or altered from
+ * any source distribution.
+ */
+
+/*
+ * Adler-32 algorithm taken from the zlib source, which is
+ * Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler
+ */
+
+#include "tinf.h"
+
+#define A32_BASE 65521
+#define A32_NMAX 5552
+
+uint32_t uzlib_adler32(const void *data, unsigned int length, uint32_t prev_sum /* 1 */)
+{
+ const unsigned char *buf = (const unsigned char *) data;
+
+ unsigned int s1 = prev_sum & 0xffff;
+ unsigned int s2 = prev_sum >> 16;
+
+ while (length > 0) {
+ int k = length < A32_NMAX ? length : A32_NMAX;
+ int i;
+
+ for (i = k / 16; i; --i, buf += 16) {
+ s1 += buf[0];
+ s2 += s1;
+ s1 += buf[1];
+ s2 += s1;
+ s1 += buf[2];
+ s2 += s1;
+ s1 += buf[3];
+ s2 += s1;
+ s1 += buf[4];
+ s2 += s1;
+ s1 += buf[5];
+ s2 += s1;
+ s1 += buf[6];
+ s2 += s1;
+ s1 += buf[7];
+ s2 += s1;
+
+ s1 += buf[8];
+ s2 += s1;
+ s1 += buf[9];
+ s2 += s1;
+ s1 += buf[10];
+ s2 += s1;
+ s1 += buf[11];
+ s2 += s1;
+ s1 += buf[12];
+ s2 += s1;
+ s1 += buf[13];
+ s2 += s1;
+ s1 += buf[14];
+ s2 += s1;
+ s1 += buf[15];
+ s2 += s1;
+ }
+
+ for (i = k % 16; i; --i) {
+ s1 += *buf++;
+ s2 += s1;
+ }
+
+ s1 %= A32_BASE;
+ s2 %= A32_BASE;
+
+ length -= k;
+ }
+
+ return ((uint32_t) s2 << 16) | s1;
+}
diff --git a/tests/screenshots/src/uzlib/crc32.c b/tests/screenshots/src/uzlib/crc32.c
new file mode 100644
index 000000000..c04645c73
--- /dev/null
+++ b/tests/screenshots/src/uzlib/crc32.c
@@ -0,0 +1,72 @@
+/*
+ * CRC32 checksum
+ *
+ * Copyright (c) 1998-2003 by Joergen Ibsen / Jibz
+ * All Rights Reserved
+ *
+ * http://www.ibsensoftware.com/
+ *
+ * This software is provided 'as-is', without any express
+ * or implied warranty. In no event will the authors be
+ * held liable for any damages arising from the use of
+ * this software.
+ *
+ * Permission is granted to anyone to use this software
+ * for any purpose, including commercial applications,
+ * and to alter it and redistribute it freely, subject to
+ * the following restrictions:
+ *
+ * 1. The origin of this software must not be
+ * misrepresented; you must not claim that you
+ * wrote the original software. If you use this
+ * software in a product, an acknowledgment in
+ * the product documentation would be appreciated
+ * but is not required.
+ *
+ * 2. Altered source versions must be plainly marked
+ * as such, and must not be misrepresented as
+ * being the original software.
+ *
+ * 3. This notice may not be removed or altered from
+ * any source distribution.
+ */
+
+/*
+ * CRC32 algorithm taken from the zlib source, which is
+ * Copyright (C) 1995-1998 Jean-loup Gailly and Mark Adler
+ */
+
+#include "tinf.h"
+
+static const uint32_t tinf_crc32tab[16] = {0x00000000,
+ 0x1db71064,
+ 0x3b6e20c8,
+ 0x26d930ac,
+ 0x76dc4190,
+ 0x6b6b51f4,
+ 0x4db26158,
+ 0x5005713c,
+ 0xedb88320,
+ 0xf00f9344,
+ 0xd6d6a3e8,
+ 0xcb61b38c,
+ 0x9b64c2b0,
+ 0x86d3d2d4,
+ 0xa00ae278,
+ 0xbdbdf21c};
+
+/* crc is previous value for incremental computation, 0xffffffff initially */
+uint32_t uzlib_crc32(const void *data, unsigned int length, uint32_t crc)
+{
+ const unsigned char *buf = (const unsigned char *) data;
+ unsigned int i;
+
+ for (i = 0; i < length; ++i) {
+ crc ^= buf[i];
+ crc = tinf_crc32tab[crc & 0x0f] ^ (crc >> 4);
+ crc = tinf_crc32tab[crc & 0x0f] ^ (crc >> 4);
+ }
+
+ // return value suitable for passing in next time, for final value invert it
+ return crc /* ^ 0xffffffff*/;
+}
diff --git a/tests/screenshots/src/uzlib/defl_static.c b/tests/screenshots/src/uzlib/defl_static.c
new file mode 100644
index 000000000..75952f83c
--- /dev/null
+++ b/tests/screenshots/src/uzlib/defl_static.c
@@ -0,0 +1,310 @@
+/*
+
+Routines in this file are based on:
+Zlib (RFC1950 / RFC1951) compression for PuTTY.
+
+PuTTY is copyright 1997-2014 Simon Tatham.
+
+Portions copyright Robert de Bath, Joris van Rantwijk, Delian
+Delchev, Andreas Schultz, Jeroen Massar, Wez Furlong, Nicolas Barry,
+Justin Bradford, Ben Harris, Malcolm Smith, Ahmad Khalifa, Markus
+Kuhn, Colin Watson, and CORE SDI S.A.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation files
+(the "Software"), to deal in the Software without restriction,
+including without limitation the rights to use, copy, modify, merge,
+publish, distribute, sublicense, and/or sell copies of the Software,
+and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE
+FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
+CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+#include
+#include
+#include
+#include
+#include "uzlib.h"
+#include "defl_static.h"
+
+#define snew(type) ((type *) malloc(sizeof(type)))
+#define snewn(n, type) ((type *) malloc((n) * sizeof(type)))
+#define sresize(x, n, type) ((type *) realloc((x), (n) * sizeof(type)))
+#define sfree(x) (free((x)))
+
+#ifndef FALSE
+#define FALSE 0
+#define TRUE (!FALSE)
+#endif
+
+/* ----------------------------------------------------------------------
+ * Zlib compression. We always use the static Huffman tree option.
+ * Mostly this is because it's hard to scan a block in advance to
+ * work out better trees; dynamic trees are great when you're
+ * compressing a large file under no significant time constraint,
+ * but when you're compressing little bits in real time, things get
+ * hairier.
+ *
+ * I suppose it's possible that I could compute Huffman trees based
+ * on the frequencies in the _previous_ block, as a sort of
+ * heuristic, but I'm not confident that the gain would balance out
+ * having to transmit the trees.
+ */
+
+void outbits(struct uzlib_comp *out, unsigned long bits, int nbits)
+{
+ assert(out->noutbits + nbits <= 32);
+ out->outbits |= bits << out->noutbits;
+ out->noutbits += nbits;
+ while (out->noutbits >= 8) {
+ if (out->outlen >= out->outsize) {
+ out->outsize = out->outlen + 64;
+ out->outbuf = sresize(out->outbuf, out->outsize, unsigned char);
+ }
+ out->outbuf[out->outlen++] = (unsigned char) (out->outbits & 0xFF);
+ out->outbits >>= 8;
+ out->noutbits -= 8;
+ }
+}
+
+static const unsigned char mirrorbytes[256] = {
+ 0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0, 0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
+ 0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8, 0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
+ 0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4, 0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
+ 0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec, 0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
+ 0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2, 0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
+ 0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea, 0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
+ 0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6, 0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
+ 0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee, 0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
+ 0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1, 0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
+ 0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9, 0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
+ 0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5, 0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
+ 0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed, 0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
+ 0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3, 0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
+ 0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb, 0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
+ 0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7, 0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
+ 0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef, 0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff,
+};
+
+typedef struct {
+ uint8_t extrabits;
+ uint8_t min, max;
+} len_coderecord;
+
+typedef struct {
+ uint8_t code, extrabits;
+ uint16_t min, max;
+} dist_coderecord;
+
+#define TO_LCODE(x, y) x - 3, y - 3
+#define FROM_LCODE(x) (x + 3)
+
+static const len_coderecord lencodes[] = {
+ {0, TO_LCODE(3, 3) },
+ {0, TO_LCODE(4, 4) },
+ {0, TO_LCODE(5, 5) },
+ {0, TO_LCODE(6, 6) },
+ {0, TO_LCODE(7, 7) },
+ {0, TO_LCODE(8, 8) },
+ {0, TO_LCODE(9, 9) },
+ {0, TO_LCODE(10, 10) },
+ {1, TO_LCODE(11, 12) },
+ {1, TO_LCODE(13, 14) },
+ {1, TO_LCODE(15, 16) },
+ {1, TO_LCODE(17, 18) },
+ {2, TO_LCODE(19, 22) },
+ {2, TO_LCODE(23, 26) },
+ {2, TO_LCODE(27, 30) },
+ {2, TO_LCODE(31, 34) },
+ {3, TO_LCODE(35, 42) },
+ {3, TO_LCODE(43, 50) },
+ {3, TO_LCODE(51, 58) },
+ {3, TO_LCODE(59, 66) },
+ {4, TO_LCODE(67, 82) },
+ {4, TO_LCODE(83, 98) },
+ {4, TO_LCODE(99, 114)},
+ {4, TO_LCODE(115, 130)},
+ {5, TO_LCODE(131, 162)},
+ {5, TO_LCODE(163, 194)},
+ {5, TO_LCODE(195, 226)},
+ {5, TO_LCODE(227, 257)},
+ {0, TO_LCODE(258, 258)},
+};
+
+static const dist_coderecord distcodes[] = {
+ {0, 0, 1, 1 },
+ {1, 0, 2, 2 },
+ {2, 0, 3, 3 },
+ {3, 0, 4, 4 },
+ {4, 1, 5, 6 },
+ {5, 1, 7, 8 },
+ {6, 2, 9, 12 },
+ {7, 2, 13, 16 },
+ {8, 3, 17, 24 },
+ {9, 3, 25, 32 },
+ {10, 4, 33, 48 },
+ {11, 4, 49, 64 },
+ {12, 5, 65, 96 },
+ {13, 5, 97, 128 },
+ {14, 6, 129, 192 },
+ {15, 6, 193, 256 },
+ {16, 7, 257, 384 },
+ {17, 7, 385, 512 },
+ {18, 8, 513, 768 },
+ {19, 8, 769, 1024 },
+ {20, 9, 1025, 1536 },
+ {21, 9, 1537, 2048 },
+ {22, 10, 2049, 3072 },
+ {23, 10, 3073, 4096 },
+ {24, 11, 4097, 6144 },
+ {25, 11, 6145, 8192 },
+ {26, 12, 8193, 12288},
+ {27, 12, 12289, 16384},
+ {28, 13, 16385, 24576},
+ {29, 13, 24577, 32768},
+};
+
+void zlib_literal(struct uzlib_comp *out, unsigned char c)
+{
+ if (out->comp_disabled) {
+ /*
+ * We're in an uncompressed block, so just output the byte.
+ */
+ outbits(out, c, 8);
+ return;
+ }
+
+ if (c <= 143) {
+ /* 0 through 143 are 8 bits long starting at 00110000. */
+ outbits(out, mirrorbytes[0x30 + c], 8);
+ }
+ else {
+ /* 144 through 255 are 9 bits long starting at 110010000. */
+ outbits(out, 1 + 2 * mirrorbytes[0x90 - 144 + c], 9);
+ }
+}
+
+void zlib_match(struct uzlib_comp *out, int distance, int len)
+{
+ const dist_coderecord *d;
+ const len_coderecord *l;
+ int i, j, k;
+ int lcode;
+
+ assert(!out->comp_disabled);
+
+ while (len > 0) {
+ int thislen;
+
+ /*
+ * We can transmit matches of lengths 3 through 258
+ * inclusive. So if len exceeds 258, we must transmit in
+ * several steps, with 258 or less in each step.
+ *
+ * Specifically: if len >= 261, we can transmit 258 and be
+ * sure of having at least 3 left for the next step. And if
+ * len <= 258, we can just transmit len. But if len == 259
+ * or 260, we must transmit len-3.
+ */
+ thislen = (len > 260 ? 258 : len <= 258 ? len : len - 3);
+ len -= thislen;
+
+ /*
+ * Binary-search to find which length code we're
+ * transmitting.
+ */
+ i = -1;
+ j = sizeof(lencodes) / sizeof(*lencodes);
+ while (1) {
+ assert(j - i >= 2);
+ k = (j + i) / 2;
+ if (thislen < FROM_LCODE(lencodes[k].min)) {
+ j = k;
+ }
+ else if (thislen > FROM_LCODE(lencodes[k].max)) {
+ i = k;
+ }
+ else {
+ l = &lencodes[k];
+ break; /* found it! */
+ }
+ }
+
+ lcode = l - lencodes + 257;
+
+ /*
+ * Transmit the length code. 256-279 are seven bits
+ * starting at 0000000; 280-287 are eight bits starting at
+ * 11000000.
+ */
+ if (lcode <= 279) {
+ outbits(out, mirrorbytes[(lcode - 256) * 2], 7);
+ }
+ else {
+ outbits(out, mirrorbytes[0xc0 - 280 + lcode], 8);
+ }
+
+ /*
+ * Transmit the extra bits.
+ */
+ if (l->extrabits) {
+ outbits(out, thislen - FROM_LCODE(l->min), l->extrabits);
+ }
+
+ /*
+ * Binary-search to find which distance code we're
+ * transmitting.
+ */
+ i = -1;
+ j = sizeof(distcodes) / sizeof(*distcodes);
+ while (1) {
+ assert(j - i >= 2);
+ k = (j + i) / 2;
+ if (distance < distcodes[k].min) {
+ j = k;
+ }
+ else if (distance > distcodes[k].max) {
+ i = k;
+ }
+ else {
+ d = &distcodes[k];
+ break; /* found it! */
+ }
+ }
+
+ /*
+ * Transmit the distance code. Five bits starting at 00000.
+ */
+ outbits(out, mirrorbytes[d->code * 8], 5);
+
+ /*
+ * Transmit the extra bits.
+ */
+ if (d->extrabits) {
+ outbits(out, distance - d->min, d->extrabits);
+ }
+ }
+}
+
+void zlib_start_block(struct uzlib_comp *out)
+{
+ // outbits(out, 0x9C78, 16);
+ outbits(out, 1, 1); /* Final block */
+ outbits(out, 1, 2); /* Static huffman block */
+}
+
+void zlib_finish_block(struct uzlib_comp *out)
+{
+ outbits(out, 0, 7); /* close block */
+ outbits(out, 0, 7); /* Make sure all bits are flushed */
+}
diff --git a/tests/screenshots/src/uzlib/defl_static.h b/tests/screenshots/src/uzlib/defl_static.h
new file mode 100644
index 000000000..cdfa973ba
--- /dev/null
+++ b/tests/screenshots/src/uzlib/defl_static.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) uzlib authors
+ *
+ * This software is provided 'as-is', without any express
+ * or implied warranty. In no event will the authors be
+ * held liable for any damages arising from the use of
+ * this software.
+ *
+ * Permission is granted to anyone to use this software
+ * for any purpose, including commercial applications,
+ * and to alter it and redistribute it freely, subject to
+ * the following restrictions:
+ *
+ * 1. The origin of this software must not be
+ * misrepresented; you must not claim that you
+ * wrote the original software. If you use this
+ * software in a product, an acknowledgment in
+ * the product documentation would be appreciated
+ * but is not required.
+ *
+ * 2. Altered source versions must be plainly marked
+ * as such, and must not be misrepresented as
+ * being the original software.
+ *
+ * 3. This notice may not be removed or altered from
+ * any source distribution.
+ */
+
+/* This files contains type declaration and prototypes for defl_static.c.
+ They may be altered/distinct from the originals used in PuTTY source
+ code. */
+
+void outbits(struct uzlib_comp *ctx, unsigned long bits, int nbits);
+void zlib_start_block(struct uzlib_comp *ctx);
+void zlib_finish_block(struct uzlib_comp *ctx);
+void zlib_literal(struct uzlib_comp *ctx, unsigned char c);
+void zlib_match(struct uzlib_comp *ctx, int distance, int len);
diff --git a/tests/screenshots/src/uzlib/genlz77.c b/tests/screenshots/src/uzlib/genlz77.c
new file mode 100644
index 000000000..0407263f9
--- /dev/null
+++ b/tests/screenshots/src/uzlib/genlz77.c
@@ -0,0 +1,127 @@
+/*
+ * genlz77 - Generic LZ77 compressor
+ *
+ * Copyright (c) 2014 by Paul Sokolovsky
+ *
+ * This software is provided 'as-is', without any express
+ * or implied warranty. In no event will the authors be
+ * held liable for any damages arising from the use of
+ * this software.
+ *
+ * Permission is granted to anyone to use this software
+ * for any purpose, including commercial applications,
+ * and to alter it and redistribute it freely, subject to
+ * the following restrictions:
+ *
+ * 1. The origin of this software must not be
+ * misrepresented; you must not claim that you
+ * wrote the original software. If you use this
+ * software in a product, an acknowledgment in
+ * the product documentation would be appreciated
+ * but is not required.
+ *
+ * 2. Altered source versions must be plainly marked
+ * as such, and must not be misrepresented as
+ * being the original software.
+ *
+ * 3. This notice may not be removed or altered from
+ * any source distribution.
+ */
+#include
+#include
+#include
+#include "uzlib.h"
+
+#if 0
+#define HASH_BITS 12
+#else
+#define HASH_BITS data->hash_bits
+#endif
+
+#define HASH_SIZE (1 << HASH_BITS)
+
+/* Minimum and maximum length of matches to look for, inclusive */
+#define MIN_MATCH 3
+#define MAX_MATCH 258
+
+/* Max offset of the match to look for, inclusive */
+#if 0
+#define MAX_OFFSET 32768
+#else
+#define MAX_OFFSET data->dict_size
+#endif
+
+/* Hash function can be defined as macro or as inline function */
+
+/*#define HASH(p) (p[0] + p[1] + p[2])*/
+
+/* This is hash function from liblzf */
+static inline int HASH(struct uzlib_comp *data, const uint8_t *p)
+{
+ int v = (p[0] << 16) | (p[1] << 8) | p[2];
+ int hash = ((v >> (3 * 8 - HASH_BITS)) - v) & (HASH_SIZE - 1);
+ return hash;
+}
+
+#ifdef DUMP_LZTXT
+
+/* Counter for approximate compressed length in LZTXT mode. */
+/* Literal is counted as 1, copy as 2 bytes. */
+unsigned approx_compressed_len;
+
+void literal(void *data, uint8_t val)
+{
+ printf("L%02x # %c\n", val, (val >= 0x20 && val <= 0x7e) ? val : '?');
+ approx_compressed_len++;
+}
+
+void copy(void *data, unsigned offset, unsigned len)
+{
+ printf("C-%u,%u\n", offset, len);
+ approx_compressed_len += 2;
+}
+
+#else
+
+static inline void literal(void *data, uint8_t val)
+{
+ zlib_literal(data, val);
+}
+
+static inline void copy(void *data, unsigned offset, unsigned len)
+{
+ zlib_match(data, offset, len);
+}
+
+#endif
+
+void uzlib_compress(struct uzlib_comp *data, const uint8_t *src, unsigned slen)
+{
+ const uint8_t *top = src + slen - MIN_MATCH;
+ while (src < top) {
+ int h = HASH(data, src);
+ const uint8_t **bucket = &data->hash_table[h & (HASH_SIZE - 1)];
+ const uint8_t *subs = *bucket;
+ *bucket = src;
+ if (subs && src > subs && (src - subs) <= MAX_OFFSET && !memcmp(src, subs, MIN_MATCH)) {
+ src += MIN_MATCH;
+ const uint8_t *m = subs + MIN_MATCH;
+ int len = MIN_MATCH;
+ while (*src == *m && len < MAX_MATCH && src < top) {
+ src++;
+ m++;
+ len++;
+ }
+ copy(data, src - len - subs, len);
+ }
+ else {
+ literal(data, *src++);
+ }
+ }
+ // Process buffer tail, which is less than MIN_MATCH
+ // (and so it doesn't make sense to look for matches there)
+ top += MIN_MATCH;
+ while (src < top) {
+ literal(data, *src++);
+ }
+}
diff --git a/tests/screenshots/src/uzlib/tinf.h b/tests/screenshots/src/uzlib/tinf.h
new file mode 100644
index 000000000..ae6e1c407
--- /dev/null
+++ b/tests/screenshots/src/uzlib/tinf.h
@@ -0,0 +1,3 @@
+/* Compatibility header for the original tinf lib/older versions of uzlib.
+ Note: may be removed in the future, please migrate to uzlib.h. */
+#include "uzlib.h"
diff --git a/tests/screenshots/src/uzlib/tinf_compat.h b/tests/screenshots/src/uzlib/tinf_compat.h
new file mode 100644
index 000000000..240d19541
--- /dev/null
+++ b/tests/screenshots/src/uzlib/tinf_compat.h
@@ -0,0 +1,9 @@
+/* This header contains compatibility defines for the original tinf API
+ and uzlib 2.x and below API. These defines are deprecated and going
+ to be removed in the future, so applications should migrate to new
+ uzlib API. */
+#define TINF_DATA struct uzlib_uncomp
+
+#define destSize dest_size
+#define destStart dest_start
+#define readSource source_read_cb
diff --git a/tests/screenshots/src/uzlib/tinfgzip.c b/tests/screenshots/src/uzlib/tinfgzip.c
new file mode 100644
index 000000000..cd0c923a7
--- /dev/null
+++ b/tests/screenshots/src/uzlib/tinfgzip.c
@@ -0,0 +1,122 @@
+/*
+ * uzlib - tiny deflate/inflate library (deflate, gzip, zlib)
+ *
+ * Copyright (c) 2003 by Joergen Ibsen / Jibz
+ * All Rights Reserved
+ *
+ * http://www.ibsensoftware.com/
+ *
+ * Copyright (c) 2014-2018 by Paul Sokolovsky
+ *
+ * This software is provided 'as-is', without any express
+ * or implied warranty. In no event will the authors be
+ * held liable for any damages arising from the use of
+ * this software.
+ *
+ * Permission is granted to anyone to use this software
+ * for any purpose, including commercial applications,
+ * and to alter it and redistribute it freely, subject to
+ * the following restrictions:
+ *
+ * 1. The origin of this software must not be
+ * misrepresented; you must not claim that you
+ * wrote the original software. If you use this
+ * software in a product, an acknowledgment in
+ * the product documentation would be appreciated
+ * but is not required.
+ *
+ * 2. Altered source versions must be plainly marked
+ * as such, and must not be misrepresented as
+ * being the original software.
+ *
+ * 3. This notice may not be removed or altered from
+ * any source distribution.
+ */
+
+#include "tinf.h"
+
+#define FTEXT 1
+#define FHCRC 2
+#define FEXTRA 4
+#define FNAME 8
+#define FCOMMENT 16
+
+void tinf_skip_bytes(TINF_DATA *d, int num);
+uint16_t tinf_get_uint16(TINF_DATA *d);
+
+void tinf_skip_bytes(TINF_DATA *d, int num)
+{
+ while (num--) {
+ uzlib_get_byte(d);
+ }
+}
+
+uint16_t tinf_get_uint16(TINF_DATA *d)
+{
+ unsigned int v = uzlib_get_byte(d);
+ v = (uzlib_get_byte(d) << 8) | v;
+ return v;
+}
+
+int uzlib_gzip_parse_header(TINF_DATA *d)
+{
+ unsigned char flg;
+
+ /* -- check format -- */
+
+ /* check id bytes */
+ if (uzlib_get_byte(d) != 0x1f || uzlib_get_byte(d) != 0x8b) {
+ return TINF_DATA_ERROR;
+ }
+
+ /* check method is deflate */
+ if (uzlib_get_byte(d) != 8) {
+ return TINF_DATA_ERROR;
+ }
+
+ /* get flag byte */
+ flg = uzlib_get_byte(d);
+
+ /* check that reserved bits are zero */
+ if (flg & 0xe0) {
+ return TINF_DATA_ERROR;
+ }
+
+ /* -- find start of compressed data -- */
+
+ /* skip rest of base header of 10 bytes */
+ tinf_skip_bytes(d, 6);
+
+ /* skip extra data if present */
+ if (flg & FEXTRA) {
+ unsigned int xlen = tinf_get_uint16(d);
+ tinf_skip_bytes(d, xlen);
+ }
+
+ /* skip file name if present */
+ if (flg & FNAME) {
+ while (uzlib_get_byte(d))
+ ;
+ }
+
+ /* skip file comment if present */
+ if (flg & FCOMMENT) {
+ while (uzlib_get_byte(d))
+ ;
+ }
+
+ /* check header crc if present */
+ if (flg & FHCRC) {
+ /*unsigned int hcrc =*/tinf_get_uint16(d);
+
+ // TODO: Check!
+ // if (hcrc != (tinf_crc32(src, start - src) & 0x0000ffff))
+ // return TINF_DATA_ERROR;
+ }
+
+ /* initialize for crc32 checksum */
+ d->checksum_type = TINF_CHKSUM_CRC;
+ d->checksum = ~0;
+
+ return TINF_OK;
+}
diff --git a/tests/screenshots/src/uzlib/tinflate.c b/tests/screenshots/src/uzlib/tinflate.c
new file mode 100644
index 000000000..d6cb75a26
--- /dev/null
+++ b/tests/screenshots/src/uzlib/tinflate.c
@@ -0,0 +1,685 @@
+/*
+ * uzlib - tiny deflate/inflate library (deflate, gzip, zlib)
+ *
+ * Copyright (c) 2003 by Joergen Ibsen / Jibz
+ * All Rights Reserved
+ * http://www.ibsensoftware.com/
+ *
+ * Copyright (c) 2014-2018 by Paul Sokolovsky
+ *
+ * This software is provided 'as-is', without any express
+ * or implied warranty. In no event will the authors be
+ * held liable for any damages arising from the use of
+ * this software.
+ *
+ * Permission is granted to anyone to use this software
+ * for any purpose, including commercial applications,
+ * and to alter it and redistribute it freely, subject to
+ * the following restrictions:
+ *
+ * 1. The origin of this software must not be
+ * misrepresented; you must not claim that you
+ * wrote the original software. If you use this
+ * software in a product, an acknowledgment in
+ * the product documentation would be appreciated
+ * but is not required.
+ *
+ * 2. Altered source versions must be plainly marked
+ * as such, and must not be misrepresented as
+ * being the original software.
+ *
+ * 3. This notice may not be removed or altered from
+ * any source distribution.
+ */
+
+#include
+#include
+#include "tinf.h"
+
+#define UZLIB_DUMP_ARRAY(heading, arr, size) \
+ { \
+ printf("%s", heading); \
+ for (int i = 0; i < size; ++i) { \
+ printf(" %d", (arr)[i]); \
+ } \
+ printf("\n"); \
+ }
+
+uint32_t tinf_get_le_uint32(TINF_DATA *d);
+uint32_t tinf_get_be_uint32(TINF_DATA *d);
+
+/* --------------------------------------------------- *
+ * -- uninitialized global data (static structures) -- *
+ * --------------------------------------------------- */
+
+#ifdef RUNTIME_BITS_TABLES
+
+/* extra bits and base tables for length codes */
+unsigned char length_bits[30];
+unsigned short length_base[30];
+
+/* extra bits and base tables for distance codes */
+unsigned char dist_bits[30];
+unsigned short dist_base[30];
+
+#else
+
+const unsigned char length_bits[30]
+ = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5};
+const unsigned short length_base[30]
+ = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27,
+ 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258};
+
+const unsigned char dist_bits[30] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6,
+ 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
+const unsigned short dist_base[30]
+ = {1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129,
+ 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577};
+
+#endif
+
+/* special ordering of code length codes */
+const unsigned char clcidx[] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15};
+
+/* ----------------------- *
+ * -- utility functions -- *
+ * ----------------------- */
+
+#ifdef RUNTIME_BITS_TABLES
+/* build extra bits and base tables */
+static void tinf_build_bits_base(unsigned char *bits, unsigned short *base, int delta, int first)
+{
+ int i, sum;
+
+ /* build bits table */
+ for (i = 0; i < delta; ++i) {
+ bits[i] = 0;
+ }
+ for (i = 0; i < 30 - delta; ++i) {
+ bits[i + delta] = i / delta;
+ }
+
+ /* build base table */
+ for (sum = first, i = 0; i < 30; ++i) {
+ base[i] = sum;
+ sum += 1 << bits[i];
+ }
+}
+#endif
+
+/* build the fixed huffman trees */
+static void tinf_build_fixed_trees(TINF_TREE *lt, TINF_TREE *dt)
+{
+ int i;
+
+ /* build fixed length tree */
+ for (i = 0; i < 7; ++i) {
+ lt->table[i] = 0;
+ }
+
+ lt->table[7] = 24;
+ lt->table[8] = 152;
+ lt->table[9] = 112;
+
+ for (i = 0; i < 24; ++i) {
+ lt->trans[i] = 256 + i;
+ }
+ for (i = 0; i < 144; ++i) {
+ lt->trans[24 + i] = i;
+ }
+ for (i = 0; i < 8; ++i) {
+ lt->trans[24 + 144 + i] = 280 + i;
+ }
+ for (i = 0; i < 112; ++i) {
+ lt->trans[24 + 144 + 8 + i] = 144 + i;
+ }
+
+ /* build fixed distance tree */
+ for (i = 0; i < 5; ++i) {
+ dt->table[i] = 0;
+ }
+
+ dt->table[5] = 32;
+
+ for (i = 0; i < 32; ++i) {
+ dt->trans[i] = i;
+ }
+}
+
+/* given an array of code lengths, build a tree */
+static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned int num)
+{
+ unsigned short offs[16];
+ unsigned int i, sum;
+
+ /* clear code length count table */
+ for (i = 0; i < 16; ++i) {
+ t->table[i] = 0;
+ }
+
+ /* scan symbol lengths, and sum code length counts */
+ for (i = 0; i < num; ++i) {
+ t->table[lengths[i]]++;
+ }
+
+#if UZLIB_CONF_DEBUG_LOG >= 2
+ UZLIB_DUMP_ARRAY("codelen counts:", t->table, TINF_ARRAY_SIZE(t->table));
+#endif
+
+ /* In the lengths array, 0 means unused code. So, t->table[0] now contains
+ number of unused codes. But table's purpose is to contain # of codes of
+ particular length, and there're 0 codes of length 0. */
+ t->table[0] = 0;
+
+ /* compute offset table for distribution sort */
+ for (sum = 0, i = 0; i < 16; ++i) {
+ offs[i] = sum;
+ sum += t->table[i];
+ }
+
+#if UZLIB_CONF_DEBUG_LOG >= 2
+ UZLIB_DUMP_ARRAY("codelen offsets:", offs, TINF_ARRAY_SIZE(offs));
+#endif
+
+ /* create code->symbol translation table (symbols sorted by code) */
+ for (i = 0; i < num; ++i) {
+ if (lengths[i]) {
+ t->trans[offs[lengths[i]]++] = i;
+ }
+ }
+}
+
+/* ---------------------- *
+ * -- decode functions -- *
+ * ---------------------- */
+
+unsigned char uzlib_get_byte(TINF_DATA *d)
+{
+ /* If end of source buffer is not reached, return next byte from source
+ buffer. */
+ if (d->source < d->source_limit) {
+ return *d->source++;
+ }
+
+ /* Otherwise if there's callback and we haven't seen EOF yet, try to
+ read next byte using it. (Note: the callback can also update ->source
+ and ->source_limit). */
+ if (d->readSource && !d->eof) {
+ int val = d->readSource(d);
+ if (val >= 0) {
+ return (unsigned char) val;
+ }
+ }
+
+ /* Otherwise, we hit EOF (either from ->readSource() or from exhaustion
+ of the buffer), and it will be "sticky", i.e. further calls to this
+ function will end up here too. */
+ d->eof = true;
+
+ return 0;
+}
+
+uint32_t tinf_get_le_uint32(TINF_DATA *d)
+{
+ uint32_t val = 0;
+ int i;
+ for (i = 4; i--;) {
+ val = val >> 8 | ((uint32_t) uzlib_get_byte(d)) << 24;
+ }
+ return val;
+}
+
+uint32_t tinf_get_be_uint32(TINF_DATA *d)
+{
+ uint32_t val = 0;
+ int i;
+ for (i = 4; i--;) {
+ val = val << 8 | uzlib_get_byte(d);
+ }
+ return val;
+}
+
+/* get one bit from source stream */
+static int tinf_getbit(TINF_DATA *d)
+{
+ unsigned int bit;
+
+ /* check if tag is empty */
+ if (!d->bitcount--) {
+ /* load next tag */
+ d->tag = uzlib_get_byte(d);
+ d->bitcount = 7;
+ }
+
+ /* shift bit out of tag */
+ bit = d->tag & 0x01;
+ d->tag >>= 1;
+
+ return bit;
+}
+
+/* read a num bit value from a stream and add base */
+static unsigned int tinf_read_bits(TINF_DATA *d, int num, int base)
+{
+ unsigned int val = 0;
+
+ /* read num bits */
+ if (num) {
+ unsigned int limit = 1 << (num);
+ unsigned int mask;
+
+ for (mask = 1; mask < limit; mask *= 2) {
+ if (tinf_getbit(d)) {
+ val += mask;
+ }
+ }
+ }
+
+ return val + base;
+}
+
+/* given a data stream and a tree, decode a symbol */
+static int tinf_decode_symbol(TINF_DATA *d, TINF_TREE *t)
+{
+ int sum = 0, cur = 0, len = 0;
+
+ /* get more bits while code value is above sum */
+ do {
+ cur = 2 * cur + tinf_getbit(d);
+
+ if (++len == TINF_ARRAY_SIZE(t->table)) {
+ return TINF_DATA_ERROR;
+ }
+
+ sum += t->table[len];
+ cur -= t->table[len];
+
+ } while (cur >= 0);
+
+ sum += cur;
+#if UZLIB_CONF_PARANOID_CHECKS
+ if (sum < 0 || sum >= TINF_ARRAY_SIZE(t->trans)) {
+ return TINF_DATA_ERROR;
+ }
+#endif
+
+ return t->trans[sum];
+}
+
+/* given a data stream, decode dynamic trees from it */
+static int tinf_decode_trees(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt)
+{
+ /* code lengths for 288 literal/len symbols and 32 dist symbols */
+ unsigned char lengths[288 + 32];
+ unsigned int hlit, hdist, hclen, hlimit;
+ unsigned int i, num, length;
+
+ /* get 5 bits HLIT (257-286) */
+ hlit = tinf_read_bits(d, 5, 257);
+
+ /* get 5 bits HDIST (1-32) */
+ hdist = tinf_read_bits(d, 5, 1);
+
+ /* get 4 bits HCLEN (4-19) */
+ hclen = tinf_read_bits(d, 4, 4);
+
+ for (i = 0; i < 19; ++i) {
+ lengths[i] = 0;
+ }
+
+ /* read code lengths for code length alphabet */
+ for (i = 0; i < hclen; ++i) {
+ /* get 3 bits code length (0-7) */
+ unsigned int clen = tinf_read_bits(d, 3, 0);
+
+ lengths[clcidx[i]] = clen;
+ }
+
+ /* build code length tree, temporarily use length tree */
+ tinf_build_tree(lt, lengths, 19);
+
+ /* decode code lengths for the dynamic trees */
+ hlimit = hlit + hdist;
+ for (num = 0; num < hlimit;) {
+ int sym = tinf_decode_symbol(d, lt);
+ unsigned char fill_value = 0;
+ int lbits, lbase = 3;
+
+ /* error decoding */
+ if (sym < 0) {
+ return sym;
+ }
+
+ switch (sym) {
+ case 16:
+ /* copy previous code length 3-6 times (read 2 bits) */
+ if (num == 0) {
+ return TINF_DATA_ERROR;
+ }
+ fill_value = lengths[num - 1];
+ lbits = 2;
+ break;
+ case 17:
+ /* repeat code length 0 for 3-10 times (read 3 bits) */
+ lbits = 3;
+ break;
+ case 18:
+ /* repeat code length 0 for 11-138 times (read 7 bits) */
+ lbits = 7;
+ lbase = 11;
+ break;
+ default:
+ /* values 0-15 represent the actual code lengths */
+ lengths[num++] = sym;
+ /* continue the for loop */
+ continue;
+ }
+
+ /* special code length 16-18 are handled here */
+ length = tinf_read_bits(d, lbits, lbase);
+ if (num + length > hlimit) {
+ return TINF_DATA_ERROR;
+ }
+ for (; length; --length) {
+ lengths[num++] = fill_value;
+ }
+ }
+
+#if UZLIB_CONF_DEBUG_LOG >= 2
+ printf("lit code lengths (%d):", hlit);
+ UZLIB_DUMP_ARRAY("", lengths, hlit);
+ printf("dist code lengths (%d):", hdist);
+ UZLIB_DUMP_ARRAY("", lengths + hlit, hdist);
+#endif
+
+#if UZLIB_CONF_PARANOID_CHECKS
+ /* Check that there's "end of block" symbol */
+ if (lengths[256] == 0) {
+ return TINF_DATA_ERROR;
+ }
+#endif
+
+ /* build dynamic trees */
+ tinf_build_tree(lt, lengths, hlit);
+ tinf_build_tree(dt, lengths + hlit, hdist);
+
+ return TINF_OK;
+}
+
+/* ----------------------------- *
+ * -- block inflate functions -- *
+ * ----------------------------- */
+
+/* given a stream and two trees, inflate next chunk of output (a byte or more) */
+static int tinf_inflate_block_data(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt)
+{
+ if (d->curlen == 0) {
+ unsigned int offs;
+ int dist;
+ int sym = tinf_decode_symbol(d, lt);
+ // printf("huff sym: %02x\n", sym);
+
+ if (d->eof) {
+ return TINF_DATA_ERROR;
+ }
+
+ /* literal byte */
+ if (sym < 256) {
+ TINF_PUT(d, sym);
+ return TINF_OK;
+ }
+
+ /* end of block */
+ if (sym == 256) {
+ return TINF_DONE;
+ }
+
+ /* substring from sliding dictionary */
+ sym -= 257;
+ if (sym >= 29) {
+ return TINF_DATA_ERROR;
+ }
+
+ /* possibly get more bits from length code */
+ d->curlen = tinf_read_bits(d, length_bits[sym], length_base[sym]);
+
+ dist = tinf_decode_symbol(d, dt);
+ if (dist >= 30) {
+ return TINF_DATA_ERROR;
+ }
+
+ /* possibly get more bits from distance code */
+ offs = tinf_read_bits(d, dist_bits[dist], dist_base[dist]);
+
+ /* calculate and validate actual LZ offset to use */
+ if (d->dict_ring) {
+ if (offs > d->dict_size) {
+ return TINF_DICT_ERROR;
+ }
+ /* Note: unlike full-dest-in-memory case below, we don't
+ try to catch offset which points to not yet filled
+ part of the dictionary here. Doing so would require
+ keeping another variable to track "filled in" size
+ of the dictionary. Appearance of such an offset cannot
+ lead to accessing memory outside of the dictionary
+ buffer, and clients which don't want to leak unrelated
+ information, should explicitly initialize dictionary
+ buffer passed to uzlib. */
+
+ d->lzOff = d->dict_idx - offs;
+ if (d->lzOff < 0) {
+ d->lzOff += d->dict_size;
+ }
+ }
+ else {
+ /* catch trying to point before the start of dest buffer */
+ if (offs > d->dest - d->destStart) {
+ return TINF_DATA_ERROR;
+ }
+ d->lzOff = -offs;
+ }
+ }
+
+ /* copy next byte from dict substring */
+ if (d->dict_ring) {
+ TINF_PUT(d, d->dict_ring[d->lzOff]);
+ if ((unsigned) ++d->lzOff == d->dict_size) {
+ d->lzOff = 0;
+ }
+ }
+ else {
+#if UZLIB_CONF_USE_MEMCPY
+ /* copy as much as possible, in one memcpy() call */
+ unsigned int to_copy = d->curlen, dest_len = d->dest_limit - d->dest;
+ if (to_copy > dest_len) {
+ to_copy = dest_len;
+ }
+ memcpy(d->dest, d->dest + d->lzOff, to_copy);
+ d->dest += to_copy;
+ d->curlen -= to_copy;
+ return TINF_OK;
+#else
+ d->dest[0] = d->dest[d->lzOff];
+ d->dest++;
+#endif
+ }
+ d->curlen--;
+ return TINF_OK;
+}
+
+/* inflate next byte from uncompressed block of data */
+static int tinf_inflate_uncompressed_block(TINF_DATA *d)
+{
+ if (d->curlen == 0) {
+ unsigned int length, invlength;
+
+ /* get length */
+ length = uzlib_get_byte(d);
+ length += 256 * uzlib_get_byte(d);
+ /* get one's complement of length */
+ invlength = uzlib_get_byte(d);
+ invlength += 256 * uzlib_get_byte(d);
+ /* check length */
+ if (length != (~invlength & 0x0000ffff)) {
+ return TINF_DATA_ERROR;
+ }
+
+ /* increment length to properly return TINF_DONE below, without
+ producing data at the same time */
+ d->curlen = length + 1;
+
+ /* make sure we start next block on a byte boundary */
+ d->bitcount = 0;
+ }
+
+ if (--d->curlen == 0) {
+ return TINF_DONE;
+ }
+
+ unsigned char c = uzlib_get_byte(d);
+ TINF_PUT(d, c);
+ return TINF_OK;
+}
+
+/* ---------------------- *
+ * -- public functions -- *
+ * ---------------------- */
+
+/* initialize global (static) data */
+void uzlib_init(void)
+{
+#ifdef RUNTIME_BITS_TABLES
+ /* build extra bits and base tables */
+ tinf_build_bits_base(length_bits, length_base, 4, 3);
+ tinf_build_bits_base(dist_bits, dist_base, 2, 1);
+
+ /* fix a special case */
+ length_bits[28] = 0;
+ length_base[28] = 258;
+#endif
+}
+
+/* initialize decompression structure */
+void uzlib_uncompress_init(TINF_DATA *d, void *dict, unsigned int dictLen)
+{
+ d->eof = 0;
+ d->bitcount = 0;
+ d->bfinal = 0;
+ d->btype = -1;
+ d->dict_size = dictLen;
+ d->dict_ring = dict;
+ d->dict_idx = 0;
+ d->curlen = 0;
+}
+
+/* inflate next output bytes from compressed stream */
+int uzlib_uncompress(TINF_DATA *d)
+{
+ do {
+ int res;
+
+ /* start a new block */
+ if (d->btype == -1) {
+ int old_btype;
+ next_blk:
+ old_btype = d->btype;
+ /* read final block flag */
+ d->bfinal = tinf_getbit(d);
+ /* read block type (2 bits) */
+ d->btype = tinf_read_bits(d, 2, 0);
+
+#if UZLIB_CONF_DEBUG_LOG >= 1
+ printf("Started new block: type=%d final=%d\n", d->btype, d->bfinal);
+#endif
+
+ if (d->btype == 1 && old_btype != 1) {
+ /* build fixed huffman trees */
+ tinf_build_fixed_trees(&d->ltree, &d->dtree);
+ }
+ else if (d->btype == 2) {
+ /* decode trees from stream */
+ res = tinf_decode_trees(d, &d->ltree, &d->dtree);
+ if (res != TINF_OK) {
+ return res;
+ }
+ }
+ }
+
+ /* process current block */
+ switch (d->btype) {
+ case 0:
+ /* decompress uncompressed block */
+ res = tinf_inflate_uncompressed_block(d);
+ break;
+ case 1:
+ case 2:
+ /* decompress block with fixed/dynamic huffman trees */
+ /* trees were decoded previously, so it's the same routine for both */
+ res = tinf_inflate_block_data(d, &d->ltree, &d->dtree);
+ break;
+ default:
+ return TINF_DATA_ERROR;
+ }
+
+ if (res == TINF_DONE && !d->bfinal) {
+ /* the block has ended (without producing more data), but we
+ can't return without data, so start processing next block */
+ goto next_blk;
+ }
+
+ if (res != TINF_OK) {
+ return res;
+ }
+
+ } while (d->dest < d->dest_limit);
+
+ return TINF_OK;
+}
+
+/* inflate next output bytes from compressed stream, updating
+ checksum, and at the end of stream, verify it */
+int uzlib_uncompress_chksum(TINF_DATA *d)
+{
+ int res;
+ unsigned char *data = d->dest;
+
+ res = uzlib_uncompress(d);
+
+ if (res < 0) {
+ return res;
+ }
+
+ switch (d->checksum_type) {
+ case TINF_CHKSUM_ADLER:
+ d->checksum = uzlib_adler32(data, d->dest - data, d->checksum);
+ break;
+
+ case TINF_CHKSUM_CRC:
+ d->checksum = uzlib_crc32(data, d->dest - data, d->checksum);
+ break;
+ }
+
+ if (res == TINF_DONE) {
+ unsigned int val;
+
+ switch (d->checksum_type) {
+ case TINF_CHKSUM_ADLER:
+ val = tinf_get_be_uint32(d);
+ if (d->checksum != val) {
+ return TINF_CHKSUM_ERROR;
+ }
+ break;
+
+ case TINF_CHKSUM_CRC:
+ val = tinf_get_le_uint32(d);
+ if (~d->checksum != val) {
+ return TINF_CHKSUM_ERROR;
+ }
+ // Uncompressed size. TODO: Check
+ val = tinf_get_le_uint32(d);
+ break;
+ }
+ }
+
+ return res;
+}
diff --git a/tests/screenshots/src/uzlib/tinfzlib.c b/tests/screenshots/src/uzlib/tinfzlib.c
new file mode 100644
index 000000000..b6efff3fa
--- /dev/null
+++ b/tests/screenshots/src/uzlib/tinfzlib.c
@@ -0,0 +1,74 @@
+/*
+ * uzlib - tiny deflate/inflate library (deflate, gzip, zlib)
+ *
+ * Copyright (c) 2003 by Joergen Ibsen / Jibz
+ * All Rights Reserved
+ *
+ * http://www.ibsensoftware.com/
+ *
+ * Copyright (c) 2014-2018 by Paul Sokolovsky
+ *
+ * This software is provided 'as-is', without any express
+ * or implied warranty. In no event will the authors be
+ * held liable for any damages arising from the use of
+ * this software.
+ *
+ * Permission is granted to anyone to use this software
+ * for any purpose, including commercial applications,
+ * and to alter it and redistribute it freely, subject to
+ * the following restrictions:
+ *
+ * 1. The origin of this software must not be
+ * misrepresented; you must not claim that you
+ * wrote the original software. If you use this
+ * software in a product, an acknowledgment in
+ * the product documentation would be appreciated
+ * but is not required.
+ *
+ * 2. Altered source versions must be plainly marked
+ * as such, and must not be misrepresented as
+ * being the original software.
+ *
+ * 3. This notice may not be removed or altered from
+ * any source distribution.
+ */
+
+#include "tinf.h"
+
+int uzlib_zlib_parse_header(TINF_DATA *d)
+{
+ unsigned char cmf, flg;
+
+ /* -- get header bytes -- */
+
+ cmf = uzlib_get_byte(d);
+ flg = uzlib_get_byte(d);
+
+ /* -- check format -- */
+
+ /* check checksum */
+ if ((256 * cmf + flg) % 31) {
+ return TINF_DATA_ERROR;
+ }
+
+ /* check method is deflate */
+ if ((cmf & 0x0f) != 8) {
+ return TINF_DATA_ERROR;
+ }
+
+ /* check window size is valid */
+ if ((cmf >> 4) > 7) {
+ return TINF_DATA_ERROR;
+ }
+
+ /* check there is no preset dictionary */
+ if (flg & 0x20) {
+ return TINF_DATA_ERROR;
+ }
+
+ /* initialize for adler32 checksum */
+ d->checksum_type = TINF_CHKSUM_ADLER;
+ d->checksum = 1;
+
+ return cmf >> 4;
+}
diff --git a/tests/screenshots/src/uzlib/uzlib.h b/tests/screenshots/src/uzlib/uzlib.h
new file mode 100644
index 000000000..afbdae141
--- /dev/null
+++ b/tests/screenshots/src/uzlib/uzlib.h
@@ -0,0 +1,177 @@
+/*
+ * uzlib - tiny deflate/inflate library (deflate, gzip, zlib)
+ *
+ * Copyright (c) 2003 by Joergen Ibsen / Jibz
+ * All Rights Reserved
+ * http://www.ibsensoftware.com/
+ *
+ * Copyright (c) 2014-2018 by Paul Sokolovsky
+ *
+ * This software is provided 'as-is', without any express
+ * or implied warranty. In no event will the authors be
+ * held liable for any damages arising from the use of
+ * this software.
+ *
+ * Permission is granted to anyone to use this software
+ * for any purpose, including commercial applications,
+ * and to alter it and redistribute it freely, subject to
+ * the following restrictions:
+ *
+ * 1. The origin of this software must not be
+ * misrepresented; you must not claim that you
+ * wrote the original software. If you use this
+ * software in a product, an acknowledgment in
+ * the product documentation would be appreciated
+ * but is not required.
+ *
+ * 2. Altered source versions must be plainly marked
+ * as such, and must not be misrepresented as
+ * being the original software.
+ *
+ * 3. This notice may not be removed or altered from
+ * any source distribution.
+ */
+
+#ifndef UZLIB_H_INCLUDED
+#define UZLIB_H_INCLUDED
+
+#include
+#include
+#include
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "uzlib_conf.h"
+#if UZLIB_CONF_DEBUG_LOG
+#include
+#endif
+
+/* calling convention */
+#ifndef TINFCC
+#ifdef __WATCOMC__
+#define TINFCC __cdecl
+#else
+#define TINFCC
+#endif
+#endif
+
+/* ok status, more data produced */
+#define TINF_OK 0
+/* end of compressed stream reached */
+#define TINF_DONE 1
+#define TINF_DATA_ERROR (-3)
+#define TINF_CHKSUM_ERROR (-4)
+#define TINF_DICT_ERROR (-5)
+
+/* checksum types */
+#define TINF_CHKSUM_NONE 0
+#define TINF_CHKSUM_ADLER 1
+#define TINF_CHKSUM_CRC 2
+
+/* helper macros */
+#define TINF_ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*(arr)))
+
+/* data structures */
+
+typedef struct {
+ unsigned short table[16]; /* table of code length counts */
+ unsigned short trans[288]; /* code -> symbol translation table */
+} TINF_TREE;
+
+struct uzlib_uncomp {
+ /* Pointer to the next byte in the input buffer */
+ const unsigned char *source;
+ /* Pointer to the next byte past the input buffer (source_limit = source + len) */
+ const unsigned char *source_limit;
+ /* If source_limit == NULL, or source >= source_limit, this function
+ will be used to read next byte from source stream. The function may
+ also return -1 in case of EOF (or irrecoverable error). Note that
+ besides returning the next byte, it may also update source and
+ source_limit fields, thus allowing for buffered operation. */
+ int (*source_read_cb)(struct uzlib_uncomp *uncomp);
+
+ unsigned int tag;
+ unsigned int bitcount;
+
+ /* Destination (output) buffer start */
+ unsigned char *dest_start;
+ /* Current pointer in dest buffer */
+ unsigned char *dest;
+ /* Pointer past the end of the dest buffer, similar to source_limit */
+ unsigned char *dest_limit;
+
+ /* Accumulating checksum */
+ unsigned int checksum;
+ char checksum_type;
+ bool eof;
+
+ int btype;
+ int bfinal;
+ unsigned int curlen;
+ int lzOff;
+ unsigned char *dict_ring;
+ unsigned int dict_size;
+ unsigned int dict_idx;
+
+ TINF_TREE ltree; /* dynamic length/symbol tree */
+ TINF_TREE dtree; /* dynamic distance tree */
+};
+
+#include "tinf_compat.h"
+
+#define TINF_PUT(d, c) \
+ { \
+ *d->dest++ = c; \
+ if (d->dict_ring) { \
+ d->dict_ring[d->dict_idx++] = c; \
+ if (d->dict_idx == d->dict_size) \
+ d->dict_idx = 0; \
+ } \
+ }
+
+unsigned char TINFCC uzlib_get_byte(TINF_DATA *d);
+
+/* Decompression API */
+
+void TINFCC uzlib_init(void);
+void TINFCC uzlib_uncompress_init(TINF_DATA *d, void *dict, unsigned int dictLen);
+int TINFCC uzlib_uncompress(TINF_DATA *d);
+int TINFCC uzlib_uncompress_chksum(TINF_DATA *d);
+
+int TINFCC uzlib_zlib_parse_header(TINF_DATA *d);
+int TINFCC uzlib_gzip_parse_header(TINF_DATA *d);
+
+/* Compression API */
+
+typedef const uint8_t *uzlib_hash_entry_t;
+
+struct uzlib_comp {
+ unsigned char *outbuf;
+ int outlen, outsize;
+ unsigned long outbits;
+ int noutbits;
+ int comp_disabled;
+
+ uzlib_hash_entry_t *hash_table;
+ unsigned int hash_bits;
+ unsigned int dict_size;
+};
+
+void TINFCC uzlib_compress(struct uzlib_comp *c, const uint8_t *src, unsigned slen);
+
+#include "defl_static.h"
+
+/* Checksum API */
+
+/* prev_sum is previous value for incremental computation, 1 initially */
+uint32_t TINFCC uzlib_adler32(const void *data, unsigned int length, uint32_t prev_sum);
+/* crc is previous value for incremental computation, 0xffffffff initially */
+uint32_t TINFCC uzlib_crc32(const void *data, unsigned int length, uint32_t crc);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* UZLIB_H_INCLUDED */
diff --git a/tests/screenshots/src/uzlib/uzlib_conf.h b/tests/screenshots/src/uzlib/uzlib_conf.h
new file mode 100644
index 000000000..fd67dec8a
--- /dev/null
+++ b/tests/screenshots/src/uzlib/uzlib_conf.h
@@ -0,0 +1,32 @@
+/*
+ * uzlib - tiny deflate/inflate library (deflate, gzip, zlib)
+ *
+ * Copyright (c) 2014-2018 by Paul Sokolovsky
+ */
+
+#ifndef UZLIB_CONF_H_INCLUDED
+#define UZLIB_CONF_H_INCLUDED
+
+#ifndef UZLIB_CONF_DEBUG_LOG
+/* Debug logging level 0, 1, 2, etc. */
+#define UZLIB_CONF_DEBUG_LOG 0
+#endif
+
+#ifndef UZLIB_CONF_PARANOID_CHECKS
+/* Perform extra checks on the input stream, even if they aren't proven
+ to be strictly required (== lack of them wasn't proven to lead to
+ crashes). */
+#define UZLIB_CONF_PARANOID_CHECKS 0
+#endif
+
+#ifndef UZLIB_CONF_USE_MEMCPY
+/* Use memcpy() for copying data out of LZ window or uncompressed blocks,
+ instead of doing this byte by byte. For well-compressed data, this
+ may noticeably increase decompression speed. But for less compressed,
+ it can actually deteriorate it (due to the fact that many memcpy()
+ implementations are optimized for large blocks of data, and have
+ too much overhead for short strings of just a few bytes). */
+#define UZLIB_CONF_USE_MEMCPY 0
+#endif
+
+#endif /* UZLIB_CONF_H_INCLUDED */
diff --git a/tests/screenshots/stax.properties b/tests/screenshots/stax.properties
new file mode 100644
index 000000000..cc256df8e
--- /dev/null
+++ b/tests/screenshots/stax.properties
@@ -0,0 +1,13 @@
+{
+ "devicename": "STAX-9DAB",
+ "bootloader_version": "2.2",
+ "seph_version": "1.95",
+ "seph_serial": "2100200782107028",
+ "os_version": "2.0.1",
+
+ "autoboot": true,
+
+ "piezosound": 0,
+
+ "features": 4,
+}