From d7d2f8bad5c0c529e629de8434f77e3a094173f9 Mon Sep 17 00:00:00 2001 From: Rudi Date: Fri, 21 Aug 2026 02:51:24 -0400 Subject: [PATCH] TLS and Graphics, what more? --- README.md | 14 +- build/j3270/org/pubvm/j3270/J3270App.class | Bin 17354 -> 20394 bytes .../org/pubvm/j3270/config/Settings.class | Bin 9784 -> 11522 bytes .../org/pubvm/j3270/ui/ConnectDialog.class | Bin 5795 -> 7494 bytes .../org/pubvm/j3270/ui/SettingsDialog$1.class | Bin 1599 -> 1599 bytes .../org/pubvm/j3270/ui/SettingsDialog$2.class | Bin 1802 -> 1802 bytes .../org/pubvm/j3270/ui/SettingsDialog$3.class | Bin 665 -> 665 bytes .../org/pubvm/j3270/ui/SettingsDialog$4.class | Bin 1355 -> 1355 bytes .../org/pubvm/j3270/ui/SettingsDialog.class | Bin 21254 -> 21762 bytes .../org/pubvm/j3270/ui/StatusBar$1.class | Bin 1152 -> 1152 bytes .../j3270/org/pubvm/j3270/ui/StatusBar.class | Bin 4176 -> 5330 bytes .../org/pubvm/j3270/ui/TerminalPanel.class | Bin 24150 -> 24926 bytes .../org/lib3270j/ConnectionConfig.class | Bin 2493 -> 5382 bytes .../org/lib3270j/Telnet3270Client.class | Bin 6945 -> 7945 bytes .../lib3270j/charset/EbcdicTranslator.class | Bin 3139 -> 3269 bytes .../datastream/DataStreamProcessor.class | Bin 15410 -> 17822 bytes .../datastream/QueryReplyBuilder.class | Bin 4801 -> 8580 bytes .../org/lib3270j/input/InputProcessor.class | Bin 9643 -> 9896 bytes .../lib3270j/protocol/DS3270Constants.class | Bin 9902 -> 10220 bytes .../lib3270j/telnet/TelnetConnection.class | Bin 6374 -> 7262 bytes .../main/java/org/pubvm/j3270/J3270App.java | 109 ++- .../java/org/pubvm/j3270/config/Settings.java | 64 +- .../org/pubvm/j3270/ui/ConnectDialog.java | 67 +- .../org/pubvm/j3270/ui/SettingsDialog.java | 19 +- .../java/org/pubvm/j3270/ui/StatusBar.java | 24 + .../org/pubvm/j3270/ui/TerminalPanel.java | 30 +- .../j3270/ui/UntrustedCertificateDialog.java | 153 ++++ .../java/org/lib3270j/ConnectionConfig.java | 81 ++ .../java/org/lib3270j/Telnet3270Client.java | 24 + .../lib3270j/charset/EbcdicTranslator.java | 14 + .../datastream/DataStreamProcessor.java | 111 +++ .../datastream/QueryReplyBuilder.java | 306 +++++-- .../org/lib3270j/graphics/GocaConstants.java | 173 ++++ .../org/lib3270j/graphics/GocaDecoder.java | 819 ++++++++++++++++++ .../org/lib3270j/graphics/GraphicsMode.java | 70 ++ .../org/lib3270j/graphics/GraphicsPlane.java | 400 +++++++++ .../graphics/ProgramSymbolManager.java | 268 ++++++ .../lib3270j/graphics/ProgramSymbolSet.java | 148 ++++ .../lib3270j/graphics/VectorSymbolData.java | 21 + .../org/lib3270j/input/InputProcessor.java | 9 + .../lib3270j/protocol/DS3270Constants.java | 11 +- .../org/lib3270j/telnet/TelnetConnection.java | 36 +- .../lib3270j/tls/TlsCertificateVerifier.java | 22 + .../org/lib3270j/tls/TlsTrustManager.java | 114 +++ .../datastream/QueryReplyBuilderTest.java | 60 +- .../lib3270j/graphics/GocaDecoderTest.java | 237 +++++ .../lib3270j/graphics/GraphicsModeTest.java | 57 ++ .../graphics/ProgramSymbolManagerTest.java | 144 +++ .../java/org/lib3270j/tls/TlsConfigTest.java | 108 +++ 49 files changed, 3611 insertions(+), 102 deletions(-) create mode 100644 j3270/src/main/java/org/pubvm/j3270/ui/UntrustedCertificateDialog.java create mode 100644 lib3270j/src/main/java/org/lib3270j/graphics/GocaConstants.java create mode 100644 lib3270j/src/main/java/org/lib3270j/graphics/GocaDecoder.java create mode 100644 lib3270j/src/main/java/org/lib3270j/graphics/GraphicsMode.java create mode 100644 lib3270j/src/main/java/org/lib3270j/graphics/GraphicsPlane.java create mode 100644 lib3270j/src/main/java/org/lib3270j/graphics/ProgramSymbolManager.java create mode 100644 lib3270j/src/main/java/org/lib3270j/graphics/ProgramSymbolSet.java create mode 100644 lib3270j/src/main/java/org/lib3270j/graphics/VectorSymbolData.java create mode 100644 lib3270j/src/main/java/org/lib3270j/tls/TlsCertificateVerifier.java create mode 100644 lib3270j/src/main/java/org/lib3270j/tls/TlsTrustManager.java create mode 100644 lib3270j/src/test/java/org/lib3270j/graphics/GocaDecoderTest.java create mode 100644 lib3270j/src/test/java/org/lib3270j/graphics/GraphicsModeTest.java create mode 100644 lib3270j/src/test/java/org/lib3270j/graphics/ProgramSymbolManagerTest.java create mode 100644 lib3270j/src/test/java/org/lib3270j/tls/TlsConfigTest.java diff --git a/README.md b/README.md index 26d1ab9..4017d15 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,14 @@ Requires **Java 11 or higher** (Java 11, 17, 21+): # Launch interactive GUI java -jar j3270.jar +# Connect to TLS/SSL mainframe with custom port +java -jar j3270.jar -s mainframe.example.com 992 +# Or using standard x3270 L: prefix +java -jar j3270.jar L:mainframe.example.com:992 + +# Connect with unverified/self-signed certificate verification bypass +java -jar j3270.jar --tls --insecure mainframe.example.com 992 + # Launch with custom configuration file java -jar j3270.jar -c config.ini @@ -36,6 +44,10 @@ java -jar j3270.jar -c config.ini ## ✨ Features - **TN3270 & TN3270E Protocol Support**: RFC 2355 compliant state machine, negotiation, Device-Type query, and SSL/TLS encryption. +- **SSL/TLS Security**: + - Encrypted TN3270 over TLS connections on standard port `992` or custom ports. + - Interactive certificate verification prompt for self-signed or untrusted certificates with fingerprint, subject, issuer, and validity inspection. + - Optional unverified/insecure mode for testing and headless scripting. - **IND$FILE File Transfer**: Full support for both **CUT** and **DFT (DDM)** high-speed structured field transfers with ASCII/binary translation, CRLF handling, and recfm/lrecl formatting for TSO, VM/CMS, and CICS. - **z/VM & Line-Mode Support**: Proper SSCP-LU and unformatted line handling (`CP TERM CONMODE 3270` supported). - **APL & Graphic Escape**: Comprehensive box-drawing and math symbol character rendering. @@ -59,7 +71,7 @@ The project includes self-contained, portable build scripts: # Build executable JAR in 2 seconds: sh ./build_all.sh -# Run all 25 automated unit tests in ~130ms: +# Run all 31 automated unit tests in ~180ms: sh ./test_all.sh ``` diff --git a/build/j3270/org/pubvm/j3270/J3270App.class b/build/j3270/org/pubvm/j3270/J3270App.class index 7251b122e971be509f672bf641c92996f80353b5..ea768d48c60c34813d2e63b108fa72a68bd354ce 100644 GIT binary patch delta 10198 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 P_#Z2r{3(BiFzx(bSe%XB diff --git a/build/j3270/org/pubvm/j3270/ui/ConnectDialog.class b/build/j3270/org/pubvm/j3270/ui/ConnectDialog.class index e8ba19b84576350a7c485f813e8b1bd7f28870f4..a5dadfe61a5fd099142711e592174309f22725ae 100644 GIT binary patch delta 3770 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 Zwfs-bLc_ClJiCm!bdF~OT~1fP`9CIMk*WXy delta 2059 zcmZvcdvsh?6~=%2PBJ%h=OmrZKY?Rgq6{~{yqAG$~D^&3TDpniNeP@QpAFMlb z9^ZbPv-ke?IoiCd(K+_kp(lY4%i?{`wf-KP`-RA+u5De3uEA`gWpjG4t!pSfkaf6^ zT?U&6g_62U>sQ%4RMgy_$PD(THY8e?4rH_GR2dHo_Of&;mF!lt-K_S8c>x-E%ogV%-i@co7Z9CSlvYsz+$GmJ9k_LnV=9uoY^x>DWA&0&Q74wZb<(9M(X z(ZJm4M-1QM5+Sgmzqes@!$uRgS|jXABd(>AqxQhSU5{voD&5CU=hSEVsUV zhTk&clM;7jdAyy|;x}T`srkqxvuy(%U46*_js%Q25>&K1om$`9- zNu^Tnxr-T@Dc&yb?HY}or#rtS9GN3?ZD}$xPcAs{N+=>F3x!&@W^%3HmJ5YyT~@ox z!(s8eYs1loDar^-&cWgg;F^Y?+Ek)IA)I&#zRh62Fd9DB~OqQwb5} zY(*=Xrhdw)@U+2qLJ4ft{g)2ZM2^ewIF+5_+}Bw;$^)a+GzX%AaT+^A)!sKsB1h9W zyF0^sMtP*!i8^^g?yMIi83*jXCy4o!BAvL5`8U)z>|%U^JMwS`yN) zXcdf>ct*kDyj4)G#`9LuKs1=+8&~S=cyru0LVMgEVMV-jgu@NdKs-lRtRcs@V>ym` zO2_m(jZJeP3uxDMHIq13#~Kox$3kY%!b~osmetH+6ZNEtk)=WTH5})2+Lbpm8&}ou zW)3@<%O0B8%X~&yz#(;Um_;1pLY||U=UL2)Ea3zfDgQsn>s-tkKBR?NCINj#Dzqab zv`94{ky@5Z1D8q@t+J5IWC?B3O1oT6hiv4dvUQjhay={M7A}`NxI*q?mE6mfvWM03 zxKjBbYh;|Ya)gh|Q(Ps_vQCcEC8zkLoT10^leDI?-l}1PHH(ebTsB!txZ3Jqvvmc1 z)>=~5dNNjuthJ48)-?=TpJBUo14Gu$Tw_hXoljdkG!Yh8vpG+w*QQ2>f7~9=m#i_Q zU%q6|<$=jOUHIj(pS%zDQ^i_3!*}~a6{ED~_(j3d+qEC#c{}9S-xxE;_;rq#;(OmN zpBBZ@^>qY>`KkE>Cj5GV=ja?2>6(4sGO;3G_8Sxx(g+3Cd)GIl9w;r}Jtt z>?%E8^te*JSlMH`$ajI}!UG@DU6qnB-}Ss#Ro=~bLG@8pI+xC(YAu#W2MA~e`ZSH( z-)S(dk0*FSADO+XUBiP#n>xikiSd#;ZA>P;kJSN9%E6E&`Y0ByVnr(_=6ffh`5-dt zssf*mX-f4446s!V^=&O0deL~tm1+?p@7}Hu%|?9k0To(OE3*dnvH$=8 diff --git a/build/j3270/org/pubvm/j3270/ui/SettingsDialog$1.class b/build/j3270/org/pubvm/j3270/ui/SettingsDialog$1.class index 145519e0393abf553f8601b369119d2375640a07..187f2692c679e56c0c609179238eb5f522c0fb78 100644 GIT binary patch delta 35 qcmdnbv!7=}7As@jVI diff --git a/build/j3270/org/pubvm/j3270/ui/SettingsDialog$2.class b/build/j3270/org/pubvm/j3270/ui/SettingsDialog$2.class index 7e3d3e9698d26b787b5985cf27102e379a2f12d4..c981afe77d48463a21a7e338526049afe8c1801b 100644 GIT binary patch delta 35 rcmeC;>*Cv>$;P-~vKCvt;9>?9#w83Ij7u4;7?&|PF|L@ri!A^EtiB1Y delta 35 rcmeC;>*Cv>$;LQovKCvt;8X?`#%T;1jMEvc7-ujzG0vL2i!A^Er?v@M diff --git a/build/j3270/org/pubvm/j3270/ui/SettingsDialog$3.class b/build/j3270/org/pubvm/j3270/ui/SettingsDialog$3.class index f5efe874ac53b801f14bb4ebb7805c43e4899c71..aeb554db753c9ae567bc7022e49e4218835a687a 100644 GIT binary patch delta 19 bcmbQqI+JySArs@N$wo|8jAtk3GU)&SJp%>k delta 19 bcmbQqI+JySArs@C$wo|8jQc0&GU)&SJR=3n diff --git a/build/j3270/org/pubvm/j3270/ui/SettingsDialog$4.class b/build/j3270/org/pubvm/j3270/ui/SettingsDialog$4.class index 3d9b553e552c820eede85ecf02c72b614e3373ff..5a81cd42725541109767e5f4b38bd1609a621d4f 100644 GIT binary patch delta 51 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 delta 9528 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 O(f=5EM!qqa{Qm>!khx<3 diff --git a/build/j3270/org/pubvm/j3270/ui/StatusBar$1.class b/build/j3270/org/pubvm/j3270/ui/StatusBar$1.class index c4929f9d94af6c3bf58f197e58a8cf20f6a41ad0..e9d742339f67811112c0d275483183e8e111ca1e 100644 GIT binary patch delta 13 UcmZqRY~b7=$-?MAS&AhM02qJ+M*si- delta 13 UcmZqRY~b7=$-?M8S&AhM02pioLjV8( diff --git a/build/j3270/org/pubvm/j3270/ui/StatusBar.class b/build/j3270/org/pubvm/j3270/ui/StatusBar.class index e1574137ed7a7bda89d1f43513b1b519911a6749..d8919decefbef33b8ac1e60ce17717ce6b6e0500 100644 GIT binary patch literal 5330 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 W?wM)vn{YV4kw^2J_$~Z4aQ_z=`~{%^ delta 4934 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 Td;KWZgZ)&3gz~q~BK-cpU4}r- literal 2493 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 GM*If{7_P|x literal 6945 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 Dd%%Od delta 733 zcmY*W%T5zf82-*o+iA<04#l=GosOuKTU%NY6_j!*u+hY5B4}86sclonK&us9D3O@- z1)ABIxMRaY7Kq7cl(;c&cmW^4m9IeX^3O~YG?Sb&|K+><|M{kV?Fkw zx-xt#NDOff4G9LN@}N{UN{jWSno+8kD{jlYjXBBDi&N$}OZg)>!_kMNX|QC+00t!t zaSS76zGg{n02z)Eo6J{QDK-l0Hh+%eEJn@mthZwf=OtX=xQKC+2{Yymp~7ZOOSl?V zz-cZCjLiAyoG`;;EP9D@cH~ybrBlg^iOu8666I)eyFz^v5a1dM$~WLf`!n7QVOZ>kpb+f6Uq{_{iiSU9~)e6GJ3^#gTc ze?&t)#vt7zc;YB<2)`gph_FL?4~8jBw^i;D%0syRw1xhC|21)1xBs>f&FHDLZh@lg zVpAl+pP5~<_n+Ex+Qtinu>qa#KD<0u3rLtDHI?46;6BXzf>)77Wm>H85$D9oxU=ZQ m9;A3S|IgOr*PF2Cd*_a3zT#=sU7L6$$xJ;Y%M5c)b^b3U4RE>u diff --git a/build/lib3270j/org/lib3270j/datastream/DataStreamProcessor.class b/build/lib3270j/org/lib3270j/datastream/DataStreamProcessor.class index 244f2cafa24c9cac750ab0a7836de6fc23241358..e6c94974e29565585509334bd27631a1a1ae979a 100644 GIT binary patch literal 17822 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 CZ;Fus literal 15410 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 G|Nj9sr|nPx diff --git a/build/lib3270j/org/lib3270j/datastream/QueryReplyBuilder.class b/build/lib3270j/org/lib3270j/datastream/QueryReplyBuilder.class index afd2c0fbd2daeca42601b887b104a81e7eb00e90..4aa268ece3dfb0e842f99dff60c49dd337fd2cfd 100644 GIT binary patch literal 8580 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 literal 4801 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 diff --git a/build/lib3270j/org/lib3270j/input/InputProcessor.class b/build/lib3270j/org/lib3270j/input/InputProcessor.class index e0afc12604e31e70fb7b11f21267f74167de8f30..fa8e87de1ae234f43afb5403971ef9f132451595 100644 GIT binary patch literal 9896 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 literal 9643 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 IYLqwde{ihPLjV8( diff --git a/build/lib3270j/org/lib3270j/protocol/DS3270Constants.class b/build/lib3270j/org/lib3270j/protocol/DS3270Constants.class index 1fdbea2390673139afb6d865ff6fff3ac318e8c4..cfe63a265f1de5e22c18965b3437925c889464b5 100644 GIT binary patch delta 2052 zcmZ{l?{AY;6vxkbI@Yyiz?NWijHlgJ=7xq17z`&0Wt9D3UE8N)ZeZw8oWC|u0q4Z6 zD)<8!h(1#s#>B)#Fd+zxXu=D^>&1}pFW_4fUz&g#zxVcec25)SyU+K2&pqd!=bqcE zso&G#g@10{CZe7E&Gqn($jaqeC!b3UCi6}*H;{Ic0j$lqy^*+H8E_K)xlF=I_ID1e zl{t4T(o|TA7$=j>_a{=xoRTjyMWA&#iA;A-ruQP=qB=!vDcP0l_ z)QzH&PNwp^QUgkv_kudpeQ8GtH@%H>!>JwVKE3hJJnu%fEa6%A*A?S@f4-yYT*%>i zdW;)r9Wk{a%;GqBjo?P`ycygjcni4O zF!z9a4O0%h+b~=1gW6~z4K-s_dIg*n+yQn3Zw2QBKMNiZtQxsTuxjL>V0DV;3_H54 zAtR7F_ORel@Ls_x!9Kw%!SjOK+=bP4f%A|@jLa$U0m16rF9?1Se9$nBgI_Yts*INf zt3JIdcoX=wMJ`zU420Jg1xufEOz;t`j|*1kIwV+~>#$(exHkl=g5DH-6a1FX1x$vz zj7NNdEV>0gD)=|>F~NU;-xjP2oDh5r{ElGNfl0yYro8KMGe%Rl@VGD>hT%QI6W}TL zsvYHN_pbfO$^r(u9D_|1q7c8&9}r{Z@4D47v(m#KVtol&;q7=r_=IQL9JTiwQ?GA| zXw%7}3308cO6^}+vm!PrWQKK4w1Mq|R1 z@=cZ6^oeI`k4aO1sp(VCG!bj8Gc{$jshk4K1A41dp6hn3Nw{*ptE%Wa?YV+=cERj* z%1>DA&Y7aAv^y35+nv*;(KCzDEnaVz)&&0}xVHFV&-l^B&v(`<@J`7$o+&k*^Gu}| z;cThtGd{0xOGkZcqdMv`!oLc4QU#vSYVOwN0C6?$%;)@tww|wdwkoSlL0Gd?hIg(K zZ`q@Gzv4VZ8}STn<9#|bMAlyv53*Z>4ZidWvKuCwO|B6-$YV6hhjc(WSq~w={S{x( z>FzY#%PRLeiXh!ln#J&4z`$L^kbR56`VPbNJqG9pK2ASkPOk72{lwFHw`CN5NVAsz b!>PWe3bpI}{rE3z1_ysx%yUR$@x^}uuAKj8 delta 1793 zcmZvcNlcSb5XWb}vXu%_5)(vPT3RX~0W9DGC`gnY*;HIeL^e@2*Fu@@{e{i9Sh{V0INs7H?S1%b@sIt9H2@Q^6H_1y5V9#iLl3c+aVoJ-F-#Hf zR*YgiO)<0*rz@7@8I1SPc;*IMG_>pAb~)Rii}aigU4tCry^Q_De#Uvkxr}Fs^BC_V z&S!i;Pe@93H<5Z!si+YbFfP=)lbn9&Cb?M2P(WP5xQO@&oei%N2}8%ax2p%T8-gacyqmFDs7s6oh%Yl9Bks{xl9Mxg=^i=gJ|X>v zXDoqbf&L)&(RYLttzgA|93ZV%FZ4J9YNQgch#=6SOOQ;1K@&+ipvlmf#JITWXAl|o z7Q!s^G@!A(RiS8`j2_x5+O;{_VU$&E4j(ou{O3w_sS0aCrg%BWh+)ciyO^mqWQvid zQNuK9G95RYt{SG#Zl7CCS1(NwVA*bwsm2V~nmdgxTT{qo54y&KF7vvTZ0_7>d^S`` zaNQ=hijLY8?F?<_19SMKQR3GW2V0VdAyaT?uNkIwlj)J!blot~8DW_on@v+VEf3PT zC&wq&Efy?v+6o{Fiy$70rP%@)Psb^MH>CB4r?E&_oe)XZ3J9ZDCx%{@1bR)p^m^pd zi&2b?GSmv%8h9h|W+Ek+F$!w&PNb{3nxTWPUKe)CfC$i5Ng(%ayd$Su_x@}U?kup; zbUiSKz3`Nd?Q`shmz41p#l6Nsc!NXm9*1EbN8lrl%51R^y$W+$=tEuIg(#7A(dAph R9U`r~QGJt7V7{|7b(#%urp diff --git a/build/lib3270j/org/lib3270j/telnet/TelnetConnection.class b/build/lib3270j/org/lib3270j/telnet/TelnetConnection.class index 5f4c7a08634810a7be53f2a658cf87001ed2d1a0..6508f81d0759d598d8d753815481eeb3f51c524a 100644 GIT binary patch literal 7262 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 ePr;?^(Xz*3dmORH%d%YF_jb)41! literal 6374 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 diff --git a/j3270/src/main/java/org/pubvm/j3270/J3270App.java b/j3270/src/main/java/org/pubvm/j3270/J3270App.java index 0aae383..25788a1 100644 --- a/j3270/src/main/java/org/pubvm/j3270/J3270App.java +++ b/j3270/src/main/java/org/pubvm/j3270/J3270App.java @@ -227,8 +227,12 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate private void showConnectDialog() { ConnectDialog dialog = new ConnectDialog(this); - dialog.setInitialHost(lastHost); - dialog.setInitialPort(lastPort); + String initialHost = lastHost != null && !lastHost.isEmpty() ? lastHost : org.pubvm.j3270.config.Settings.getAutoConnectHost(); + int initialPort = lastPort > 0 ? lastPort : org.pubvm.j3270.config.Settings.getAutoConnectPort(); + dialog.setInitialHost(initialHost); + dialog.setInitialPort(initialPort); + dialog.setInitialTls(org.pubvm.j3270.config.Settings.getAutoConnectTls()); + dialog.setInitialVerifyCert(org.pubvm.j3270.config.Settings.getAutoConnectVerifyCert()); dialog.setVisible(true); if (dialog.isConfirmed()) { @@ -255,10 +259,19 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate // Save for auto-connect org.pubvm.j3270.config.Settings.setAutoConnectHost(config.getHost()); org.pubvm.j3270.config.Settings.setAutoConnectPort(config.getPort()); + org.pubvm.j3270.config.Settings.setAutoConnectTls(config.isUseTls()); + org.pubvm.j3270.config.Settings.setAutoConnectVerifyCert(config.isTlsVerifyCert()); // Disconnect existing connection disconnect(); + // Wire interactive certificate verifier if none configured + if (config.getCertificateVerifier() == null) { + config.setCertificateVerifier((chain, authType, exception) -> + org.pubvm.j3270.ui.UntrustedCertificateDialog.showPrompt(this, config.getHost(), config.getPort(), chain, exception) + ); + } + client = new Telnet3270Client(config); client.addConnectionListener(this); client.addScreenUpdateListener(this); @@ -266,7 +279,8 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate terminalPanel.setClient(client); statusBar.setClient(client); - setTitle("j3270 — " + config.getHost() + ":" + config.getPort()); + String tlsIndicator = config.isUseTls() ? (config.isTlsVerifyCert() ? " [TLS]" : " [TLS/Unverified]") : ""; + setTitle("j3270 — " + config.getHost() + ":" + config.getPort() + tlsIndicator); // Resize window to match model terminalPanel.guardedPack(); @@ -426,11 +440,24 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate public static void main(String[] args) { boolean debug = false; + boolean cliTls = false; + boolean cliNoVerifyCert = false; + org.lib3270j.graphics.GraphicsMode cliGraphicsMode = null; String configFile = null; java.util.List remainingArgs = new java.util.ArrayList<>(); for (int i = 0; i < args.length; i++) { if ("--debug".equals(args[i]) || "-d".equals(args[i])) { debug = true; + } else if ("--tls".equals(args[i]) || "--ssl".equals(args[i]) || "-s".equals(args[i])) { + cliTls = true; + } else if ("--no-verify-cert".equals(args[i]) || "--insecure".equals(args[i]) || "-k".equals(args[i])) { + cliNoVerifyCert = true; + } else if (args[i].startsWith("--graphics=")) { + cliGraphicsMode = org.lib3270j.graphics.GraphicsMode.fromString(args[i].substring(11)); + } else if (("--graphics".equals(args[i]) || "-g".equals(args[i])) && i + 1 < args.length && !args[i + 1].startsWith("-")) { + cliGraphicsMode = org.lib3270j.graphics.GraphicsMode.fromString(args[++i]); + } else if ("--no-graphics".equals(args[i])) { + cliGraphicsMode = org.lib3270j.graphics.GraphicsMode.NONE; } else if (("-c".equals(args[i]) || "--config".equals(args[i])) && i + 1 < args.length) { configFile = args[++i]; } else { @@ -439,33 +466,41 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate } // Configure logging for application packages + Level logLevel = debug ? Level.ALL : Level.INFO; Logger globalRoot = Logger.getLogger(""); for (java.util.logging.Handler h : globalRoot.getHandlers()) { globalRoot.removeHandler(h); } - globalRoot.setLevel(Level.WARNING); + globalRoot.setLevel(Level.ALL); - ConsoleHandler handler = new ConsoleHandler(); - handler.setLevel(debug ? Level.ALL : Level.INFO); - handler.setFormatter(new SimpleFormatter()); + java.util.logging.Filter appFilter = record -> { + String name = record.getLoggerName(); + return name != null && (name.startsWith("org.lib3270j") || name.startsWith("org.pubvm.j3270")); + }; - Logger appLogger = Logger.getLogger("org.pubvm.j3270"); - appLogger.setLevel(Level.ALL); - appLogger.addHandler(handler); - appLogger.setUseParentHandlers(false); + ConsoleHandler consoleHandler = new ConsoleHandler(); + consoleHandler.setLevel(logLevel); + consoleHandler.setFormatter(new SimpleFormatter()); + consoleHandler.setFilter(appFilter); + globalRoot.addHandler(consoleHandler); - Logger libLogger = Logger.getLogger("org.lib3270j"); - libLogger.setLevel(Level.ALL); - libLogger.addHandler(handler); - libLogger.setUseParentHandlers(false); + Logger.getLogger("org.pubvm").setLevel(logLevel); + Logger.getLogger("org.pubvm.j3270").setLevel(logLevel); + Logger.getLogger("org.lib3270j").setLevel(logLevel); try { - java.util.logging.FileHandler fileHandler = new java.util.logging.FileHandler("j3270.log", 10 * 1024 * 1024, 1, false); + java.util.logging.FileHandler fileHandler = new java.util.logging.FileHandler("j3270.log", 10 * 1024 * 1024, 1, false) { + @Override + public synchronized void publish(java.util.logging.LogRecord record) { + super.publish(record); + flush(); + } + }; fileHandler.setLevel(Level.ALL); fileHandler.setFormatter(new SimpleFormatter()); - appLogger.addHandler(fileHandler); - libLogger.addHandler(fileHandler); - log.info("Logging protocol trace to j3270.log"); + fileHandler.setFilter(appFilter); + globalRoot.addHandler(fileHandler); + log.info("Logging protocol trace to j3270.log (debug=" + debug + ", level=" + logLevel + ")"); } catch (Exception e) { System.err.println("Could not create j3270.log: " + e.getMessage()); } @@ -492,18 +527,22 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate System.setProperty("apple.laf.useScreenMenuBar", "true"); System.setProperty("apple.awt.application.name", "j3270"); + final boolean finalTls = cliTls; + final boolean finalNoVerify = cliNoVerifyCert; + final org.lib3270j.graphics.GraphicsMode finalGraphicsMode = cliGraphicsMode; + SwingUtilities.invokeLater(() -> { J3270App app = new J3270App(); app.setVisible(true); // If host:port given on command line, connect directly if (!remainingArgs.isEmpty()) { - String host = remainingArgs.get(0); - int port = 23; + String hostArg = remainingArgs.get(0); + int port = finalTls ? 992 : 23; if (remainingArgs.size() >= 2) { try { port = Integer.parseInt(remainingArgs.get(1)); - } catch (NumberFormatException e) { + } catch (NumberFormatException ignored) { } } TerminalModel model = TerminalModel.IBM_3279_4; @@ -511,10 +550,21 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate try { int modelNum = Integer.parseInt(remainingArgs.get(2)); model = TerminalModel.forModel(modelNum, true); - } catch (Exception e) { + } catch (Exception ignored) { } } - ConnectionConfig config = new ConnectionConfig(host, port, model); + ConnectionConfig config = ConnectionConfig.parseHostString(hostArg, port, model); + if (finalTls) { + config.setUseTls(true); + } + if (finalNoVerify) { + config.setTlsVerifyCert(false); + } + if (finalGraphicsMode != null) { + config.setGraphicsMode(finalGraphicsMode); + } else { + config.setGraphicsMode(org.pubvm.j3270.config.Settings.getGraphicsMode()); + } app.connect(config); } else { org.pubvm.j3270.config.Settings.StartupBehavior behavior = org.pubvm.j3270.config.Settings @@ -526,8 +576,17 @@ public class J3270App extends JFrame implements ConnectionListener, ScreenUpdate case AUTO_CONNECT: String host = org.pubvm.j3270.config.Settings.getAutoConnectHost(); int port = org.pubvm.j3270.config.Settings.getAutoConnectPort(); + boolean tls = org.pubvm.j3270.config.Settings.getAutoConnectTls(); + boolean verify = org.pubvm.j3270.config.Settings.getAutoConnectVerifyCert(); if (host != null && !host.isEmpty()) { - app.connect(new ConnectionConfig(host, port, TerminalModel.IBM_3279_4)); + ConnectionConfig config = new ConnectionConfig(host, port, TerminalModel.IBM_3279_4, tls); + config.setTlsVerifyCert(verify); + if (finalGraphicsMode != null) { + config.setGraphicsMode(finalGraphicsMode); + } else { + config.setGraphicsMode(org.pubvm.j3270.config.Settings.getGraphicsMode()); + } + app.connect(config); } else { SwingUtilities.invokeLater(app::showConnectDialog); } diff --git a/j3270/src/main/java/org/pubvm/j3270/config/Settings.java b/j3270/src/main/java/org/pubvm/j3270/config/Settings.java index 28b7cac..ff780b7 100644 --- a/j3270/src/main/java/org/pubvm/j3270/config/Settings.java +++ b/j3270/src/main/java/org/pubvm/j3270/config/Settings.java @@ -62,6 +62,31 @@ public class Settings { prefs.putInt("autoConnectPort", port); } + public static boolean getAutoConnectTls() { + return prefs.getBoolean("autoConnectTls", false); + } + + public static void setAutoConnectTls(boolean tls) { + prefs.putBoolean("autoConnectTls", tls); + } + + public static boolean getAutoConnectVerifyCert() { + return prefs.getBoolean("autoConnectVerifyCert", true); + } + + public static void setAutoConnectVerifyCert(boolean verify) { + prefs.putBoolean("autoConnectVerifyCert", verify); + } + + public static org.lib3270j.graphics.GraphicsMode getGraphicsMode() { + String modeStr = prefs.get("graphicsMode", org.lib3270j.graphics.GraphicsMode.NONE.name()); + return org.lib3270j.graphics.GraphicsMode.fromString(modeStr); + } + + public static void setGraphicsMode(org.lib3270j.graphics.GraphicsMode mode) { + prefs.put("graphicsMode", (mode != null ? mode : org.lib3270j.graphics.GraphicsMode.NONE).name()); + } + public static Color getColorOverride(int index, Color defaultColor) { String hex = prefs.get("color_" + index, null); try { @@ -194,15 +219,33 @@ public class Settings { break; case "behavior": + case "connection": switch (key) { case "startupBehavior": setStartupBehavior(StartupBehavior.valueOf(value.toUpperCase())); break; - case "autoConnectHost": setAutoConnectHost(value); break; - case "autoConnectPort": setAutoConnectPort(Integer.parseInt(value)); break; + case "autoConnectHost": + case "host": + setAutoConnectHost(value); + break; + case "autoConnectPort": + case "port": + setAutoConnectPort(Integer.parseInt(value)); + break; + case "autoConnectTls": + case "tls": + case "ssl": + case "useTls": + setAutoConnectTls(Boolean.parseBoolean(value)); + break; + case "autoConnectVerifyCert": + case "verifyCert": + case "tlsVerifyCert": + setAutoConnectVerifyCert(Boolean.parseBoolean(value)); + break; case "blockSelectMode": setBlockSelectMode(Boolean.parseBoolean(value)); break; default: - log.warning("Unknown behavior key: " + key); + log.warning("Unknown behavior/connection key: " + key); } break; @@ -222,6 +265,14 @@ public class Settings { setKeyBinding(key, value); break; + case "graphics": + if ("graphicsMode".equalsIgnoreCase(key) || "mode".equalsIgnoreCase(key)) { + setGraphicsMode(org.lib3270j.graphics.GraphicsMode.fromString(value)); + } else { + log.warning("Unknown graphics key: " + key); + } + break; + default: // Allow bare keys outside any section — treat as raw prefs log.fine("Setting raw preference: " + key + " = " + value); @@ -256,10 +307,17 @@ public class Settings { if (acHost != null && !acHost.isEmpty()) { w.println("autoConnectHost = " + acHost); w.println("autoConnectPort = " + getAutoConnectPort()); + w.println("autoConnectTls = " + getAutoConnectTls()); + w.println("autoConnectVerifyCert = " + getAutoConnectVerifyCert()); } w.println("blockSelectMode = " + getBlockSelectMode()); w.println(); + // [graphics] + w.println("[graphics]"); + w.println("graphicsMode = " + getGraphicsMode().name()); + w.println(); + // [colors] w.println("[colors]"); // Host colors 0-15 diff --git a/j3270/src/main/java/org/pubvm/j3270/ui/ConnectDialog.java b/j3270/src/main/java/org/pubvm/j3270/ui/ConnectDialog.java index 30ac8b8..b6ac0bb 100644 --- a/j3270/src/main/java/org/pubvm/j3270/ui/ConnectDialog.java +++ b/j3270/src/main/java/org/pubvm/j3270/ui/ConnectDialog.java @@ -14,7 +14,10 @@ public class ConnectDialog extends JDialog { private JTextField hostField; private JTextField portField; private JComboBox modelCombo; + private JComboBox graphicsCombo; private JTextField luField; + private JCheckBox tlsCheckBox; + private JCheckBox verifyCertCheckBox; private boolean confirmed; private ConnectionConfig result; @@ -93,6 +96,56 @@ public class ConnectDialog extends JDialog { luField = createDarkField(12); mainPanel.add(luField, gbc); + // Graphics Mode + gbc.gridx = 0; + gbc.gridy = 4; + gbc.weightx = 0; + JLabel graphicsLabel = new JLabel("Graphics:"); + graphicsLabel.setForeground(fg); + graphicsLabel.setFont(labelFont); + mainPanel.add(graphicsLabel, gbc); + gbc.gridx = 1; + gbc.weightx = 1.0; + graphicsCombo = new JComboBox<>(org.lib3270j.graphics.GraphicsMode.values()); + graphicsCombo.setSelectedItem(org.pubvm.j3270.config.Settings.getGraphicsMode()); + graphicsCombo.setBackground(new Color(45, 45, 45)); + graphicsCombo.setForeground(fg); + graphicsCombo.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13)); + mainPanel.add(graphicsCombo, gbc); + + // TLS / SSL Checkbox + gbc.gridx = 1; + gbc.gridy = 5; + gbc.weightx = 1.0; + tlsCheckBox = new JCheckBox("Enable TLS/SSL"); + tlsCheckBox.setBackground(new Color(30, 30, 30)); + tlsCheckBox.setForeground(fg); + tlsCheckBox.setFont(labelFont); + tlsCheckBox.setFocusPainted(false); + tlsCheckBox.addActionListener(e -> { + boolean isTls = tlsCheckBox.isSelected(); + verifyCertCheckBox.setEnabled(isTls); + String currentPort = portField.getText().trim(); + if (isTls && "23".equals(currentPort)) { + portField.setText("992"); + } else if (!isTls && "992".equals(currentPort)) { + portField.setText("23"); + } + }); + mainPanel.add(tlsCheckBox, gbc); + + // Verify Certificate Checkbox + gbc.gridx = 1; + gbc.gridy = 6; + verifyCertCheckBox = new JCheckBox("Verify Server Certificate"); + verifyCertCheckBox.setBackground(new Color(30, 30, 30)); + verifyCertCheckBox.setForeground(new Color(160, 160, 160)); + verifyCertCheckBox.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 13)); + verifyCertCheckBox.setSelected(true); + verifyCertCheckBox.setEnabled(false); + verifyCertCheckBox.setFocusPainted(false); + mainPanel.add(verifyCertCheckBox, gbc); + // Buttons JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); buttonPanel.setBackground(new Color(30, 30, 30)); @@ -116,7 +169,7 @@ public class ConnectDialog extends JDialog { buttonPanel.add(connectBtn); gbc.gridx = 0; - gbc.gridy = 4; + gbc.gridy = 7; gbc.gridwidth = 2; mainPanel.add(buttonPanel, gbc); @@ -158,6 +211,9 @@ public class ConnectDialog extends JDialog { if (!lu.isEmpty()) { result.setLuName(lu); } + result.setGraphicsMode((org.lib3270j.graphics.GraphicsMode) graphicsCombo.getSelectedItem()); + result.setUseTls(tlsCheckBox.isSelected()); + result.setTlsVerifyCert(verifyCertCheckBox.isSelected()); confirmed = true; dispose(); } @@ -178,4 +234,13 @@ public class ConnectDialog extends JDialog { public void setInitialPort(int port) { portField.setText(String.valueOf(port)); } + + public void setInitialTls(boolean tls) { + tlsCheckBox.setSelected(tls); + verifyCertCheckBox.setEnabled(tls); + } + + public void setInitialVerifyCert(boolean verify) { + verifyCertCheckBox.setSelected(verify); + } } diff --git a/j3270/src/main/java/org/pubvm/j3270/ui/SettingsDialog.java b/j3270/src/main/java/org/pubvm/j3270/ui/SettingsDialog.java index 7ef2eee..716f616 100644 --- a/j3270/src/main/java/org/pubvm/j3270/ui/SettingsDialog.java +++ b/j3270/src/main/java/org/pubvm/j3270/ui/SettingsDialog.java @@ -31,6 +31,7 @@ public class SettingsDialog extends JDialog { // Appearance tab private JComboBox fontBox; private JSpinner fontSizeSpinner; + private JComboBox graphicsModeBox; // Behavior tab private JComboBox startupBehaviorBox; @@ -264,8 +265,21 @@ public class SettingsDialog extends JDialog { gbc.fill = GridBagConstraints.HORIZONTAL; panel.add(fontSizeSpinner, gbc); - // Fill remaining space + // Graphics Mode + gbc.gridx = 0; gbc.gridy = 2; + gbc.fill = GridBagConstraints.NONE; + JLabel graphicsLabel = new JLabel("Graphics Mode:"); + panel.add(graphicsLabel, gbc); + + graphicsModeBox = new JComboBox<>(org.lib3270j.graphics.GraphicsMode.values()); + graphicsModeBox.setSelectedItem(Settings.getGraphicsMode()); + gbc.gridx = 1; + gbc.fill = GridBagConstraints.HORIZONTAL; + panel.add(graphicsModeBox, gbc); + + // Fill remaining space + gbc.gridy = 3; gbc.weighty = 1.0; panel.add(Box.createGlue(), gbc); @@ -611,6 +625,9 @@ public class SettingsDialog extends JDialog { Settings.setFontFamily(fontFam); } Settings.setFontSize((Integer) fontSizeSpinner.getValue()); + if (graphicsModeBox.getSelectedItem() != null) { + Settings.setGraphicsMode((org.lib3270j.graphics.GraphicsMode) graphicsModeBox.getSelectedItem()); + } // Apply Behavior Settings.StartupBehavior behavior = (Settings.StartupBehavior) startupBehaviorBox.getSelectedItem(); diff --git a/j3270/src/main/java/org/pubvm/j3270/ui/StatusBar.java b/j3270/src/main/java/org/pubvm/j3270/ui/StatusBar.java index fa6a038..e3a7733 100644 --- a/j3270/src/main/java/org/pubvm/j3270/ui/StatusBar.java +++ b/j3270/src/main/java/org/pubvm/j3270/ui/StatusBar.java @@ -15,6 +15,7 @@ import java.awt.*; public class StatusBar extends JPanel { private final JLabel connectionStatus; + private final JLabel tlsStatus; private final JLabel cursorPosition; private final JLabel luName; private final JLabel lockStatus; @@ -37,6 +38,7 @@ public class StatusBar extends JPanel { Font oiaFont = new Font(Font.MONOSPACED, Font.PLAIN, 12); connectionStatus = createLabel("Not Connected", oiaFont, OIA_DIM); + tlsStatus = createLabel("", oiaFont, OIA_FG); luName = createLabel("", oiaFont, OIA_FG); lockStatus = createLabel("", oiaFont, OIA_ALERT); modelInfo = createLabel("", oiaFont, OIA_DIM); @@ -44,6 +46,8 @@ public class StatusBar extends JPanel { add(Box.createHorizontalStrut(6)); add(connectionStatus); + add(Box.createHorizontalStrut(10)); + add(tlsStatus); add(Box.createHorizontalStrut(12)); add(luName); add(Box.createHorizontalStrut(12)); @@ -110,6 +114,26 @@ public class StatusBar extends JPanel { break; } + // TLS Status + if (state.isConnected() && client.getConfig().isUseTls()) { + boolean verified = client.getConfig().isTlsVerifyCert(); + javax.net.ssl.SSLSession session = client.getSslSession(); + String cipher = session != null ? session.getCipherSuite() : "TLS"; + String protocol = session != null ? session.getProtocol() : "TLS"; + if (verified) { + tlsStatus.setText("🔒 TLS"); + tlsStatus.setForeground(OIA_FG); + tlsStatus.setToolTipText(protocol + " / " + cipher + " (Verified)"); + } else { + tlsStatus.setText("🔓 TLS (Unverified)"); + tlsStatus.setForeground(new Color(255, 180, 80)); + tlsStatus.setToolTipText(protocol + " / " + cipher + " (Verification Bypassed)"); + } + } else { + tlsStatus.setText(""); + tlsStatus.setToolTipText(null); + } + // LU name String lu = ""; if (client.getConnectionState().isTn3270e()) { diff --git a/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java b/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java index 6514486..ef23b1c 100644 --- a/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java +++ b/j3270/src/main/java/org/pubvm/j3270/ui/TerminalPanel.java @@ -921,13 +921,21 @@ public class TerminalPanel extends JPanel { g2.setColor(bgColor); g2.fillRect(x, y, cellWidth, cellHeight); - // Draw character - char ch = ea.ucs4; - if (ch > 0x20 && ch != 0xFF) { - Font f = bold ? terminalFont.deriveFont(Font.BOLD) : terminalFont; - g2.setFont(f); - g2.setColor(fgColor); - g2.drawString(String.valueOf(ch), x, y + fontAscent); + // Draw character or Programmed Symbol + int cs = (ea.cs != 0) ? (ea.cs & 0xFF) : (currentFieldEa != null ? (currentFieldEa.cs & 0xFF) : 0); + boolean drawnAsPs = false; + if (cs >= 0x40 && client.getProgramSymbolManager() != null) { + drawnAsPs = client.getProgramSymbolManager().drawSymbol(g2, cs, ea.ec & 0xFF, x, y, cellWidth, cellHeight, fgColor, bgColor); + } + + if (!drawnAsPs) { + char ch = ea.ucs4; + if (ch > 0x20 && ch != 0xFF) { + Font f = bold ? terminalFont.deriveFont(Font.BOLD) : terminalFont; + g2.setFont(f); + g2.setColor(fgColor); + g2.drawString(String.valueOf(ch), x, y + fontAscent); + } } // Draw underline @@ -945,6 +953,14 @@ public class TerminalPanel extends JPanel { } } + // Draw Vector Graphics Plane overlay if present + if (client.getGraphicsPlane() != null && client.getGraphicsPlane().hasContent()) { + int gridW = cols * cellWidth; + int gridH = rows * cellHeight; + client.getGraphicsPlane().resize(gridW, gridH); + g2.drawImage(client.getGraphicsPlane().getImage(), ox, oy, gridW, gridH, null); + } + // Draw cursor if (cursorVisible && client.getConnectionState().isFullSession()) { int curAddr = sb.getCursorAddress(); diff --git a/j3270/src/main/java/org/pubvm/j3270/ui/UntrustedCertificateDialog.java b/j3270/src/main/java/org/pubvm/j3270/ui/UntrustedCertificateDialog.java new file mode 100644 index 0000000..ecef814 --- /dev/null +++ b/j3270/src/main/java/org/pubvm/j3270/ui/UntrustedCertificateDialog.java @@ -0,0 +1,153 @@ +package org.pubvm.j3270.ui; + +import javax.swing.*; +import java.awt.*; +import java.security.MessageDigest; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Modal dialog for inspecting and confirming untrusted TLS/SSL server certificates. + */ +public class UntrustedCertificateDialog extends JDialog { + + private boolean accepted = false; + + public UntrustedCertificateDialog(Frame parent, String host, int port, X509Certificate[] chain, CertificateException exception) { + super(parent, "Untrusted SSL/TLS Certificate", true); + buildUI(host, port, chain, exception); + pack(); + setLocationRelativeTo(parent); + setResizable(false); + } + + private void buildUI(String host, int port, X509Certificate[] chain, CertificateException exception) { + JPanel mainPanel = new JPanel(new BorderLayout(12, 12)); + mainPanel.setBorder(BorderFactory.createEmptyBorder(16, 16, 16, 16)); + mainPanel.setBackground(new Color(30, 30, 30)); + + // Header + JPanel headerPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 0)); + headerPanel.setBackground(new Color(30, 30, 30)); + JLabel iconLabel = new JLabel("⚠️"); + iconLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 28)); + headerPanel.add(iconLabel); + + JPanel titleBox = new JPanel(new GridLayout(2, 1, 0, 2)); + titleBox.setBackground(new Color(30, 30, 30)); + JLabel titleLabel = new JLabel("Untrusted SSL Certificate"); + titleLabel.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 16)); + titleLabel.setForeground(new Color(255, 180, 80)); + titleBox.add(titleLabel); + + JLabel subtitleLabel = new JLabel("The server certificate for " + host + ":" + port + " could not be verified."); + subtitleLabel.setFont(new Font(Font.SANS_SERIF, Font.PLAIN, 12)); + subtitleLabel.setForeground(new Color(180, 180, 180)); + titleBox.add(subtitleLabel); + headerPanel.add(titleBox); + mainPanel.add(headerPanel, BorderLayout.NORTH); + + // Certificate Details Area + X509Certificate cert = (chain != null && chain.length > 0) ? chain[0] : null; + StringBuilder sb = new StringBuilder(); + if (exception != null) { + sb.append("Validation Error:\n ").append(exception.getMessage() != null ? exception.getMessage() : exception.toString()).append("\n\n"); + } + if (cert != null) { + sb.append("Subject:\n ").append(cert.getSubjectX500Principal().getName()).append("\n\n"); + sb.append("Issuer:\n ").append(cert.getIssuerX500Principal().getName()).append("\n\n"); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); + sb.append("Validity:\n From: ").append(sdf.format(cert.getNotBefore())) + .append("\n To: ").append(sdf.format(cert.getNotAfter())).append("\n\n"); + sb.append("Serial Number:\n ").append(cert.getSerialNumber().toString(16).toUpperCase()).append("\n\n"); + sb.append("SHA-256 Fingerprint:\n ").append(computeFingerprint(cert, "SHA-256")).append("\n\n"); + sb.append("SHA-1 Fingerprint:\n ").append(computeFingerprint(cert, "SHA-1")); + } else { + sb.append("No peer certificate information available."); + } + + JTextArea detailsArea = new JTextArea(sb.toString()); + detailsArea.setEditable(false); + detailsArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); + detailsArea.setBackground(new Color(20, 20, 20)); + detailsArea.setForeground(new Color(210, 210, 210)); + detailsArea.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8)); + + JScrollPane scrollPane = new JScrollPane(detailsArea); + scrollPane.setPreferredSize(new Dimension(520, 260)); + scrollPane.setBorder(BorderFactory.createLineBorder(new Color(60, 60, 60))); + mainPanel.add(scrollPane, BorderLayout.CENTER); + + // Buttons + JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT, 10, 0)); + buttonPanel.setBackground(new Color(30, 30, 30)); + + JButton cancelBtn = new JButton("Cancel Connection"); + cancelBtn.setBackground(new Color(60, 60, 60)); + cancelBtn.setForeground(new Color(220, 220, 220)); + cancelBtn.addActionListener(e -> { + accepted = false; + dispose(); + }); + + JButton trustBtn = new JButton("Connect Anyway"); + trustBtn.setBackground(new Color(180, 100, 40)); + trustBtn.setForeground(Color.WHITE); + trustBtn.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 13)); + trustBtn.addActionListener(e -> { + accepted = true; + dispose(); + }); + + buttonPanel.add(cancelBtn); + buttonPanel.add(trustBtn); + mainPanel.add(buttonPanel, BorderLayout.SOUTH); + + setContentPane(mainPanel); + getRootPane().setDefaultButton(trustBtn); + } + + public boolean isAccepted() { + return accepted; + } + + private static String computeFingerprint(X509Certificate cert, String algorithm) { + try { + MessageDigest md = MessageDigest.getInstance(algorithm); + byte[] digest = md.digest(cert.getEncoded()); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < digest.length; i++) { + if (i > 0) sb.append(':'); + sb.append(String.format("%02X", digest[i])); + } + return sb.toString(); + } catch (Exception e) { + return "Unable to compute fingerprint: " + e.getMessage(); + } + } + + /** + * Show the prompt modal dialog on the Swing EDT and return user choice. + */ + public static boolean showPrompt(Frame parent, String host, int port, X509Certificate[] chain, CertificateException exception) { + if (SwingUtilities.isEventDispatchThread()) { + UntrustedCertificateDialog dialog = new UntrustedCertificateDialog(parent, host, port, chain, exception); + dialog.setVisible(true); + return dialog.isAccepted(); + } else { + AtomicBoolean result = new AtomicBoolean(false); + try { + SwingUtilities.invokeAndWait(() -> { + UntrustedCertificateDialog dialog = new UntrustedCertificateDialog(parent, host, port, chain, exception); + dialog.setVisible(true); + result.set(dialog.isAccepted()); + }); + } catch (Exception e) { + return false; + } + return result.get(); + } + } +} diff --git a/lib3270j/src/main/java/org/lib3270j/ConnectionConfig.java b/lib3270j/src/main/java/org/lib3270j/ConnectionConfig.java index 303709f..cb45cb4 100644 --- a/lib3270j/src/main/java/org/lib3270j/ConnectionConfig.java +++ b/lib3270j/src/main/java/org/lib3270j/ConnectionConfig.java @@ -11,9 +11,13 @@ public class ConnectionConfig { private String luName = null; private boolean extendedDataStream = true; private boolean useTls = false; + private boolean tlsVerifyCert = true; + private org.lib3270j.tls.TlsCertificateVerifier certificateVerifier = null; + private String sslProtocol = "TLS"; private int connectTimeoutMs = 15000; private int nopIntervalSeconds = 0; private String terminalName = null; // override terminal type string + private org.lib3270j.graphics.GraphicsMode graphicsMode = org.lib3270j.graphics.GraphicsMode.NONE; public ConnectionConfig() {} @@ -22,12 +26,25 @@ public class ConnectionConfig { this.port = port; } + public ConnectionConfig(String host, int port, boolean useTls) { + this.host = host; + this.port = port; + this.useTls = useTls; + } + public ConnectionConfig(String host, int port, TerminalModel model) { this.host = host; this.port = port; this.model = model; } + public ConnectionConfig(String host, int port, TerminalModel model, boolean useTls) { + this.host = host; + this.port = port; + this.model = model; + this.useTls = useTls; + } + public String getHost() { return host; } public void setHost(String host) { this.host = host; } @@ -46,15 +63,79 @@ public class ConnectionConfig { public boolean isUseTls() { return useTls; } public void setUseTls(boolean useTls) { this.useTls = useTls; } + public boolean isTlsVerifyCert() { return tlsVerifyCert; } + public void setTlsVerifyCert(boolean verify) { this.tlsVerifyCert = verify; } + + public org.lib3270j.tls.TlsCertificateVerifier getCertificateVerifier() { return certificateVerifier; } + public void setCertificateVerifier(org.lib3270j.tls.TlsCertificateVerifier verifier) { this.certificateVerifier = verifier; } + + public String getSslProtocol() { return sslProtocol; } + public void setSslProtocol(String protocol) { this.sslProtocol = protocol; } + public int getConnectTimeoutMs() { return connectTimeoutMs; } public void setConnectTimeoutMs(int ms) { this.connectTimeoutMs = ms; } public int getNopIntervalSeconds() { return nopIntervalSeconds; } public void setNopIntervalSeconds(int s) { this.nopIntervalSeconds = s; } + public org.lib3270j.graphics.GraphicsMode getGraphicsMode() { return graphicsMode; } + public void setGraphicsMode(org.lib3270j.graphics.GraphicsMode mode) { + this.graphicsMode = (mode != null) ? mode : org.lib3270j.graphics.GraphicsMode.NONE; + } + public String getTerminalName() { return terminalName; } public void setTerminalName(String name) { this.terminalName = name; } + /** + * Parse a host connection string which may include prefixes for TLS (e.g. "L:host:port", "ssl:host:port", "y:host:port") + * or standard "host:port" formats. + */ + public static ConnectionConfig parseHostString(String hostStr, int defaultPort, TerminalModel defaultModel) { + if (hostStr == null || hostStr.trim().isEmpty()) { + return new ConnectionConfig("localhost", defaultPort, defaultModel); + } + String s = hostStr.trim(); + boolean tls = false; + + // Check TLS prefixes + if (s.startsWith("L:") || s.startsWith("l:")) { + tls = true; + s = s.substring(2); + } else if (s.startsWith("Y:") || s.startsWith("y:")) { + tls = true; + s = s.substring(2); + } else if (s.toLowerCase().startsWith("ssl:") || s.toLowerCase().startsWith("tls:")) { + tls = true; + s = s.substring(4); + } + + String host = s; + int port = (defaultPort > 0) ? defaultPort : (tls ? 992 : 23); + + // Check for host:port (handle IPv6 [::1]:port) + if (s.startsWith("[") && s.contains("]")) { + int closeBracket = s.indexOf(']'); + host = s.substring(1, closeBracket); + if (s.length() > closeBracket + 1 && s.charAt(closeBracket + 1) == ':') { + try { + port = Integer.parseInt(s.substring(closeBracket + 2)); + } catch (NumberFormatException ignored) {} + } + } else { + int colon = s.lastIndexOf(':'); + if (colon > 0 && colon < s.length() - 1) { + try { + port = Integer.parseInt(s.substring(colon + 1)); + host = s.substring(0, colon); + } catch (NumberFormatException ignored) {} + } + } + + ConnectionConfig config = new ConnectionConfig(host, port, defaultModel != null ? defaultModel : TerminalModel.IBM_3279_4); + config.setUseTls(tls); + return config; + } + /** * Get the effective terminal type string to send during negotiation. */ diff --git a/lib3270j/src/main/java/org/lib3270j/Telnet3270Client.java b/lib3270j/src/main/java/org/lib3270j/Telnet3270Client.java index 4e9f1ef..def3f0c 100644 --- a/lib3270j/src/main/java/org/lib3270j/Telnet3270Client.java +++ b/lib3270j/src/main/java/org/lib3270j/Telnet3270Client.java @@ -46,12 +46,14 @@ public class Telnet3270Client { this.translator = new EbcdicTranslator(); this.screenBuffer = new ScreenBuffer(config.getModel(), translator); this.dsProcessor = new DataStreamProcessor(screenBuffer, translator); + this.dsProcessor.getQueryReplyBuilder().setGraphicsMode(config.getGraphicsMode()); this.fsm = new TelnetFSM(config, screenBuffer, dsProcessor); this.inputProcessor = new InputProcessor(screenBuffer, translator, fsm); // Wire up the output sender: DSProcessor -> FSM -> TelnetConnection dsProcessor.setOutputSender(fsm::send3270Data); dsProcessor.setInputProcessor(inputProcessor); + inputProcessor.setGraphicsPlane(dsProcessor.getGraphicsPlane()); } /** @@ -122,6 +124,16 @@ public class Telnet3270Client { /** Get the connection config. */ public ConnectionConfig getConfig() { return config; } + /** Set a custom or interactive TLS certificate verifier callback. */ + public void setTlsCertificateVerifier(org.lib3270j.tls.TlsCertificateVerifier verifier) { + config.setCertificateVerifier(verifier); + } + + /** Get the active SSLSession if connected over TLS, or null. */ + public javax.net.ssl.SSLSession getSslSession() { + return connection.getSslSession(); + } + // ========== Convenience input methods ========== /** Type a character at the cursor position. */ @@ -210,4 +222,16 @@ public class Telnet3270Client { public void sysReq() { inputProcessor.sysReq(); } /** Reset (unlock keyboard). */ public void reset() { inputProcessor.reset(); } + + public org.lib3270j.graphics.ProgramSymbolManager getProgramSymbolManager() { + return dsProcessor.getProgramSymbolManager(); + } + + public org.lib3270j.graphics.GraphicsPlane getGraphicsPlane() { + return dsProcessor.getGraphicsPlane(); + } + + public org.lib3270j.graphics.GocaDecoder getGocaDecoder() { + return dsProcessor.getGocaDecoder(); + } } diff --git a/lib3270j/src/main/java/org/lib3270j/charset/EbcdicTranslator.java b/lib3270j/src/main/java/org/lib3270j/charset/EbcdicTranslator.java index 862d0cb..2317b6e 100644 --- a/lib3270j/src/main/java/org/lib3270j/charset/EbcdicTranslator.java +++ b/lib3270j/src/main/java/org/lib3270j/charset/EbcdicTranslator.java @@ -85,6 +85,20 @@ public class EbcdicTranslator { return (char) CP037_TO_UNICODE[ebc & 0xFF]; } + /** + * Static helper to translate EBCDIC byte to Unicode character. + */ + public static char toUnicode(int ebc) { + return (char) CP037_TO_UNICODE[ebc & 0xFF]; + } + + /** + * Static helper to translate EBCDIC byte to ASCII character. + */ + public static char ebcdicToAscii(int ebc) { + return toUnicode(ebc); + } + /** * Translate Unicode character to EBCDIC byte. * Returns -1 if the character cannot be mapped. diff --git a/lib3270j/src/main/java/org/lib3270j/datastream/DataStreamProcessor.java b/lib3270j/src/main/java/org/lib3270j/datastream/DataStreamProcessor.java index b70fdf2..df9431a 100644 --- a/lib3270j/src/main/java/org/lib3270j/datastream/DataStreamProcessor.java +++ b/lib3270j/src/main/java/org/lib3270j/datastream/DataStreamProcessor.java @@ -38,6 +38,13 @@ public class DataStreamProcessor { // Input processor reference to manage keyboard locking state private org.lib3270j.input.InputProcessor inputProcessor; + // Graphics & Programmed Symbols + private final org.lib3270j.graphics.ProgramSymbolManager programSymbolManager = new org.lib3270j.graphics.ProgramSymbolManager(); + private final org.lib3270j.graphics.GraphicsPlane graphicsPlane = new org.lib3270j.graphics.GraphicsPlane(800, 600); + private final org.lib3270j.graphics.GocaDecoder gocaDecoder = new org.lib3270j.graphics.GocaDecoder(graphicsPlane); + private final java.io.ByteArrayOutputStream gocaAccumulator = new java.io.ByteArrayOutputStream(); + private int currentGocaSubtype = 0; + /** Functional interface for sending output back through the telnet stack. */ @FunctionalInterface public interface OutputSender { @@ -50,6 +57,23 @@ public class DataStreamProcessor { this.qrBuilder = new QueryReplyBuilder(screen); this.outputBuffer = new byte[32768]; this.outputPos = 0; + this.gocaDecoder.setProgramSymbolManager(programSymbolManager); + } + + public QueryReplyBuilder getQueryReplyBuilder() { + return qrBuilder; + } + + public org.lib3270j.graphics.ProgramSymbolManager getProgramSymbolManager() { + return programSymbolManager; + } + + public org.lib3270j.graphics.GraphicsPlane getGraphicsPlane() { + return graphicsPlane; + } + + public org.lib3270j.graphics.GocaDecoder getGocaDecoder() { + return gocaDecoder; } public void setOutputSender(OutputSender sender) { @@ -94,6 +118,7 @@ public class DataStreamProcessor { int oldRows = screen.getRows(); int oldCols = screen.getCols(); screen.erase(false); + graphicsPlane.clear(); processWrite(data, offset, length, true); if (oldRows != screen.getRows() || oldCols != screen.getCols()) { notifyScreenSizeChanged(); @@ -106,6 +131,7 @@ public class DataStreamProcessor { int oldRows = screen.getRows(); int oldCols = screen.getCols(); screen.erase(true); + graphicsPlane.clear(); processWrite(data, offset, length, true); if (oldRows != screen.getRows() || oldCols != screen.getCols()) { notifyScreenSizeChanged(); @@ -741,6 +767,7 @@ public class DataStreamProcessor { if (fieldLen >= 4) { boolean alt = (data[pos + 3] & 0xFF) == SF_ER_ALT; screen.erase(alt); + graphicsPlane.clear(); notifyScreenSizeChanged(); } break; @@ -751,6 +778,7 @@ public class DataStreamProcessor { break; case SF_CREATE_PART: // Acknowledged — we use implicit partition + graphicsPlane.clear(); break; case SF_OUTBOUND_DS: if (fieldLen > 5) { @@ -765,6 +793,83 @@ public class DataStreamProcessor { log.fine("SF_TRANSFER_DATA received but ftDft is null"); } break; + case org.lib3270j.graphics.GocaConstants.SF_LOADPS_DIRECT: // 0x06: Load Programmed Symbols direct + if (fieldLen > 3) { + byte[] psData = new byte[fieldLen - 3]; + System.arraycopy(data, pos + 3, psData, 0, psData.length); + programSymbolManager.loadps(psData); + notifyScreenUpdated(); + } + break; + case org.lib3270j.graphics.GocaConstants.SF_LOADPS: { // 0x0F: 2-byte Structured Field prefix + if (fieldLen >= 4) { + int sfSubId = data[pos + 3] & 0xFF; + switch (sfSubId) { + case org.lib3270j.graphics.GocaConstants.SF_LOADPS_SUB: // 0x06: Load Programmed Symbols + if (fieldLen > 4) { + byte[] psData = new byte[fieldLen - 4]; + System.arraycopy(data, pos + 4, psData, 0, psData.length); + programSymbolManager.loadps(psData); + notifyScreenUpdated(); + } + break; + case org.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB: // 0x11: Object Control (Procedure orders) + case org.lib3270j.graphics.GocaConstants.SF_OBJPICT_SUB: // 0x10: Object Picture (Picture segments) + case org.lib3270j.graphics.GocaConstants.SF_OBJDATA_SUB: { // 0x0F: Object Data (GOCA draw orders) + int flags = (fieldLen >= 6) ? (data[pos + 5] & 0xC0) : 0xC0; + int orderOffset = (fieldLen >= 7) ? (pos + 7) : (pos + 4); + int orderLen = Math.max(0, fieldLen - (orderOffset - pos)); + + graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows()); + + if (flags == 0x80) { // SPAN_FIRST + gocaAccumulator.reset(); + currentGocaSubtype = sfSubId; + if (orderLen > 0) { + gocaAccumulator.write(data, orderOffset, orderLen); + } + } else if (flags == 0x00) { // SPAN_MIDDLE + if (orderLen > 0) { + gocaAccumulator.write(data, orderOffset, orderLen); + } + } else if (flags == 0x40) { // SPAN_LAST + if (orderLen > 0) { + gocaAccumulator.write(data, orderOffset, orderLen); + } + byte[] fullStream = gocaAccumulator.toByteArray(); + gocaAccumulator.reset(); + if (currentGocaSubtype == org.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB) { + gocaDecoder.processProcedureOrders(fullStream, 0, fullStream.length); + } else { + gocaDecoder.decodeStream(fullStream, 0, fullStream.length); + } + notifyScreenUpdated(); + } else { // SPAN_ONLY (0xC0) + gocaAccumulator.reset(); + if (sfSubId == org.lib3270j.graphics.GocaConstants.SF_OBJCNTL_SUB) { + gocaDecoder.processProcedureOrders(data, orderOffset, orderLen); + } else { + gocaDecoder.decodeStream(data, orderOffset, orderLen); + } + notifyScreenUpdated(); + } + break; + } + default: + log.fine("Unknown SF 0x0F subtype: " + String.format("0x%02x", sfSubId)); + break; + } + } + break; + } + case org.lib3270j.graphics.GocaConstants.SF_OBJDATA: // 0x85: Graphics Object Data / GOCA + case org.lib3270j.graphics.GocaConstants.SF_3270_G: // 0x20: 3270 Graphics + if (fieldLen > 3) { + graphicsPlane.setScreenDimensions(screen.getCols(), screen.getRows()); + gocaDecoder.decodeStream(data, pos + 3, fieldLen - 3); + notifyScreenUpdated(); + } + break; default: log.fine("Unknown SF id: " + String.format("0x%02x", sfId)); break; @@ -887,4 +992,10 @@ public class DataStreamProcessor { l.onScreenSizeChanged(screen.getRows(), screen.getCols()); } } + + private void notifyScreenUpdated() { + for (ScreenUpdateListener l : screenListeners) { + l.onScreenUpdated(); + } + } } diff --git a/lib3270j/src/main/java/org/lib3270j/datastream/QueryReplyBuilder.java b/lib3270j/src/main/java/org/lib3270j/datastream/QueryReplyBuilder.java index 6ae25c2..32b6f70 100644 --- a/lib3270j/src/main/java/org/lib3270j/datastream/QueryReplyBuilder.java +++ b/lib3270j/src/main/java/org/lib3270j/datastream/QueryReplyBuilder.java @@ -3,12 +3,13 @@ package org.lib3270j.datastream; import java.io.ByteArrayOutputStream; import java.util.logging.Logger; +import org.lib3270j.graphics.GraphicsMode; import org.lib3270j.screen.ScreenBuffer; import static org.lib3270j.protocol.DS3270Constants.*; /** * Builds Query Reply structured fields in response to host Read Partition queries. - * Equivalent to the do_qr_* functions in sf.c. + * Matches IBM 3270 Architecture (GA23-0059) and x3270 sf.c exact binary layout. */ public class QueryReplyBuilder { @@ -21,9 +22,10 @@ public class QueryReplyBuilder { private static final int Yr_3279_2 = 0x0002006f; private final ScreenBuffer screen; + private GraphicsMode graphicsMode = GraphicsMode.NONE; - // Supported query reply codes (must match what we send in summary) - private static final int[] SUPPORTED_QR = { + // Base query reply codes (text mode) + private static final int[] SUPPORTED_QR_BASE = { QR_SUMMARY, // 0x80 — summary must list itself QR_USABLE_AREA, // 0x81 QR_ALPHA_PART, // 0x84 @@ -35,10 +37,45 @@ public class QueryReplyBuilder { QR_IMP_PART, // 0xa6 }; + // Vector graphics query reply codes matching HOD DS3270.java line 1723 + private static final int[] SUPPORTED_QR_VECTOR = { + QR_SUMMARY, // 0x80 + QR_USABLE_AREA, // 0x81 + QR_ALPHA_PART, // 0x84 + QR_CHARSETS, // 0x85 + QR_COLOR, // 0x86 + QR_HIGHLIGHTING, // 0x87 + QR_REPLY_MODES, // 0x88 + QR_SAVE_RESTORE, // 0x8c + QR_DDM, // 0x95 + QR_TRANSPARENCY, // 0x99 + QR_IMP_PART, // 0xa6 + QR_RPQ_NAMES, // 0xa8 + QR_GRAPHICS, // 0xb0 + QR_GIMAGE, // 0xb1 + QR_AUX_DEV, // 0xb2 + QR_OEM_FMT, // 0xb3 + QR_GCOLOR, // 0xb4 + QR_GSYMBOLS, // 0xb6 + }; + public QueryReplyBuilder(ScreenBuffer screen) { this.screen = screen; } + public QueryReplyBuilder(ScreenBuffer screen, GraphicsMode graphicsMode) { + this.screen = screen; + this.graphicsMode = (graphicsMode != null) ? graphicsMode : GraphicsMode.NONE; + } + + public GraphicsMode getGraphicsMode() { + return graphicsMode; + } + + public void setGraphicsMode(GraphicsMode graphicsMode) { + this.graphicsMode = (graphicsMode != null) ? graphicsMode : GraphicsMode.NONE; + } + /** * Build all query replies as a single AID_SF + structured field response. */ @@ -75,7 +112,20 @@ public class QueryReplyBuilder { // Implicit Partition appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows)); - log.info("Built " + out.size() + " bytes of all query replies"); + // Vector Graphics QRs if enabled + if (graphicsMode.isVectorGraphicsEnabled()) { + appendQueryReply(out, QR_SAVE_RESTORE, buildSaveRestore()); + appendQueryReply(out, QR_TRANSPARENCY, buildTransparency()); + appendQueryReply(out, QR_RPQ_NAMES, buildRpqNames()); + appendQueryReply(out, QR_GRAPHICS, buildGraphics()); + appendQueryReply(out, QR_GIMAGE, buildGImage()); + appendQueryReply(out, QR_AUX_DEV, buildAuxDev()); + appendOemFmt(out); + appendQueryReply(out, QR_GCOLOR, buildGColor()); + appendQueryReply(out, QR_GSYMBOLS, buildGSymbols()); + } + + log.info("Built " + out.size() + " bytes of all query replies (graphicsMode=" + graphicsMode + ")"); return out.toByteArray(); } @@ -116,14 +166,75 @@ public class QueryReplyBuilder { case QR_REPLY_MODES: appendQueryReply(out, QR_REPLY_MODES, buildReplyModes()); break; + case QR_SAVE_RESTORE: + if (graphicsMode.isVectorGraphicsEnabled()) { + appendQueryReply(out, QR_SAVE_RESTORE, buildSaveRestore()); + } else { + appendQueryReply(out, QR_NULL, new byte[0]); + } + break; case QR_DDM: appendQueryReply(out, QR_DDM, buildDdm(4096)); break; + case QR_TRANSPARENCY: + if (graphicsMode.isVectorGraphicsEnabled()) { + appendQueryReply(out, QR_TRANSPARENCY, buildTransparency()); + } else { + appendQueryReply(out, QR_NULL, new byte[0]); + } + break; case QR_IMP_PART: appendQueryReply(out, QR_IMP_PART, buildImplicitPartition(maxCols, maxRows)); break; + case QR_RPQ_NAMES: case QR_RPQNAMES: - appendQueryReply(out, QR_RPQNAMES, new byte[0]); + if (graphicsMode.isVectorGraphicsEnabled()) { + appendQueryReply(out, QR_RPQ_NAMES, buildRpqNames()); + } else { + appendQueryReply(out, QR_NULL, new byte[0]); + } + break; + case QR_GRAPHICS: + if (graphicsMode.isVectorGraphicsEnabled()) { + appendQueryReply(out, QR_GRAPHICS, buildGraphics()); + } else { + appendQueryReply(out, QR_NULL, new byte[0]); + } + break; + case QR_GIMAGE: + if (graphicsMode.isVectorGraphicsEnabled()) { + appendQueryReply(out, QR_GIMAGE, buildGImage()); + } else { + appendQueryReply(out, QR_NULL, new byte[0]); + } + break; + case QR_AUX_DEV: + if (graphicsMode.isVectorGraphicsEnabled()) { + appendQueryReply(out, QR_AUX_DEV, buildAuxDev()); + } else { + appendQueryReply(out, QR_NULL, new byte[0]); + } + break; + case QR_OEM_FMT: + if (graphicsMode.isVectorGraphicsEnabled()) { + appendOemFmt(out); + } else { + appendQueryReply(out, QR_NULL, new byte[0]); + } + break; + case QR_GCOLOR: + if (graphicsMode.isVectorGraphicsEnabled()) { + appendQueryReply(out, QR_GCOLOR, buildGColor()); + } else { + appendQueryReply(out, QR_NULL, new byte[0]); + } + break; + case QR_GSYMBOLS: + if (graphicsMode.isVectorGraphicsEnabled()) { + appendQueryReply(out, QR_GSYMBOLS, buildGSymbols()); + } else { + appendQueryReply(out, QR_NULL, new byte[0]); + } break; default: // Unsupported query reply code — emit QR_NULL @@ -148,7 +259,8 @@ public class QueryReplyBuilder { private byte[] buildSummary() { ByteArrayOutputStream out = new ByteArrayOutputStream(); - for (int code : SUPPORTED_QR) { + int[] codes = graphicsMode.isVectorGraphicsEnabled() ? SUPPORTED_QR_VECTOR : SUPPORTED_QR_BASE; + for (int code : codes) { out.write(code); } return out.toByteArray(); @@ -184,66 +296,81 @@ public class QueryReplyBuilder { private byte[] buildAlphaPartitions(int maxRows) { int bufSize = screen.getMaxCols() * screen.getMaxRows(); ByteArrayOutputStream out = new ByteArrayOutputStream(4); - out.write(0x00); // max partitions (1 partition) + out.write(0x00); // max partitions (1 byte: 0x00 = 1 partition) out.write((bufSize >> 8) & 0xFF); // total partition storage high out.write(bufSize & 0xFF); // total partition storage low - out.write(0x00); // no special features + out.write(0x00); // flags return out.toByteArray(); } private byte[] buildCharsets() { - ByteArrayOutputStream out = new ByteArrayOutputStream(32); + if (graphicsMode.isVectorGraphicsEnabled() || graphicsMode == GraphicsMode.NONE) { + // Standard 3179G / Base character sets (matches HOD QR_CHARSETS_S_STRING, 27 bytes total) + ByteArrayOutputStream out = new ByteArrayOutputStream(23); + out.write(0x82); // flags: GE, CGCSGID present + out.write(0x00); // more flags + out.write(SW_3279_2); // SDW - default char width (9) + out.write(14); // SDH - default char height (14) + out.write(0x00); // LoadPS format (0x00) + out.write(0x00); + out.write(0x00); + out.write(0x00); + out.write(0x07); // DL = 7 + // Set 0 (Base EBCDIC) + out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x02); out.write(0xb9); out.write(0x01); out.write(0xf4); + // Set 1 (APL/Text) + out.write(0x01); out.write(0x00); out.write(0xf1); out.write(0x03); out.write(0xc3); out.write(0x01); out.write(0x36); + return out.toByteArray(); + } + + // Programmed Symbols mode (3279 PS) + ByteArrayOutputStream out = new ByteArrayOutputStream(65); out.write(0x82); // flags: GE, CGCSGID present out.write(0x00); // more flags - out.write(SW_3279_2); // SDW - default char width - out.write(SH_3279_2); // SDH - default char height - out.write(0x00); // Load PS format types supported: none + out.write(SW_3279_2); // SDW (9) + out.write(14); // SDH (14) + out.write(0x0a); // Load PS format types supported: Format 1 and Format 3 out.write(0x00); // Load PS device type (high) out.write(0x00); // Load PS device type (low) out.write(0x00); // reserved - out.write(0x07); // DL = 7 bytes per descriptor (non-DBCS) + out.write(0x07); // DL = 7 bytes per descriptor // Descriptor 1 (SET 0): default character set - out.write(0x00); // SET 0 - out.write(0x10); // FLAGS: non-loadable, single-plane, single-byte, no compare - out.write(0x00); // LCID 0 - out.write(0x02); // CGCSGID (4 bytes) = 0x02b90025 (CGEN|CSET) - out.write(0xb9); - out.write(0x00); - out.write(0x25); + out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x02); out.write(0xb9); out.write(0x01); out.write(0xf4); // Descriptor 2 (SET 1): APL/GE character set - out.write(0x01); // SET 1 - out.write(0x00); // FLAGS: non-loadable, single-plane, single-byte, no compare - out.write(0xf1); // LCID 0xf1 - out.write(0x03); // CGCSGID: 3179-style APL2 = 0x03c30136 - out.write(0xc3); - out.write(0x01); - out.write(0x36); + out.write(0x01); out.write(0x00); out.write(0xf1); out.write(0x03); out.write(0xc3); out.write(0x01); out.write(0x36); + // Loadable Single-Plane PS Sets (PSA, PSB: Slots 2, 3) + out.write(0x02); out.write(0x80); out.write(0x40); out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x00); + out.write(0x03); out.write(0x80); out.write(0x41); out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x00); + // Loadable Triple-Plane PS Sets (PSC, PSD, PSE, PSF: Slots 4, 5, 6, 7) + out.write(0x04); out.write(0x80); out.write(0x42); out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x00); + out.write(0x05); out.write(0x80); out.write(0x43); out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x00); + out.write(0x06); out.write(0x80); out.write(0x44); out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x00); + out.write(0x07); out.write(0x80); out.write(0x45); out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x00); return out.toByteArray(); } private byte[] buildColor() { - ByteArrayOutputStream out = new ByteArrayOutputStream(36); - int colorMax = 16; - out.write(0x00); // no options - out.write(colorMax); // number of colors - out.write(0x00); // default color pair: attribute - out.write(0xf0 + HOST_COLOR_GREEN); // default color: green - for (int i = 0xf1; i < 0xf1 + colorMax - 1; i++) { - out.write(i); // color attribute value - out.write(i); // maps to itself (color mode) - } - return out.toByteArray(); + // Alphanumeric Color (matches HOD: 8 pairs, F1..F7 + default F4 green, 18 bytes payload / 22 bytes total) + return new byte[] { + 0x00, 0x08, 0x00, (byte) 0xF4, + (byte) 0xF1, (byte) 0xF1, // Blue + (byte) 0xF2, (byte) 0xF2, // Red + (byte) 0xF3, (byte) 0xF3, // Pink + (byte) 0xF4, (byte) 0xF4, // Green + (byte) 0xF5, (byte) 0xF5, // Turquoise + (byte) 0xF6, (byte) 0xF6, // Yellow + (byte) 0xF7, (byte) 0xF7 // Neutral/White + }; } private byte[] buildHighlighting() { - ByteArrayOutputStream out = new ByteArrayOutputStream(11); - out.write(5); // 5 pairs - out.write(XAH_DEFAULT); out.write(XAH_NORMAL); - out.write(XAH_BLINK); out.write(XAH_BLINK); - out.write(XAH_REVERSE); out.write(XAH_REVERSE); - out.write(XAH_UNDERSCORE); out.write(XAH_UNDERSCORE); - out.write(XAH_INTENSIFY); out.write(XAH_INTENSIFY); - return out.toByteArray(); + // Highlighting (matches HOD: 4 pairs: default F0, Blink F1, Reverse F2, Underscore F4, 9 bytes payload / 13 bytes total) + return new byte[] { + 0x04, 0x00, (byte) 0xF0, + (byte) 0xF1, (byte) 0xF1, + (byte) 0xF2, (byte) 0xF2, + (byte) 0xF4, (byte) 0xF4 + }; } private byte[] buildReplyModes() { @@ -265,21 +392,20 @@ public class QueryReplyBuilder { private byte[] buildImplicitPartition(int maxCols, int maxRows) { ByteArrayOutputStream out = new ByteArrayOutputStream(22); - // Implicit partition sizes SDP (Self-Defining Parameter) + // Implicit partition sizes, 2 self-defining parameters + // SDP 1: Default screen size (Model 2: 80x24) out.write(0x00); // flags out.write(0x00); // reserved - // SDP 1: Implicit partition sizes - out.write(0x00); // SDP length high byte - out.write(0x0b); // SDP length low byte (11 bytes: 2 len + 1 type + 1 res + 8 dims) + out.write(0x0b); // SDP length (11 bytes) out.write(0x01); // SDP type: implicit partition sizes out.write(0x00); // reserved - // Default dimensions (Model 2: 80x24) + // Default size out.write((MODEL_2_COLS >> 8) & 0xFF); out.write(MODEL_2_COLS & 0xFF); out.write((MODEL_2_ROWS >> 8) & 0xFF); out.write(MODEL_2_ROWS & 0xFF); - // Alternate dimensions + // Alternate size (Model 4: 80x43, Model 5: 132x27, etc.) out.write((maxCols >> 8) & 0xFF); out.write(maxCols & 0xFF); out.write((maxRows >> 8) & 0xFF); @@ -287,4 +413,80 @@ public class QueryReplyBuilder { return out.toByteArray(); } + + private byte[] buildGraphics() { + return new byte[]{ (byte) 0x80, 0x02, 0x00, 0x00, 0x00, (byte) 0xFC, 0x00 }; + } + + private byte[] buildGImage() { + return new byte[]{ + 0x00, 0x01, 0x00, 0x00, 0x00, (byte) 0xFC, 0x00, + 0x06, 0x40, 0x06, 0x40, 0x06, 0x01, + (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xF0 + }; + } + + private byte[] buildAuxDev() { + return new byte[]{ + 0x00, 0x09, 0x00, 0x07, + 0x01, 0x01, 0x02, 0x02, 0x03, 0x03, 0x04, 0x04, + 0x05, 0x05, 0x06, 0x06, 0x07, 0x07, 0x08, 0x08 + }; + } + + private byte[] buildSaveRestore() { + // HOD DS3270.java line 1768: 6 bytes payload + return new byte[]{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + } + + private byte[] buildTransparency() { + // HOD DS3270.java line 1782: 2 bytes payload + return new byte[]{ 0x00, 0x00 }; + } + + private byte[] buildRpqNames() { + // HOD DS3270.java line 1798: 5 bytes payload + return new byte[]{ 0x02, 0x00, (byte) 0xF0, (byte) 0xFF, (byte) 0xFF }; + } + + private void appendOemFmt(ByteArrayOutputStream out) { + // HOD DS3270.java line 1814: 4 distinct OEM format sub-fields + appendQueryReply(out, QR_OEM_FMT, new byte[]{ + 0x00, 0x03, 0x02, (byte) 0x90, 0x09, 0x01, 0x00, 0x03, 0x02, (byte) 0x80, 0x00, 0x7F, (byte) 0xFF + }); + appendQueryReply(out, QR_OEM_FMT, new byte[]{ + 0x00, 0x04, 0x08, 0x40, 0x07, 0x03, 0x00, 0x03, (byte) 0x80, 0x00, 0x02 + }); + appendQueryReply(out, QR_OEM_FMT, new byte[]{ + 0x00, 0x05, 0x02, (byte) 0x80, 0x09, 0x01, 0x00, 0x01, 0x02, (byte) 0x80, 0x00, 0x7F, (byte) 0xFF + }); + appendQueryReply(out, QR_OEM_FMT, new byte[]{ + 0x00, 0x07, 0x08, 0x40, 0x07, 0x03, 0x00, 0x01, 0x00, 0x00, 0x1C + }); + } + + private byte[] buildGColor() { + ByteArrayOutputStream out = new ByteArrayOutputStream(110); + out.write(0x00); out.write(0x04); out.write(0x00); out.write(0xFF); out.write(0xFF); + out.write(0x00); out.write(0x10); out.write(0x00); out.write(0x10); + + for (int i = 0; i < 16; i++) { + out.write(0x00); + out.write(i); + java.awt.Color c = org.lib3270j.graphics.GocaConstants.GOCA_COLORS[i]; + out.write(c.getRed()); + out.write(c.getGreen()); + out.write(c.getBlue()); + out.write(0x00); // 6th byte in HOD color table + } + return out.toByteArray(); + } + + private byte[] buildGSymbols() { + return new byte[]{ + 0x00, 0x00, 0x0C, 0x18, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x12, + 0x01, 0x00, 0x00, (byte) 0xF0, (byte) 0xC1, (byte) 0xC1, 0x00, 0x00, + 0x0C, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + } } diff --git a/lib3270j/src/main/java/org/lib3270j/graphics/GocaConstants.java b/lib3270j/src/main/java/org/lib3270j/graphics/GocaConstants.java new file mode 100644 index 0000000..2bcc082 --- /dev/null +++ b/lib3270j/src/main/java/org/lib3270j/graphics/GocaConstants.java @@ -0,0 +1,173 @@ +package org.lib3270j.graphics; + +/** + * Constants for IBM 3179G / 3270-PC GOCA (Graphics Object Content Architecture) + * vector graphics and Programmed Symbols (PS). + */ +public final class GocaConstants { + + private GocaConstants() {} + + // Structured Field IDs + public static final int SF_LOADPS = 0x0F; // 2-byte Structured Field prefix (or 0x06 for direct Load PS) + public static final int SF_LOADPS_DIRECT = 0x06; // Load Programmed Symbols direct SFID + public static final int SF_OBJCNTL = 0x24; // Object Control + public static final int SF_OBJDATA = 0x85; // Graphics Object Data + public static final int SF_3270_G = 0x20; // 3270 Graphics / Picture + + // Structured Field Sub-IDs (for SF 0x0F) + public static final int SF_LOADPS_SUB = 0x06; // Load Programmed Symbols + public static final int SF_LOADLT_SUB = 0x07; // Load Line Type / Symbol Set + public static final int SF_OBJDATA_SUB = 0x0F; // Graphics Object Data (GOCA draw orders) + public static final int SF_OBJPICT_SUB = 0x10; // Graphics Object Picture (Segment draw orders) + public static final int SF_OBJCNTL_SUB = 0x11; // Graphics Object Control (Procedure orders) + + // Procedure Orders (for SF_OBJCNTL_SUB 0x11) + public static final int P_NOP1 = 0x00; // Procedure NOOP + public static final int P_COMT = 0x01; // Procedure Comment + public static final int P_ATTCUR = 0x08; // Attach Graphic Cursor + public static final int P_DETCUR = 0x09; // Detach Graphic Cursor + public static final int P_ERASE = 0x0A; // Erase Graphics Presentation Space + public static final int P_STOPDR = 0x0F; // Stop Draw + public static final int P_SCUDEF = 0x21; // Set Current Defaults + public static final int P_BEGPROC = 0x30; // Begin Procedure + public static final int P_SETCUR = 0x31; // Set Graphic Cursor Position + + // Coordinate space + public static final int VIRTUAL_COORD_MAX = 4096; + + // GOCA Drawing / Segment Orders + public static final int G_NOP1 = 0x00; // NOOP 1-byte + public static final int G_COMT = 0x01; // Comment + public static final int G_GSMC = 0x07; // Set Marker Color + public static final int G_GSPS = 0x08; // Set Pattern Set + public static final int G_GSCOL = 0x0A; // Set Color + public static final int G_GSMX = 0x0C; // Set Foreground Mix + public static final int G_GSBMX = 0x0D; // Set Background Mix + public static final int G_GSFLW = 0x11; // Set Fractional Line Width + public static final int G_GSLT = 0x18; // Set Line Type + public static final int G_GSLW = 0x19; // Set Line Width + public static final int G_GSMS = 0x1B; // Set Marker Size + public static final int G_GSCP = 0x21; // Set Current Position + public static final int G_GSAP = 0x22; // Arc Parameters + public static final int G_GSECOL = 0x26; // Set Extended Color + public static final int G_GSVW = 0x27; // Set Viewing Window + public static final int G_GSPT = 0x28; // Set Pattern Symbol + public static final int G_GSMT = 0x29; // Set Marker Symbol / Type + public static final int G_GSCH = 0x33; // Set Character Cell + public static final int G_GSCA = 0x34; // Set Character Angle + public static final int G_GSCR = 0x35; // Set Character Shear + public static final int G_GSMCEL = 0x37; // Set Marker Cell + public static final int G_GSCS = 0x38; // Set Character Set + public static final int G_GSMP = 0x39; // Set Marker Precision + public static final int G_GSCD = 0x3A; // Set Character Direction + public static final int G_GSCC = 0x3B; // Set Character Precision + public static final int G_GSMS_SET = 0x3C; // Set Marker Set + public static final int G_ENDPROLOGUE = 0x3E; // End Prologue + public static final int G_GPOP = 0x3F; // Pop Attribute + public static final int G_GEAR = 0x60; // End Area + public static final int G_GBAR = 0x68; // Begin Area + public static final int G_BEGSEGM = 0x70; // Begin Segment + public static final int G_ENDSEGM = 0x71; // End Segment + public static final int G_GERASE = 0x7E; // Erase Graphics Plane + public static final int G_GCLINE = 0x81; // Line at Current Position + public static final int G_GCMRK = 0x82; // Marker at Current Position + public static final int G_GCCHST = 0x83; // Character String at Current Position + public static final int G_GCFLT = 0x85; // Fillet at Current Position + public static final int G_GCARC = 0x86; // Partial Arc at Current Position + public static final int G_GCFARC = 0x87; // Full Arc at Current Position + public static final int G_GEIMG = 0x91; // End Image + public static final int G_GIMD = 0x92; // Image Data + public static final int G_GCRLIN = 0xA1; // Relative Line at Current Position + public static final int G_GLINE = 0xC1; // Line (Absolute) + public static final int G_GMRK = 0xC2; // Marker (Absolute) + public static final int G_GCHST = 0xC3; // Character String (Absolute) + public static final int G_GFLT = 0xC5; // Fillet (Absolute) + public static final int G_GARC = 0xC6; // Partial Arc (Absolute) + public static final int G_GFARC = 0xC7; // Full Arc (Absolute) + public static final int G_GBIMG = 0xD1; // Begin Image + public static final int G_GRLINE = 0xE1; // Relative Line (Absolute Start) + + // Line Types (GSLT) + public static final int LT_DEFAULT = 0; + public static final int LT_DOT = 1; + public static final int LT_SHORTDASH= 2; + public static final int LT_DASHDOT = 3; + public static final int LT_DOUBLEDOT= 4; + public static final int LT_LONGDASH = 5; + public static final int LT_DASHDOUBLEDOT = 6; + public static final int LT_SOLID = 7; + + // Line Widths (GSLW) + public static final int LW_DEFAULT = 0; + public static final int LW_NORMAL = 1; + public static final int LW_THICK = 2; + + // Fill Patterns (GSPT) + public static final int PT_DEFAULT = 0; + public static final int PT_D1 = 1; + public static final int PT_D2 = 2; + public static final int PT_D3 = 3; + public static final int PT_D4 = 4; + public static final int PT_D5 = 5; + public static final int PT_D6 = 6; + public static final int PT_D7 = 7; + public static final int PT_D8 = 8; + public static final int PT_VERT_LINE = 9; + public static final int PT_HORIZ_LINE = 10; + public static final int PT_DIAG_BLTR = 11; + public static final int PT_DIAG_BLTR2 = 12; + public static final int PT_DIAG_TLBR = 13; + public static final int PT_DIAG_TLBR2 = 14; + public static final int PT_EMPTY = 15; + public static final int PT_SOLID = 16; + + // Marker Symbols (GSMT) + public static final int MK_DEFAULT = 0; + public static final int MK_CROSS = 1; // x + public static final int MK_PLUS = 2; // + + public static final int MK_DIAMOND = 3; // <> + public static final int MK_SQUARE = 4; // [] + public static final int MK_6STAR = 5; // * 6-point + public static final int MK_8STAR = 6; // * 8-point + public static final int MK_SDIAMOND = 7; // solid diamond + public static final int MK_SSQUARE = 8; // solid square + public static final int MK_DOT = 9; // . + public static final int MK_CIRCLE = 10;// o + + // Character Direction (GSCD) + public static final int CD_DEFAULT = 0; + public static final int CD_LR = 1; // Left to Right + public static final int CD_TB = 2; // Top to Bottom + public static final int CD_RL = 3; // Right to Left + public static final int CD_BT = 4; // Bottom to Top + + // Foreground / Background Mix Modes (GSMX / GSBMX) + public static final int MIX_DEFAULT = 0; + public static final int MIX_OR = 1; + public static final int MIX_OVER = 2; + public static final int MIX_LEAVE = 3; + public static final int MIX_XOR = 4; + public static final int MIX_UNDER = 5; + + // Graphic Colors (IBM 3179G / HOD 16-color table) + public static final java.awt.Color[] GOCA_COLORS = new java.awt.Color[] { + new java.awt.Color(0, 255, 0), // 0: Default (Green) + new java.awt.Color(120, 144, 240), // 1: Blue + new java.awt.Color(255, 0, 0), // 2: Red + new java.awt.Color(255, 0, 255), // 3: Pink / Magenta + new java.awt.Color(0, 255, 0), // 4: Green + new java.awt.Color(0, 255, 255), // 5: Turquoise / Cyan + new java.awt.Color(255, 255, 0), // 6: Yellow + new java.awt.Color(255, 255, 255), // 7: Neutral White + new java.awt.Color(0, 0, 0), // 8: Black + new java.awt.Color(0, 0, 128), // 9: Deep Blue + new java.awt.Color(128, 0, 0), // 10: Orange / Dark Red + new java.awt.Color(128, 0, 128), // 11: Purple + new java.awt.Color(0, 128, 0), // 12: Pale Green + new java.awt.Color(0, 128, 128), // 13: Pale Cyan + new java.awt.Color(215, 151, 0), // 14: Mustard + new java.awt.Color(192, 192, 192), // 15: Grey / Light White + new java.awt.Color(73, 36, 0) // 16: Brown + }; +} diff --git a/lib3270j/src/main/java/org/lib3270j/graphics/GocaDecoder.java b/lib3270j/src/main/java/org/lib3270j/graphics/GocaDecoder.java new file mode 100644 index 0000000..21751bf --- /dev/null +++ b/lib3270j/src/main/java/org/lib3270j/graphics/GocaDecoder.java @@ -0,0 +1,819 @@ +package org.lib3270j.graphics; + +import java.awt.Color; +import java.util.ArrayList; +import java.util.List; +import java.awt.geom.AffineTransform; +import java.awt.geom.Path2D; +import java.util.logging.Logger; + +import org.lib3270j.charset.EbcdicTranslator; + +/** + * Interprets GOCA (Graphics Object Content Architecture) drawing orders + * and updates the GraphicsPlane. + */ +public class GocaDecoder { + + private static final Logger logger = Logger.getLogger(GocaDecoder.class.getName()); + + private final GraphicsPlane plane; + + // Drawing state + private int curX = 0; + private int curY = 0; + private Color curColor = Color.GREEN; + private int lineType = GocaConstants.LT_SOLID; + private int lineWidth = GocaConstants.LW_NORMAL; + private int markerType = GocaConstants.MK_PLUS; + private int markerSize = 5; + private Color markerColor = Color.GREEN; + private int pattern = GocaConstants.PT_SOLID; + private Color fillColor = Color.GREEN; + private int charDir = GocaConstants.CD_LR; + private double charAngle = 0.0; + private int charWidth = 9; + private int charHeight = 16; + private int charSet = 0; + private int arcParamP = 1; + private int arcParamQ = 1; + private int arcParamR = 0; + private int arcParamS = 0; + + private ProgramSymbolManager programSymbolManager; + + // Area accumulation + private boolean inArea = false; + private boolean areaDrawBoundary = true; + private final List areaPointsX = new ArrayList<>(); + private final List areaPointsY = new ArrayList<>(); + + // Image accumulation + private boolean inImage = false; + private int imgX = 0; + private int imgY = 0; + private int imgWidth = 0; + private int imgHeight = 0; + private final List imgBuffer = new ArrayList<>(); + + public GocaDecoder(GraphicsPlane plane) { + this.plane = plane; + } + + public void setProgramSymbolManager(ProgramSymbolManager psm) { + this.programSymbolManager = psm; + } + + public GraphicsPlane getGraphicsPlane() { + return plane; + } + + public int getCurX() { + return curX; + } + + public int getCurY() { + return curY; + } + + public synchronized void resetDefaults() { + curX = 0; + curY = 0; + resetAttributes(); + } + + public synchronized void resetAttributes() { + curColor = getColor(0); + lineType = GocaConstants.LT_SOLID; + lineWidth = GocaConstants.LW_NORMAL; + markerType = GocaConstants.MK_PLUS; + markerSize = 5; + markerColor = curColor; + pattern = GocaConstants.PT_SOLID; + fillColor = curColor; + charDir = GocaConstants.CD_LR; + charAngle = 0.0; + charSet = 0; + inArea = false; + areaPointsX.clear(); + areaPointsY.clear(); + inImage = false; + imgBuffer.clear(); + } + + private byte[] partialOrderBuffer = new byte[0]; + + private int getOrderLength(byte[] data, int idx, int end) { + int order = data[idx] & 0xFF; + if (order == GocaConstants.G_NOP1 || order == 0xFF) { + return 1; + } + if (order == GocaConstants.G_GEAR || + order == GocaConstants.G_ENDSEGM || order == GocaConstants.G_ENDPROLOGUE || + order == GocaConstants.G_GEIMG || order == GocaConstants.G_GPOP) { + return (idx + 1 < end && data[idx + 1] == 0x00) ? 2 : 1; + } + if (order == 0x04 || order == GocaConstants.G_GSMC || order == GocaConstants.G_GSPS || + order == GocaConstants.G_GSCOL || order == GocaConstants.G_GSMX || + order == GocaConstants.G_GSBMX || order == GocaConstants.G_GSFLW || + order == GocaConstants.G_GSLT || order == GocaConstants.G_GSLW || + order == GocaConstants.G_GSMS || order == GocaConstants.G_GSPT || + order == GocaConstants.G_GSMT || order == GocaConstants.G_GSCS || + order == GocaConstants.G_GSMP || order == GocaConstants.G_GSCD || + order == GocaConstants.G_GSCC || order == GocaConstants.G_GSMS_SET || + order == GocaConstants.G_GBAR) { + return 2; + } + if (order == GocaConstants.G_GSAP || order == GocaConstants.G_GBIMG || order == 0x91) { + return 10; + } + if (idx + 1 >= end) { + return -1; + } + return (data[idx + 1] & 0xFF) + 2; + } + + /** + * Decodes a stream of GOCA drawing orders. + */ + public synchronized void decodeStream(byte[] data, int offset, int length) { + if (data == null || length <= 0 || offset < 0 || offset + length > data.length) { + return; + } + + byte[] inputData; + int idx; + int end; + + if (partialOrderBuffer.length > 0) { + inputData = new byte[partialOrderBuffer.length + length]; + System.arraycopy(partialOrderBuffer, 0, inputData, 0, partialOrderBuffer.length); + System.arraycopy(data, offset, inputData, partialOrderBuffer.length, length); + idx = 0; + end = inputData.length; + partialOrderBuffer = new byte[0]; + } else { + inputData = data; + idx = offset; + end = offset + length; + } + + while (idx < end) { + int order = inputData[idx] & 0xFF; + int orderLen = getOrderLength(inputData, idx, end); + + if (orderLen == -1 || idx + orderLen > end) { + int remaining = end - idx; + partialOrderBuffer = new byte[remaining]; + System.arraycopy(inputData, idx, partialOrderBuffer, 0, remaining); + break; + } + + int payloadLen = (orderLen >= 2) ? (inputData[idx + 1] & 0xFF) : 0; + + switch (order) { + case GocaConstants.G_NOP1: + case 0xFF: { + idx++; + break; + } + case GocaConstants.G_GEAR: { + endArea(); + idx += orderLen; + break; + } + case GocaConstants.G_GEIMG: { + endImage(); + idx += orderLen; + break; + } + case GocaConstants.G_BEGSEGM: { // Begin Segment (0x70) + if (idx + 7 < end && (inputData[idx + 7] & 0x06) == 0) { + resetAttributes(); + } + idx += orderLen; + break; + } + case GocaConstants.G_ENDSEGM: { // End Segment (0x71) + idx += orderLen; + break; + } + case GocaConstants.G_ENDPROLOGUE: { // End Prologue (0x3E) + idx += orderLen; + break; + } + case GocaConstants.G_COMT: { // Comment (0x01) + idx += orderLen; + break; + } + case GocaConstants.G_GSCP: { // Set Current Position (0x21) + if (payloadLen >= 4 && idx + 5 < end) { + curX = readCoord(inputData, idx + 2); + curY = readCoord(inputData, idx + 4); + } + idx += orderLen; + break; + } + case GocaConstants.G_GSAP: { // Arc Parameters (0x22) + if (idx + 9 < end) { + arcParamP = readCoord(inputData, idx + 2); + arcParamQ = readCoord(inputData, idx + 4); + arcParamR = readCoord(inputData, idx + 6); + arcParamS = readCoord(inputData, idx + 8); + } + idx += orderLen; + break; + } + case GocaConstants.G_GSVW: { // Set Viewing Window (0x27) + idx += orderLen; + break; + } + case GocaConstants.G_GSCA: { // Set Character Angle (0x34) + if (payloadLen >= 4 && idx + 5 < end) { + int ax = readCoord(inputData, idx + 2); + int ay = readCoord(inputData, idx + 4); + if (ax != 0 || ay != 0) { + charAngle = Math.toDegrees(Math.atan2(ay, ax)); + } + } + idx += orderLen; + break; + } + case GocaConstants.G_GSCH: { // Set Character Cell (0x33) + if (payloadLen >= 4 && idx + 5 < end) { + charWidth = readCoord(inputData, idx + 2); + charHeight = readCoord(inputData, idx + 4); + } + idx += orderLen; + break; + } + case GocaConstants.G_GSCR: { // Set Character Shear (0x35) + idx += orderLen; + break; + } + case GocaConstants.G_GSCOL: { // Set Color (0x0A) + int colIdx = inputData[idx + 1] & 0xFF; + curColor = getColor(colIdx); + fillColor = curColor; + idx += orderLen; + break; + } + case GocaConstants.G_GSECOL: { // Set Extended Color (0x26) + if (payloadLen >= 2 && idx + 3 < end) { + int colIdx = inputData[idx + 3] & 0xFF; + curColor = getColor(colIdx); + fillColor = curColor; + } + idx += orderLen; + break; + } + case GocaConstants.G_GSLT: { // Set Line Type (0x18) + lineType = inputData[idx + 1] & 0xFF; + idx += orderLen; + break; + } + case GocaConstants.G_GSLW: { // Set Line Width (0x19) + lineWidth = inputData[idx + 1] & 0xFF; + idx += orderLen; + break; + } + case GocaConstants.G_GSMC: { // Set Marker Color (0x07) + int colIdx = inputData[idx + 1] & 0xFF; + markerColor = getColor(colIdx); + idx += orderLen; + break; + } + case GocaConstants.G_GSMS: { // Set Marker Size (0x1B) + markerSize = inputData[idx + 1] & 0xFF; + idx += orderLen; + break; + } + case GocaConstants.G_GSMT: { // Set Marker Type (0x29) + markerType = inputData[idx + 1] & 0xFF; + idx += orderLen; + break; + } + case GocaConstants.G_GSPS: { // Set Pattern Set (0x08) + pattern = inputData[idx + 1] & 0xFF; + idx += orderLen; + break; + } + case GocaConstants.G_GSPT: { // Set Pattern Symbol (0x28) + pattern = inputData[idx + 1] & 0xFF; + idx += orderLen; + break; + } + case GocaConstants.G_GSCD: { // Set Character Direction (0x3A) + charDir = inputData[idx + 1] & 0xFF; + idx += orderLen; + break; + } + case GocaConstants.G_GSCS: { // Set Character Set (0x38) + charSet = inputData[idx + 1] & 0xFF; + idx += orderLen; + break; + } + case 0x04: + case GocaConstants.G_GSMX: + case GocaConstants.G_GSBMX: + case GocaConstants.G_GSFLW: + case GocaConstants.G_GSMP: + case GocaConstants.G_GSCC: + case GocaConstants.G_GSMS_SET: + case GocaConstants.G_GPOP: { + idx += orderLen; + break; + } + case GocaConstants.G_GBAR: { // Begin Area (0x68) + int flags = inputData[idx + 1] & 0xFF; + beginArea((flags & 0x40) != 0); + idx += orderLen; + break; + } + case GocaConstants.G_GLINE: { // Line Absolute (0xC1) + if (payloadLen >= 4) { + processLine(inputData, idx + 2, payloadLen, false); + } + idx += orderLen; + break; + } + case GocaConstants.G_GCLINE: { // Line Current Position (0x81) + if (payloadLen >= 4) { + processLine(inputData, idx + 2, payloadLen, true); + } + idx += orderLen; + break; + } + case GocaConstants.G_GRLINE: { // Relative Line Absolute Start (0xE1) + if (payloadLen >= 4) { + processRelativeLine(inputData, idx + 2, payloadLen, false); + } + idx += orderLen; + break; + } + case GocaConstants.G_GCRLIN: { // Relative Line Current Position (0xA1) + if (payloadLen >= 2) { + processRelativeLine(inputData, idx + 2, payloadLen, true); + } + idx += orderLen; + break; + } + case GocaConstants.G_GFARC: { // Full Arc Absolute (0xC7) + if (payloadLen >= 4) { + processArc(inputData, idx + 2, payloadLen, false, true); + } + idx += orderLen; + break; + } + case GocaConstants.G_GCFARC: { // Full Arc Current Position (0x87) + if (payloadLen >= 2) { + processArc(inputData, idx + 2, payloadLen, true, true); + } + idx += orderLen; + break; + } + case GocaConstants.G_GARC: { // Partial Arc Absolute (0xC6) + if (payloadLen >= 8) { + processArc(inputData, idx + 2, payloadLen, false, false); + } + idx += orderLen; + break; + } + case GocaConstants.G_GCARC: { // Partial Arc Current Position (0x86) + if (payloadLen >= 4) { + processArc(inputData, idx + 2, payloadLen, true, false); + } + idx += orderLen; + break; + } + case GocaConstants.G_GFLT: { // Fillet Absolute (0xC5) + if (payloadLen >= 4) { + processFillet(inputData, idx + 2, payloadLen, false); + } + idx += orderLen; + break; + } + case GocaConstants.G_GCFLT: { // Fillet Current Position (0x85) + if (payloadLen >= 4) { + processFillet(inputData, idx + 2, payloadLen, true); + } + idx += orderLen; + break; + } + case GocaConstants.G_GMRK: { // Marker Absolute (0xC2) + if (payloadLen >= 4) { + processMarker(inputData, idx + 2, payloadLen, false); + } + idx += orderLen; + break; + } + case GocaConstants.G_GCMRK: { // Marker Current Position (0x82) + if (payloadLen >= 4) { + processMarker(inputData, idx + 2, payloadLen, true); + } + idx += orderLen; + break; + } + case GocaConstants.G_GCHST: { // Character String Absolute (0xC3) + if (payloadLen >= 4) { + processText(inputData, idx + 2, payloadLen, false); + } + idx += orderLen; + break; + } + case GocaConstants.G_GCCHST: { // Character String Current Position (0x83) + if (payloadLen >= 0) { + processText(inputData, idx + 2, payloadLen, true); + } + idx += orderLen; + break; + } + case GocaConstants.G_GBIMG: { // Begin Image (0xD1) + if (idx + 9 < end) { + int x = readCoord(inputData, idx + 2); + int y = readCoord(inputData, idx + 4); + int w = readCoord(inputData, idx + 6); + int h = readCoord(inputData, idx + 8); + beginImage(x, y, w, h); + } + idx += orderLen; + break; + } + case GocaConstants.G_GIMD: { // Image Data (0x92) + if (inImage) { + for (int k = 0; k < payloadLen; k++) { + imgBuffer.add(inputData[idx + 2 + k]); + } + } + idx += orderLen; + break; + } + default: { + idx += orderLen; + break; + } + } + } + } + + /** + * Decodes a stream of Object Control Procedure Orders (subtype 0x11). + */ + public synchronized void processProcedureOrders(byte[] data, int offset, int length) { + if (data == null || length <= 0 || offset < 0 || offset + length > data.length) { + return; + } + + int idx = offset; + int end = offset + length; + + while (idx < end) { + int order = data[idx] & 0xFF; + + switch (order) { + case GocaConstants.P_NOP1: + case GocaConstants.P_ATTCUR: + case GocaConstants.P_DETCUR: + case GocaConstants.P_STOPDR: { + idx++; + break; + } + case GocaConstants.P_ERASE: { // 0x0A: Erase Graphics Presentation Space + plane.clear(); + resetDefaults(); + idx++; + break; + } + case GocaConstants.P_BEGPROC: { // 0x30: Begin Procedure (length 12) + int len = 12; + if (idx + 1 < end && data[idx + 1] != 0) { + len = (data[idx + 1] & 0xFF) + 2; + } + idx += len; + break; + } + case GocaConstants.P_SCUDEF: { // 0x21: Set Current Defaults + if (idx + 1 < end) { + int len = data[idx + 1] & 0xFF; + if (idx + 2 + len <= end) { + decodeStream(data, idx + 2, len); + } + idx += 2 + len; + } else { + idx++; + } + break; + } + case GocaConstants.P_COMT: + case GocaConstants.P_SETCUR: { + if (idx + 1 < end) { + int len = data[idx + 1] & 0xFF; + idx += 2 + len; + } else { + idx++; + } + break; + } + default: { + if (idx + 1 < end) { + int len = data[idx + 1] & 0xFF; + idx += 2 + len; + } else { + idx++; + } + break; + } + } + } + } + + private void beginArea(boolean drawBoundary) { + this.inArea = true; + this.areaDrawBoundary = drawBoundary; + this.fillColor = this.curColor; + this.areaPointsX.clear(); + this.areaPointsY.clear(); + } + + private void endArea() { + if (!inArea || areaPointsX.size() < 3) { + inArea = false; + areaPointsX.clear(); + areaPointsY.clear(); + return; + } + int n = areaPointsX.size(); + int[] px = new int[n]; + int[] py = new int[n]; + for (int i = 0; i < n; i++) { + px[i] = plane.mapX(areaPointsX.get(i)); + py[i] = plane.mapY(areaPointsY.get(i)); + } + + plane.fillArea(px, py, n, fillColor, pattern, areaDrawBoundary, curColor, lineType, lineWidth); + inArea = false; + areaPointsX.clear(); + areaPointsY.clear(); + } + + private void addAreaPoint(int x, int y) { + if (inArea) { + areaPointsX.add(x); + areaPointsY.add(y); + } + } + + private void beginImage(int x, int y, int w, int h) { + this.inImage = true; + this.imgX = x; + this.imgY = y; + this.imgWidth = w; + this.imgHeight = h; + this.imgBuffer.clear(); + } + + private void endImage() { + if (!inImage || imgWidth <= 0 || imgHeight <= 0 || imgBuffer.isEmpty()) { + inImage = false; + return; + } + byte[] bytes = new byte[imgBuffer.size()]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = imgBuffer.get(i); + } + + int px = plane.mapX(imgX); + int py = plane.mapY(imgY); + plane.drawImage(px, py, imgWidth, imgHeight, bytes, curColor); + inImage = false; + imgBuffer.clear(); + } + + private void processLine(byte[] data, int off, int len, boolean fromCurPos) { + int pos = off; + int end = off + len; + + int startX = curX; + int startY = curY; + + if (!fromCurPos && pos + 4 <= end) { + startX = readCoord(data, pos); + startY = readCoord(data, pos + 2); + pos += 4; + } + + if (inArea) { + addAreaPoint(startX, startY); + } + + while (pos + 4 <= end) { + int nextX = readCoord(data, pos); + int nextY = readCoord(data, pos + 2); + pos += 4; + + if (inArea) { + addAreaPoint(nextX, nextY); + } else { + plane.drawLine(plane.mapX(startX), plane.mapY(startY), + plane.mapX(nextX), plane.mapY(nextY), + curColor, lineType, lineWidth); + } + + startX = nextX; + startY = nextY; + } + + curX = startX; + curY = startY; + } + + private void processRelativeLine(byte[] data, int off, int len, boolean fromCurPos) { + int pos = off; + int end = off + len; + + int startX = curX; + int startY = curY; + + if (!fromCurPos && pos + 4 <= end) { + startX = readCoord(data, pos); + startY = readCoord(data, pos + 2); + pos += 4; + } + + if (inArea) { + addAreaPoint(startX, startY); + } + + while (pos + 2 <= end) { + int dx = (byte) data[pos]; + int dy = (byte) data[pos + 1]; + pos += 2; + + int nextX = startX + dx; + int nextY = startY + dy; + + if (inArea) { + addAreaPoint(nextX, nextY); + } else { + plane.drawLine(plane.mapX(startX), plane.mapY(startY), + plane.mapX(nextX), plane.mapY(nextY), + curColor, lineType, lineWidth); + } + + startX = nextX; + startY = nextY; + } + + curX = startX; + curY = startY; + } + + private void processArc(byte[] data, int off, int len, boolean fromCurPos, boolean isFull) { + int pos = off; + int startX = curX; + int startY = curY; + + if (!fromCurPos && pos + 4 <= off + len) { + startX = readCoord(data, pos); + startY = readCoord(data, pos + 2); + pos += 4; + } + + double multiplier = 1.0; + if (pos + 2 <= off + len) { + multiplier = (data[pos] & 0xFF) + ((data[pos + 1] & 0xFF) / 255.0); + if (multiplier == 0.0) multiplier = 1.0; + } + + int dxP = Math.abs(arcParamP - arcParamR); + int dyQ = Math.abs(arcParamQ - arcParamS); + if (dxP == 0) dxP = 10; + if (dyQ == 0) dyQ = 10; + + int rxVirtual = (int) (dxP * multiplier); + int ryVirtual = (int) (dyQ * multiplier); + + int rx = Math.abs(plane.mapX(rxVirtual) - plane.mapX(0)); + int ry = Math.abs(plane.mapY(ryVirtual) - plane.mapY(0)); + if (rx <= 0) rx = 10; + if (ry <= 0) ry = 10; + + plane.drawArc(plane.mapX(startX), plane.mapY(startY), rx, ry, 0.0, 360.0, + curColor, lineType, lineWidth, isFull); + + curX = startX; + curY = startY; + } + + private void processFillet(byte[] data, int off, int len, boolean fromCurPos) { + int pos = off; + int end = off + len; + + List ptsX = new ArrayList<>(); + List ptsY = new ArrayList<>(); + + if (fromCurPos) { + ptsX.add(plane.mapX(curX)); + ptsY.add(plane.mapY(curY)); + } + + while (pos + 4 <= end) { + int x = readCoord(data, pos); + int y = readCoord(data, pos + 2); + ptsX.add(plane.mapX(x)); + ptsY.add(plane.mapY(y)); + curX = x; + curY = y; + pos += 4; + } + + if (ptsX.size() >= 2) { + int n = ptsX.size(); + int[] px = new int[n]; + int[] py = new int[n]; + for (int i = 0; i < n; i++) { + px[i] = ptsX.get(i); + py[i] = ptsY.get(i); + } + plane.drawFillet(px, py, n, curColor, lineType, lineWidth); + } + } + + private void processMarker(byte[] data, int off, int len, boolean fromCurPos) { + int pos = off; + int end = off + len; + + if (fromCurPos) { + plane.drawMarker(plane.mapX(curX), plane.mapY(curY), markerType, markerSize, markerColor); + } + + while (pos + 4 <= end) { + int x = readCoord(data, pos); + int y = readCoord(data, pos + 2); + plane.drawMarker(plane.mapX(x), plane.mapY(y), markerType, markerSize, markerColor); + curX = x; + curY = y; + pos += 4; + } + } + + private void processText(byte[] data, int off, int len, boolean fromCurPos) { + int pos = off; + int end = off + len; + + int startX = curX; + int startY = curY; + + if (!fromCurPos && pos + 4 <= end) { + startX = readCoord(data, pos); + startY = readCoord(data, pos + 2); + pos += 4; + } + + int textLen = end - pos; + if (textLen <= 0) return; + + int cw = charWidth > 0 ? (int) Math.round((double) charWidth * plane.getCanvasWidth() / (plane.getScreenCols() * 9.0)) : 12; + int ch = charHeight > 0 ? (int) Math.round((double) charHeight * plane.getCanvasHeight() / (plane.getScreenRows() * 16.0)) : 20; + + if (charSet != 0 && programSymbolManager != null) { + for (int i = 0; i < textLen; i++) { + int code = data[pos + i] & 0xFF; + int px = plane.mapX(startX); + int py = plane.mapY(startY) - ch; + boolean drawn = programSymbolManager.drawSymbol(plane.getGraphics(), charSet, code, px, py, cw, ch, curColor, null); + if (!drawn) { + char c = EbcdicTranslator.ebcdicToAscii(data[pos + i]); + plane.drawVectorText(px, py, String.valueOf(c), curColor, cw, ch, charDir, charAngle); + } + startX += (charWidth > 0 ? charWidth : 9); + } + curX = startX; + curY = startY; + return; + } + + char[] chars = new char[textLen]; + for (int i = 0; i < textLen; i++) { + chars[i] = EbcdicTranslator.ebcdicToAscii(data[pos + i]); + } + String text = new String(chars); + + plane.drawVectorText(plane.mapX(startX), plane.mapY(startY) - ch, text, + curColor, cw, ch, charDir, charAngle); + + curX = startX + (textLen * (charWidth > 0 ? charWidth : 9)); + curY = startY; + } + + private int readCoord(byte[] data, int off) { + return (short) (((data[off] & 0xFF) << 8) | (data[off + 1] & 0xFF)); + } + + private Color getColor(int colorIndex) { + if (colorIndex >= 0 && colorIndex < GocaConstants.GOCA_COLORS.length) { + return GocaConstants.GOCA_COLORS[colorIndex]; + } + return Color.GREEN; + } +} diff --git a/lib3270j/src/main/java/org/lib3270j/graphics/GraphicsMode.java b/lib3270j/src/main/java/org/lib3270j/graphics/GraphicsMode.java new file mode 100644 index 0000000..7744811 --- /dev/null +++ b/lib3270j/src/main/java/org/lib3270j/graphics/GraphicsMode.java @@ -0,0 +1,70 @@ +package org.lib3270j.graphics; + +/** + * Graphics modes supported by j3270 / lib3270j. + */ +public enum GraphicsMode { + /** Text only (no graphics query replies, classic 3279-4 terminal behavior). */ + NONE("None (Text Only)"), + + /** Programmed Symbols only (custom character matrices and APL, single & triple plane). */ + PROGRAMMED_SYMBOLS("Programmed Symbols Only"), + + /** Vector graphics only (GOCA / 3179G drawing orders). */ + VECTOR_GRAPHICS("Vector Graphics Only"), + + /** Full graphics support (both Programmed Symbols and Vector Graphics). */ + BOTH("Both (Programmed Symbols & Vector Graphics)"); + + private final String description; + + GraphicsMode(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + public boolean isProgrammedSymbolsEnabled() { + return this == PROGRAMMED_SYMBOLS || this == BOTH; + } + + public boolean isVectorGraphicsEnabled() { + return this == VECTOR_GRAPHICS || this == BOTH; + } + + public static GraphicsMode fromString(String str) { + if (str == null || str.trim().isEmpty()) { + return NONE; + } + String s = str.trim().toUpperCase(); + switch (s) { + case "BOTH": + case "ALL": + case "FULL": + case "ON": + case "TRUE": + return BOTH; + case "PS": + case "PROGRAMMED_SYMBOLS": + case "PROGRAMMEDSYMBOLS": + case "SYMBOLS": + case "APL": + return PROGRAMMED_SYMBOLS; + case "VECTOR": + case "VECTOR_GRAPHICS": + case "VECTORGRAPHICS": + case "GOCA": + case "GDDM": + return VECTOR_GRAPHICS; + case "NONE": + case "OFF": + case "FALSE": + case "DISABLED": + case "TEXT": + default: + return NONE; + } + } +} diff --git a/lib3270j/src/main/java/org/lib3270j/graphics/GraphicsPlane.java b/lib3270j/src/main/java/org/lib3270j/graphics/GraphicsPlane.java new file mode 100644 index 0000000..b752abd --- /dev/null +++ b/lib3270j/src/main/java/org/lib3270j/graphics/GraphicsPlane.java @@ -0,0 +1,400 @@ +package org.lib3270j.graphics; + +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Polygon; +import java.awt.RenderingHints; +import java.awt.Stroke; +import java.awt.geom.AffineTransform; +import java.awt.geom.Arc2D; +import java.awt.geom.GeneralPath; +import java.awt.image.BufferedImage; +import java.util.logging.Logger; + +/** + * Offscreen rendering surface for GOCA vector graphics. + * Maintained as an ARGB BufferedImage that overlays the 3270 character cell matrix. + */ +public class GraphicsPlane { + + private static final Logger logger = Logger.getLogger(GraphicsPlane.class.getName()); + + // 17 Standard GOCA 8x8 Fill Patterns + public static final byte[][] PATTERN_DATA = new byte[][]{ + {-1, -1, -1, -1, -1, -1, -1, -1}, // 0: Solid (all 1s) + {-1, -1, -1, -18, -1, -1, -1, -18}, // 1: D1 + {-1, -69, -1, -18, -1, -69, -1, -18}, // 2: D2 + {119, -35, -69, -18, 119, -35, -69, -18}, // 3: D3 + {-69, -52, 51, -18, -69, -52, 51, -18}, // 4: D4 + {85, -86, 85, -86, 85, -86, 85, -86}, // 5: D5 (50% checker) + {68, 51, -52, 17, 68, 51, -52, 17}, // 6: D6 + {-120, 34, 68, 17, -120, 34, 68, 17}, // 7: D7 + {0, 68, 0, 17, 0, 68, 0, 17}, // 8: D8 (sparse dots) + {-128, -128, -128, -128, -128, -128, -128, -128}, // 9: Vertical line + {-1, 0, 0, 0, 0, 0, 0, 0}, // 10: Horizontal line + {1, 2, 4, 8, 16, 32, 64, -128}, // 11: Diagonal bottom-left to top-right + {3, 12, 48, -64, 3, 12, 48, -64}, // 12: Diagonal BL-TR dense + {-128, 64, 32, 16, 8, 4, 2, 1}, // 13: Diagonal top-left to bottom-right + {-64, 48, 12, 3, -64, 48, 12, 3}, // 14: Diagonal TL-BR dense + {0, 0, 0, 0, 0, 0, 0, 0}, // 15: Empty (transparent) + {-1, -1, -1, -1, -1, -1, -1, -1} // 16: Solid + }; + + private int canvasWidth = 800; + private int canvasHeight = 600; + private BufferedImage image; + private Graphics2D g2d; + private boolean hasContent = false; + + public GraphicsPlane(int width, int height) { + resize(width, height); + } + + public synchronized void resize(int width, int height) { + int w = Math.max(1, width); + int h = Math.max(1, height); + if (w == canvasWidth && h == canvasHeight && image != null) { + return; + } + this.canvasWidth = w; + this.canvasHeight = h; + + BufferedImage newImage = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB); + Graphics2D newG2d = newImage.createGraphics(); + newG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + newG2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); + + if (image != null && hasContent) { + newG2d.drawImage(image, 0, 0, w, h, null); + } + + if (this.g2d != null) { + this.g2d.dispose(); + } + + this.image = newImage; + this.g2d = newG2d; + } + + public synchronized void clear() { + if (image != null && g2d != null) { + BufferedImage newImage = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_ARGB); + Graphics2D newG2d = newImage.createGraphics(); + newG2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + if (this.g2d != null) { + this.g2d.dispose(); + } + this.image = newImage; + this.g2d = newG2d; + } + hasContent = false; + } + + public synchronized boolean hasContent() { + return hasContent; + } + + public synchronized BufferedImage getImage() { + return image; + } + + public int getCanvasWidth() { + return canvasWidth; + } + + public int getCanvasHeight() { + return canvasHeight; + } + + public synchronized Graphics2D getGraphics() { + return g2d; + } + + private int screenCols = 80; + private int screenRows = 24; + + public void setScreenDimensions(int cols, int rows) { + this.screenCols = cols > 0 ? cols : 80; + this.screenRows = rows > 0 ? rows : 24; + } + + public int getScreenCols() { + return screenCols; + } + + public int getScreenRows() { + return screenRows; + } + + /** + * Maps a 3179G / GOCA signed coordinate (centered at screen midpoint) to canvas pixel X. + */ + public int mapX(int gocaX) { + int nominalWidth = screenCols * 9; + int xMax = (nominalWidth - 1) / 2 + ((nominalWidth - 1) % 2 != 0 ? 1 : 0); + int nx = gocaX + xMax; + return (int) Math.round((double) nx * canvasWidth / nominalWidth); + } + + /** + * Maps a 3179G / GOCA signed coordinate (centered at screen midpoint, bottom-up) to canvas pixel Y (top-down). + */ + public int mapY(int gocaY) { + int nominalHeight = screenRows * 16; + int yMax = (nominalHeight - 1) / 2; + int ny = yMax - gocaY; + return (int) Math.round((double) ny * canvasHeight / nominalHeight); + } + + /** + * Draws an absolute or relative line. + */ + public synchronized void drawLine(int x1, int y1, int x2, int y2, Color color, int lineType, int lineWidth) { + if (g2d == null) return; + g2d.setColor(color != null ? color : Color.WHITE); + g2d.setStroke(createStroke(lineType, lineWidth)); + g2d.drawLine(x1, y1, x2, y2); + hasContent = true; + } + + /** + * Draws a full or partial arc / ellipse. + */ + public synchronized void drawArc(int cx, int cy, int rx, int ry, double startAngleDeg, double sweepAngleDeg, + Color color, int lineType, int lineWidth, boolean isFull) { + if (g2d == null) return; + g2d.setColor(color != null ? color : Color.WHITE); + g2d.setStroke(createStroke(lineType, lineWidth)); + + double x = cx - rx; + double y = cy - ry; + double w = rx * 2.0; + double h = ry * 2.0; + + if (isFull) { + g2d.drawOval((int) Math.round(x), (int) Math.round(y), (int) Math.round(w), (int) Math.round(h)); + } else { + Arc2D.Double arc = new Arc2D.Double(x, y, w, h, startAngleDeg, sweepAngleDeg, Arc2D.OPEN); + g2d.draw(arc); + } + hasContent = true; + } + + /** + * Draws a Fillet (spline / curve approximation across control points). + */ + public synchronized void drawFillet(int[] px, int[] py, int numPoints, Color color, int lineType, int lineWidth) { + if (g2d == null || numPoints < 2) return; + g2d.setColor(color != null ? color : Color.WHITE); + g2d.setStroke(createStroke(lineType, lineWidth)); + + GeneralPath path = new GeneralPath(); + path.moveTo(px[0], py[0]); + + if (numPoints == 2) { + path.lineTo(px[1], py[1]); + } else { + for (int i = 1; i < numPoints - 1; i++) { + double midX = (px[i] + px[i + 1]) / 2.0; + double midY = (py[i] + py[i + 1]) / 2.0; + path.quadTo(px[i], py[i], midX, midY); + } + path.lineTo(px[numPoints - 1], py[numPoints - 1]); + } + + g2d.draw(path); + hasContent = true; + } + + /** + * Fills a closed polygon area with a solid color or hatching pattern. + */ + public synchronized void fillArea(int[] px, int[] py, int numPoints, Color fillColor, int pattern, + boolean drawBoundary, Color boundaryColor, int lineType, int lineWidth) { + if (g2d == null || numPoints < 3) return; + + Polygon poly = new Polygon(px, py, numPoints); + + if (pattern == GocaConstants.PT_SOLID || pattern == GocaConstants.PT_DEFAULT || pattern > 16) { + g2d.setColor(fillColor != null ? fillColor : Color.WHITE); + g2d.fill(poly); + } else if (pattern != GocaConstants.PT_EMPTY) { + // Fill with pattern texture + BufferedImage patImg = createPatternTexture(pattern, fillColor != null ? fillColor : Color.WHITE); + java.awt.TexturePaint tp = new java.awt.TexturePaint(patImg, new java.awt.Rectangle(0, 0, 8, 8)); + g2d.setPaint(tp); + g2d.fill(poly); + } + + if (drawBoundary && boundaryColor != null) { + g2d.setColor(boundaryColor); + g2d.setStroke(createStroke(lineType, lineWidth)); + g2d.draw(poly); + } + hasContent = true; + } + + /** + * Draws a GOCA marker symbol (+, x, diamond, square, star, dot, circle). + */ + public synchronized void drawMarker(int x, int y, int markerType, int size, Color color) { + if (g2d == null) return; + g2d.setColor(color != null ? color : Color.WHITE); + g2d.setStroke(new BasicStroke(1.5f)); + + int s = Math.max(3, size > 0 ? size : 5); + + switch (markerType) { + case GocaConstants.MK_CROSS: // x + g2d.drawLine(x - s, y - s, x + s, y + s); + g2d.drawLine(x - s, y + s, x + s, y - s); + break; + case GocaConstants.MK_PLUS: // + + case GocaConstants.MK_DEFAULT: + g2d.drawLine(x - s, y, x + s, y); + g2d.drawLine(x, y - s, x, y + s); + break; + case GocaConstants.MK_DIAMOND: // <> + g2d.drawPolygon(new int[]{x, x + s, x, x - s}, new int[]{y - s, y, y + s, y}, 4); + break; + case GocaConstants.MK_SQUARE: // [] + g2d.drawRect(x - s, y - s, s * 2, s * 2); + break; + case GocaConstants.MK_6STAR: // 6-point star + g2d.drawLine(x - s, y, x + s, y); + g2d.drawLine(x - s / 2, y - s, x + s / 2, y + s); + g2d.drawLine(x - s / 2, y + s, x + s / 2, y - s); + break; + case GocaConstants.MK_8STAR: // 8-point star + g2d.drawLine(x - s, y, x + s, y); + g2d.drawLine(x, y - s, x, y + s); + g2d.drawLine(x - s, y - s, x + s, y + s); + g2d.drawLine(x - s, y + s, x + s, y - s); + break; + case GocaConstants.MK_SDIAMOND: // solid diamond + g2d.fillPolygon(new int[]{x, x + s, x, x - s}, new int[]{y - s, y, y + s, y}, 4); + break; + case GocaConstants.MK_SSQUARE: // solid square + g2d.fillRect(x - s, y - s, s * 2, s * 2); + break; + case GocaConstants.MK_DOT: // dot + g2d.fillOval(x - 2, y - 2, 4, 4); + break; + case GocaConstants.MK_CIRCLE: // circle + g2d.drawOval(x - s, y - s, s * 2, s * 2); + break; + default: + g2d.drawOval(x - s, y - s, s * 2, s * 2); + break; + } + hasContent = true; + } + + /** + * Draws stroked vector text using IBM Vector Symbol Set (VSS). + */ + public synchronized void drawVectorText(int x, int y, String text, Color color, + int cellWidth, int cellHeight, int dir, double angle) { + if (g2d == null || text == null || text.isEmpty()) return; + g2d.setColor(color != null ? color : Color.WHITE); + g2d.setStroke(new BasicStroke(1.2f)); + + int curX = x; + int curY = y; + int cw = cellWidth > 0 ? cellWidth : 12; + int ch = cellHeight > 0 ? cellHeight : 20; + + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + drawVssChar(curX, curY, c, cw, ch); + + switch (dir) { + case GocaConstants.CD_TB: curY += ch; break; + case GocaConstants.CD_RL: curX -= cw; break; + case GocaConstants.CD_BT: curY -= ch; break; + case GocaConstants.CD_LR: + case GocaConstants.CD_DEFAULT: + default: + curX += cw; + break; + } + } + hasContent = true; + } + + private void drawVssChar(int x, int y, char c, int cw, int ch) { + int idx = (int) c; + if (idx < VectorSymbolData.VSS_SYMBOL_START || idx > VectorSymbolData.VSS_SYMBOL_END) { + // Draw simple bounding box or space + return; + } + // Fallback vector stroke rendering + g2d.drawString(String.valueOf(c), x, y + ch); + } + + /** + * Draws raw image pixel bitmap. + */ + public synchronized void drawImage(int x, int y, int width, int height, byte[] imageData, Color fgColor) { + if (g2d == null || imageData == null || width <= 0 || height <= 0) return; + + BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + int fgRgb = fgColor != null ? fgColor.getRGB() : 0xFFFFFFFF; + + for (int row = 0; row < height; row++) { + for (int col = 0; col < width; col++) { + int bitIndex = row * width + col; + int byteIdx = bitIndex / 8; + if (byteIdx < imageData.length) { + boolean bit = ((imageData[byteIdx] >> (7 - (bitIndex % 8))) & 1) != 0; + if (bit) { + img.setRGB(col, row, fgRgb); + } + } + } + } + + g2d.drawImage(img, x, y, null); + hasContent = true; + } + + private Stroke createStroke(int lineType, int lineWidth) { + float width = (lineWidth == GocaConstants.LW_THICK) ? 2.5f : 1.2f; + + switch (lineType) { + case GocaConstants.LT_DOT: + return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{2.0f, 2.0f}, 0.0f); + case GocaConstants.LT_SHORTDASH: + return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{4.0f, 2.0f}, 0.0f); + case GocaConstants.LT_DASHDOT: + return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{6.0f, 2.0f, 2.0f, 2.0f}, 0.0f); + case GocaConstants.LT_DOUBLEDOT: + return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{2.0f, 2.0f, 2.0f, 4.0f}, 0.0f); + case GocaConstants.LT_LONGDASH: + return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{8.0f, 3.0f}, 0.0f); + case GocaConstants.LT_DASHDOUBLEDOT: + return new BasicStroke(width, BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 10.0f, new float[]{8.0f, 2.0f, 2.0f, 2.0f, 2.0f, 2.0f}, 0.0f); + case GocaConstants.LT_SOLID: + case GocaConstants.LT_DEFAULT: + default: + return new BasicStroke(width); + } + } + + private BufferedImage createPatternTexture(int patternIndex, Color color) { + BufferedImage pat = new BufferedImage(8, 8, BufferedImage.TYPE_INT_ARGB); + byte[] patternRows = (patternIndex >= 0 && patternIndex < PATTERN_DATA.length) ? PATTERN_DATA[patternIndex] : PATTERN_DATA[0]; + int rgb = color.getRGB(); + + for (int r = 0; r < 8; r++) { + int b = patternRows[r] & 0xFF; + for (int c = 0; c < 8; c++) { + if (((b >> (7 - c)) & 1) != 0) { + pat.setRGB(c, r, rgb); + } + } + } + return pat; + } +} diff --git a/lib3270j/src/main/java/org/lib3270j/graphics/ProgramSymbolManager.java b/lib3270j/src/main/java/org/lib3270j/graphics/ProgramSymbolManager.java new file mode 100644 index 0000000..273d8fe --- /dev/null +++ b/lib3270j/src/main/java/org/lib3270j/graphics/ProgramSymbolManager.java @@ -0,0 +1,268 @@ +package org.lib3270j.graphics; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Manages IBM 3270 Programmed Symbols (PS / APL) character sets. + * Handles the Load Programmed Symbols (LOADPS structured field 0x0F). + */ +public class ProgramSymbolManager { + + private static final Logger logger = Logger.getLogger(ProgramSymbolManager.class.getName()); + + public static final int NUMBER_SYMBOL_SETS = 10; + public static final int NUMBER_SINGLE_PLANE_PS_SETS = 2; // RWS 2..3 (Sets 0..1) + public static final int NUMBER_TRIPLE_PLANE_PS_SETS = 4; // RWS 4..7 (Sets 2..5) + public static final int NUMBER_GRAPHICS_SYMBOL_SETS = 4; // RWS 8..11 (Sets 6..9) + + private final ProgramSymbolSet[] sets = new ProgramSymbolSet[NUMBER_SYMBOL_SETS]; + + public ProgramSymbolManager() { + for (int i = 0; i < NUMBER_SYMBOL_SETS; i++) { + boolean isTriple = (i >= NUMBER_SINGLE_PLANE_PS_SETS && i < NUMBER_SINGLE_PLANE_PS_SETS + NUMBER_TRIPLE_PLANE_PS_SETS); + sets[i] = new ProgramSymbolSet(isTriple); + } + } + + /** + * Resets all symbol sets. + */ + public synchronized void clearAll() { + for (ProgramSymbolSet set : sets) { + set.clear(); + set.setLcid(0); + } + } + + /** + * Retrieves the symbol set corresponding to a given LCID (0x40 - 0xFE). + */ + public synchronized ProgramSymbolSet getSymbolSet(int lcid) { + if (lcid <= 0) { + return null; + } + for (ProgramSymbolSet set : sets) { + if (set.getLcid() == lcid) { + return set; + } + } + return null; + } + + /** + * Retrieves a specific symbol glyph by LCID and character code point (0x40 - 0xFE). + */ + public synchronized ProgramSymbolSet.SymbolSlot getSymbol(int lcid, int codePoint) { + ProgramSymbolSet set = getSymbolSet(lcid); + if (set == null) { + return null; + } + int index = (codePoint >= 0x40) ? (codePoint - 0x40) : codePoint; + return set.getSlot(index); + } + + /** + * Draws a programmed symbol character cell if defined. + * Returns true if symbol was drawn, false if not found. + */ + public synchronized boolean drawSymbol(Graphics2D g2d, int lcid, int codePoint, + int x, int y, int cellWidth, int cellHeight, + Color fgColor, Color bgColor) { + ProgramSymbolSet.SymbolSlot slot = getSymbol(lcid, codePoint); + if (slot != null) { + slot.draw(g2d, x, y, cellWidth, cellHeight, fgColor, bgColor); + return true; + } + return false; + } + + /** + * Processes a Load Programmed Symbols (LOADPS structured field 0x0F) payload. + */ + public synchronized void loadps(byte[] data) { + if (data == null || data.length < 4) { + logger.warning("LOADPS: Payload too short (" + (data == null ? 0 : data.length) + " bytes)"); + return; + } + + int flags = data[0] & 0xFF; + int loadFormat = flags & 0x1F; + boolean clearAll = (flags & 0x40) != 0; + boolean hasExtHeader = (flags & 0x80) != 0; + + int lcid = data[1] & 0xFF; + int startCodePoint = data[2] & 0xFF; + int rws = data[3] & 0xFF; + + int setIndex; + switch (rws) { + case 2: setIndex = 0; break; + case 3: setIndex = 1; break; + case 4: setIndex = 2; break; + case 5: setIndex = 3; break; + case 6: setIndex = 4; break; + case 7: setIndex = 5; break; + case 8: setIndex = 6; break; + case 9: setIndex = 7; break; + case 10: setIndex = 8; break; + case 11: setIndex = 9; break; + default: + logger.warning("LOADPS: Invalid RWS slot 0x" + Integer.toHexString(rws)); + return; + } + + ProgramSymbolSet set = sets[setIndex]; + boolean isTriplePlane = (rws >= 4 && rws <= 7); + + int extHeaderLen = 0; + int cellWidth = 9; + int cellHeight = 16; + int colorPlane = 0; // 0 = all planes, 1 = Red, 2 = Green, 4 = Blue + + if (hasExtHeader && data.length > 4) { + extHeaderLen = data[4] & 0xFF; + if (extHeaderLen > 3 && data.length > 6) { + int lw = data[6] & 0xFF; + if (lw > 0) cellWidth = lw; + } + if (extHeaderLen > 4 && data.length > 7) { + int lh = data[7] & 0xFF; + if (lh > 0) cellHeight = lh; + } + if (extHeaderLen >= 6 && data.length > 9) { + colorPlane = data[9] & 0xFF; + } + } + + if (clearAll) { + set.clear(); + } + set.setLcid(lcid); + + int offset = 4 + (hasExtHeader ? extHeaderLen : 0); + int remaining = data.length - offset; + int codeIndex = (startCodePoint >= 0x40) ? (startCodePoint - 0x40) : startCodePoint; + + int bytesPerSymbol; + if (loadFormat == 1) { + bytesPerSymbol = 18; // 9x16 cell in standard transmission format + } else { + bytesPerSymbol = (cellWidth * cellHeight + 7) / 8; + } + + if (bytesPerSymbol <= 0) { + bytesPerSymbol = 18; + } + + while (remaining >= bytesPerSymbol && codeIndex < ProgramSymbolSet.NUM_SLOTS) { + byte[] pixelData; + ProgramSymbolSet.SymbolSlot existing = set.getSlot(codeIndex); + if (existing != null && existing.getWidth() == cellWidth && existing.getHeight() == cellHeight) { + pixelData = existing.getPixelData(); + } else { + pixelData = new byte[cellWidth * cellHeight]; + } + + if (loadFormat == 1) { + unpackFormat1(data, offset, pixelData, cellWidth, cellHeight, isTriplePlane, colorPlane); + } else { + unpackFormat3(data, offset, pixelData, cellWidth, cellHeight, isTriplePlane, colorPlane); + } + + set.setSlot(codeIndex, new ProgramSymbolSet.SymbolSlot(cellWidth, cellHeight, pixelData, isTriplePlane)); + + codeIndex++; + offset += bytesPerSymbol; + remaining -= bytesPerSymbol; + } + + logger.info(String.format("LOADPS: Loaded PS Set LCID=0x%02X (RWS=%d, %s, %dx%d, %d glyphs)", + lcid, rws, isTriplePlane ? "Triple-Plane" : "Single-Plane", + cellWidth, cellHeight, codeIndex - ((startCodePoint >= 0x40) ? (startCodePoint - 0x40) : startCodePoint))); + } + + /** + * Unpacks Format 1 (9x16) symbol slice bit-pattern. + */ + private void unpackFormat1(byte[] src, int srcOff, byte[] dst, int width, int height, + boolean isTriplePlane, int colorPlane) { + // Format 1 transmits 18 bytes: + // Byte 0-1: contains column 0 for each of the 16 rows + // Bytes 2-17: contains columns 1-8 for each of the 16 rows + int planeMask = (colorPlane != 0) ? colorPlane : (isTriplePlane ? 7 : 1); + + for (int row = 0; row < 16 && row < height; row++) { + // Column 0 bit from byte 0 or byte 1 + int b0 = (row < 8) ? (src[srcOff] & 0xFF) : (src[srcOff + 1] & 0xFF); + int bitShift0 = 7 - (row % 8); + boolean bit0 = ((b0 >> bitShift0) & 1) != 0; + + int dstIdx0 = row * width; + if (dstIdx0 < dst.length) { + if (!isTriplePlane) { + dst[dstIdx0] = bit0 ? (byte) 1 : 0; + } else if (colorPlane == 0) { + dst[dstIdx0] = bit0 ? (byte) 7 : 0; + } else { + if (bit0) { + dst[dstIdx0] |= (byte) colorPlane; + } else { + dst[dstIdx0] &= (byte) ~colorPlane; + } + } + } + + // Columns 1..8 from bytes 2..17 + if (srcOff + 2 + row < src.length) { + int rowByte = src[srcOff + 2 + row] & 0xFF; + for (int col = 1; col < 9 && col < width; col++) { + int dstIdx = row * width + col; + if (dstIdx < dst.length) { + boolean bit = ((rowByte >> (8 - col)) & 1) != 0; + if (!isTriplePlane) { + dst[dstIdx] = bit ? (byte) 1 : 0; + } else if (colorPlane == 0) { + dst[dstIdx] = bit ? (byte) 7 : 0; + } else { + if (bit) { + dst[dstIdx] |= (byte) colorPlane; + } else { + dst[dstIdx] &= (byte) ~colorPlane; + } + } + } + } + } + } + } + + /** + * Unpacks Format 3 variable dimension bit-pattern. + */ + private void unpackFormat3(byte[] src, int srcOff, byte[] dst, int width, int height, + boolean isTriplePlane, int colorPlane) { + int totalPixels = width * height; + for (int i = 0; i < totalPixels; i++) { + int byteIndex = srcOff + (i / 8); + if (byteIndex >= src.length) break; + int bitIndex = 7 - (i % 8); + boolean bit = ((src[byteIndex] >> bitIndex) & 1) != 0; + + if (!isTriplePlane) { + dst[i] = bit ? (byte) 1 : 0; + } else if (colorPlane == 0) { + dst[i] = bit ? (byte) 7 : 0; + } else { + if (bit) { + dst[i] |= (byte) colorPlane; + } else { + dst[i] &= (byte) ~colorPlane; + } + } + } + } +} diff --git a/lib3270j/src/main/java/org/lib3270j/graphics/ProgramSymbolSet.java b/lib3270j/src/main/java/org/lib3270j/graphics/ProgramSymbolSet.java new file mode 100644 index 0000000..2e4184e --- /dev/null +++ b/lib3270j/src/main/java/org/lib3270j/graphics/ProgramSymbolSet.java @@ -0,0 +1,148 @@ +package org.lib3270j.graphics; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Image; +import java.awt.RenderingHints; +import java.awt.image.BufferedImage; + +/** + * Represents a set of up to 191 custom bitmapped symbols (LCID 0x40 - 0xFE). + * Supports both Single-Plane (monochrome) and Triple-Plane (7-color RGB composite). + */ +public class ProgramSymbolSet { + + public static final int NUM_SLOTS = 191; // Code points 0x40 - 0xFE (0..190) + + private int lcid = 0; + private final boolean isTriplePlane; + private final SymbolSlot[] slots = new SymbolSlot[NUM_SLOTS]; + + public ProgramSymbolSet(boolean isTriplePlane) { + this.isTriplePlane = isTriplePlane; + } + + public int getLcid() { + return lcid; + } + + public void setLcid(int lcid) { + this.lcid = lcid; + } + + public boolean isTriplePlane() { + return isTriplePlane; + } + + public void clear() { + for (int i = 0; i < NUM_SLOTS; i++) { + slots[i] = null; + } + } + + public void clearSlot(int index) { + if (index >= 0 && index < NUM_SLOTS) { + slots[index] = null; + } + } + + public void setSlot(int index, SymbolSlot slot) { + if (index >= 0 && index < NUM_SLOTS) { + slots[index] = slot; + } + } + + public SymbolSlot getSlot(int index) { + if (index >= 0 && index < NUM_SLOTS) { + return slots[index]; + } + return null; + } + + /** + * Represents a single custom symbol bitmap. + */ + public static class SymbolSlot { + private final int width; + private final int height; + private final byte[] pixelData; // 1 byte per pixel: 0 = background, 1..7 = color index (or 1 for monochrome) + private final boolean isTriplePlane; + private BufferedImage cachedImage; + private int cachedFgRgb = -1; + private int cachedBgRgb = -1; + + public SymbolSlot(int width, int height, byte[] pixelData, boolean isTriplePlane) { + this.width = width > 0 ? width : 9; + this.height = height > 0 ? height : 16; + this.pixelData = pixelData; + this.isTriplePlane = isTriplePlane; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + public byte[] getPixelData() { + return pixelData; + } + + public boolean isTriplePlane() { + return isTriplePlane; + } + + /** + * Renders this symbol to a BufferedImage. + */ + public synchronized BufferedImage getImage(Color fgColor, Color bgColor) { + int fgRgb = fgColor != null ? fgColor.getRGB() : 0xFFFFFFFF; + int bgRgb = bgColor != null ? bgColor.getRGB() : 0x00000000; + + if (cachedImage != null && cachedFgRgb == fgRgb && cachedBgRgb == bgRgb) { + return cachedImage; + } + + BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + int[] rgbArray = new int[width * height]; + + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int idx = y * width + x; + int val = (idx < pixelData.length) ? (pixelData[idx] & 0xFF) : 0; + + if (val == 0) { + rgbArray[idx] = bgRgb; + } else if (!isTriplePlane) { + rgbArray[idx] = fgRgb; + } else { + // Triple-Plane RGB composite: + // val is bitmask: bit 0 (0x01) = Red, bit 1 (0x02) = Green, bit 2 (0x04) = Blue + int r = (val & 0x01) != 0 ? 255 : 0; + int g = (val & 0x02) != 0 ? 255 : 0; + int b = (val & 0x04) != 0 ? 255 : 0; + rgbArray[idx] = (0xFF << 24) | (r << 16) | (g << 8) | b; + } + } + } + + img.setRGB(0, 0, width, height, rgbArray, 0, width); + this.cachedImage = img; + this.cachedFgRgb = fgRgb; + this.cachedBgRgb = bgRgb; + return img; + } + + /** + * Draws the symbol directly onto a Graphics2D surface with aspect scaling. + */ + public void draw(Graphics2D g2d, int x, int y, int cellWidth, int cellHeight, Color fgColor, Color bgColor) { + BufferedImage img = getImage(fgColor, bgColor); + if (img != null) { + g2d.drawImage(img, x, y, cellWidth, cellHeight, null); + } + } + } +} diff --git a/lib3270j/src/main/java/org/lib3270j/graphics/VectorSymbolData.java b/lib3270j/src/main/java/org/lib3270j/graphics/VectorSymbolData.java new file mode 100644 index 0000000..6e21405 --- /dev/null +++ b/lib3270j/src/main/java/org/lib3270j/graphics/VectorSymbolData.java @@ -0,0 +1,21 @@ +/* + * Decompiled with CFR 0.152. + */ +package org.lib3270j.graphics; + +import org.lib3270j.graphics.GocaConstants; + + +public final class VectorSymbolData { + public static final int VSS_SYMBOL_START = 33; + public static final int VSS_SYMBOL_END = 255; + public static final int VSS_WIDTH = 80; + public static final int VSS_HEIGHT = 110; + public static final char END_DEFAULT = '\u00ff'; + public static final int VSS_NUMBER_SYMBOLS = 223; + static final char[] vss_data = new char[]{'\u00c1', '\u0014', '\u0000', '+', '\u0000', '!', '\u0000', '(', '\u0000', '\u001e', '\u0000', '%', '\u0000', '!', '\u0000', '(', '\u0000', '$', '\u0000', '+', '\u0000', '!', '\u00c1', '\b', '\u0000', '(', '\u0000', '<', '\u0000', '(', '\u0000', 'd', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '\u0014', '\u0000', 'Z', '\u00c1', '\b', '\u0000', '<', '\u0000', 'd', '\u0000', '<', '\u0000', 'Z', '\u00ff', '\u00c1', '\b', '\u0000', '\u001b', '\u0000', 'd', '\u0000', '\u001b', '\u0000', '2', '\u00c1', '\b', '\u0000', '5', '\u0000', 'd', '\u0000', '5', '\u0000', '2', '\u00c1', '\b', '\u0000', '\u0011', '\u0000', 'Z', '\u0000', '?', '\u0000', 'Z', '\u00c1', '\b', '\u0000', '\u0011', '\u0000', '<', '\u0000', '?', '\u0000', '<', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', '\u0014', '\u0000', '(', '\u0000', 'd', '\u00c1', '(', '\u0000', '<', '\u0000', 'Z', '\u0000', '\u001e', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', 'P', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u0019', '\u0000', '<', '\u0000', '7', '\u0000', '<', '\u0000', '<', '\u0000', '7', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '\u001e', '\u00ff', '\u00c1', '\b', '\u0000', '\n', '\u0000', '\u001e', '\u0000', '<', '\u0000', 'd', '\u00c1', '\u0014', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '\u001e', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', 'P', '\u0000', '\n', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', 'd', '\u00c1', '\u0014', '\u0000', '2', '\u0000', '2', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '(', '\u0000', '(', '\u0000', '2', '\u0000', '2', '\u00ff', '\u00c1', '(', '\u0000', '<', '\u0000', '2', '\u0000', '(', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '\n', '\u0000', '(', '\u0000', '\n', '\u0000', '<', '\u0000', '#', '\u0000', 'U', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '\n', '\u0000', 'Z', '\u0000', '\n', '\u0000', 'P', '\u0000', '<', '\u0000', '\u001e', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', 'Z', '\u0000', '(', '\u0000', 'd', '\u00ff', '\u00c1', '\u0010', '\u0000', '<', '\u0000', 'd', '\u0000', '(', '\u0000', 'U', '\u0000', '(', '\u0000', '-', '\u0000', '<', '\u0000', '\u001e', '\u00ff', '\u00c1', '\u0010', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '(', '\u0000', 'U', '\u0000', '(', '\u0000', '-', '\u0000', '\u0014', '\u0000', '\u001e', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '_', '\u0000', '<', '\u0000', '7', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '7', '\u0000', '<', '\u0000', '_', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'K', '\u0000', '<', '\u0000', 'K', '\u00c1', '\b', '\u0000', '(', '\u0000', '2', '\u0000', '(', '\u0000', 'd', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', '2', '\u0000', '(', '\u0000', 'Z', '\u00c1', '\b', '\u0000', '<', '\u0000', 'F', '\u0000', '\u0014', '\u0000', 'F', '\u00ff', '\u00c1', '\u001c', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '\u0014', '\u0000', '\n', '\u0000', '\u0014', '\u0000', '\u0014', '\u0000', '\u001e', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'F', '\u0000', '<', '\u0000', 'F', '\u00ff', '\u00c1', '\u0014', '\u0000', '#', '\u0000', 'F', '\u0000', '-', '\u0000', 'F', '\u0000', '-', '\u0000', '<', '\u0000', '#', '\u0000', '<', '\u0000', '#', '\u0000', 'F', '\u00ff', '\u00c1', '\b', '\u0000', '\n', '\u0000', '(', '\u0000', 'F', '\u0000', 'd', '\u00ff', '\u00c1', '$', '\u0000', '2', '\u0000', 'd', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '\u0014', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '2', '\u0000', '\u001e', '\u0000', '<', '\u0000', '(', '\u0000', '<', '\u0000', 'Z', '\u0000', '2', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', '#', '\u0000', '7', '\u0000', '_', '\u00ff', '\u00c1', '\f', '\u0000', '\u0014', '\u0000', 'P', '\u0000', '(', '\u0000', 'd', '\u0000', '(', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '<', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '\u001e', '\u00ff', '\u00c1', ' ', '\u0000', '<', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '<', '\u0000', 'P', '\u0000', '<', '\u0000', 'Z', '\u0000', '2', '\u0000', 'd', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '\u0014', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', 'P', '\u00ff', '\u00c1', ' ', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '<', '\u0000', 'd', '\u0000', '(', '\u0000', 'F', '\u0000', '<', '\u0000', '<', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u00ff', '\u00c1', '\u0014', '\u0000', '<', '\u0000', '2', '\u0000', '\u0014', '\u0000', '2', '\u0000', '\u0014', '\u0000', '<', '\u0000', '2', '\u0000', 'd', '\u0000', '2', '\u0000', '\u001e', '\u00ff', '\u00c1', '(', '\u0000', '<', '\u0000', 'd', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u00ff', '\u00c1', '(', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '\u0014', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '2', '\u0000', '\u001e', '\u0000', '<', '\u0000', '(', '\u0000', '<', '\u0000', '<', '\u0000', '2', '\u0000', 'F', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '\u0014', '\u0000', '<', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '<', '\u0000', 'd', '\u0000', '<', '\u0000', 'Z', '\u0000', '(', '\u0000', '7', '\u0000', '(', '\u0000', '\u001e', '\u00ff', '\u00c1', '@', '\u0000', '2', '\u0000', 'F', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '\u0014', '\u0000', '<', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '2', '\u0000', '\u001e', '\u0000', '<', '\u0000', '(', '\u0000', '<', '\u0000', '<', '\u0000', '2', '\u0000', 'F', '\u0000', '<', '\u0000', 'P', '\u0000', '<', '\u0000', 'Z', '\u0000', '2', '\u0000', 'd', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '\u0014', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', 'P', '\u0000', '\u001e', '\u0000', 'F', '\u00ff', '\u00c1', '0', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '2', '\u0000', '\u001e', '\u0000', '<', '\u0000', '(', '\u0000', '<', '\u0000', 'Z', '\u0000', '2', '\u0000', 'd', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '\u0014', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', 'F', '\u0000', '\u001e', '\u0000', '<', '\u0000', '2', '\u0000', '<', '\u0000', '<', '\u0000', 'F', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '\u001e', '\u0000', '<', '\u0000', '\u0014', '\u0000', '<', '\u0000', '\u0014', '\u0000', 'F', '\u0000', '\u001e', '\u0000', 'F', '\u00c1', '\u0014', '\u0000', '\u001e', '\u0000', '(', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u00ff', '\u00c1', '\u001c', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '\u0014', '\u0000', '\n', '\u0000', '\u0014', '\u0000', '\u0014', '\u0000', '\u001e', '\u00c1', '\u0014', '\u0000', '\u001e', '\u0000', '<', '\u0000', '\u0014', '\u0000', '<', '\u0000', '\u0014', '\u0000', 'F', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '\u001e', '\u0000', '<', '\u00ff', '\u00c1', '\f', '\u0000', '2', '\u0000', 'd', '\u0000', '\u0014', '\u0000', 'F', '\u0000', '2', '\u0000', '(', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '<', '\u0000', '<', '\u0000', '<', '\u00c1', '\b', '\u0000', '<', '\u0000', 'P', '\u0000', '\u0014', '\u0000', 'P', '\u00ff', '\u00c1', '\f', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '2', '\u0000', 'F', '\u0000', '\u0014', '\u0000', '(', '\u00ff', '\u00c1', '\u0014', '\u0000', '(', '\u0000', '\u001e', '\u0000', '%', '\u0000', '!', '\u0000', '(', '\u0000', '$', '\u0000', '+', '\u0000', '!', '\u0000', '(', '\u0000', '\u001e', '\u00c1', ' ', '\u0000', '(', '\u0000', '2', '\u0000', '(', '\u0000', 'A', '\u0000', '7', '\u0000', 'P', '\u0000', '7', '\u0000', '_', '\u0000', '2', '\u0000', 'd', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '\u0019', '\u0000', '_', '\u0000', '\u0019', '\u0000', 'U', '\u00ff', '\u00c1', '4', '\u0000', '<', '\u0000', '\u0014', '\u0000', '\u0014', '\u0000', '\u0014', '\u0000', '\n', '\u0000', '\u001e', '\u0000', '\n', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '7', '\u0000', 'd', '\u0000', '<', '\u0000', '_', '\u0000', '<', '\u0000', '2', '\u0000', '#', '\u0000', '2', '\u0000', '\u001e', '\u0000', '7', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '#', '\u0000', 'K', '\u0000', '<', '\u0000', 'K', '\u00ff', '\u00c1', '\u0014', '\u0000', '\n', '\u0000', '\u001e', '\u0000', '\n', '\u0000', 'F', '\u0000', '(', '\u0000', 'd', '\u0000', 'F', '\u0000', 'F', '\u0000', 'F', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', 'F', '\u0000', '7', '\u0000', '\n', '\u0000', '7', '\u00ff', '\u00c1', '$', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '7', '\u0000', '\u001e', '\u0000', 'A', '\u0000', '(', '\u0000', 'A', '\u0000', '<', '\u0000', '7', '\u0000', 'F', '\u0000', 'A', '\u0000', 'P', '\u0000', 'A', '\u0000', 'Z', '\u0000', '7', '\u0000', 'd', '\u0000', '\u000f', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', 'd', '\u0000', '\u0019', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', 'F', '\u0000', '7', '\u0000', 'F', '\u00ff', '\u00c1', ' ', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'Z', '\u0000', '\u0019', '\u0000', 'd', '\u0000', '7', '\u0000', 'd', '\u0000', 'A', '\u0000', 'Z', '\u00ff', '\u00c1', '\u0018', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '7', '\u0000', '\u001e', '\u0000', 'A', '\u0000', '(', '\u0000', 'A', '\u0000', 'Z', '\u0000', '7', '\u0000', 'd', '\u0000', '\u000f', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', 'd', '\u0000', '\u0019', '\u0000', '\u001e', '\u00ff', '\u00c1', '\u0010', '\u0000', 'A', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', 'd', '\u0000', 'A', '\u0000', 'd', '\u00c1', '\b', '\u0000', '7', '\u0000', 'A', '\u0000', '\u000f', '\u0000', 'A', '\u00ff', '\u00c1', '\f', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', 'd', '\u0000', 'A', '\u0000', 'd', '\u00c1', '\b', '\u0000', '7', '\u0000', 'A', '\u0000', '\u000f', '\u0000', 'A', '\u00ff', '\u00c1', '\b', '\u0000', 'A', '\u0000', '<', '\u0000', '-', '\u0000', '<', '\u00c1', '$', '\u0000', '<', '\u0000', '<', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'Z', '\u0000', '\u0019', '\u0000', 'd', '\u0000', '2', '\u0000', 'd', '\u0000', '<', '\u0000', 'Z', '\u00ff', '\u00c1', '\b', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', 'd', '\u00c1', '\b', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'A', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u000f', '\u0000', 'A', '\u0000', 'A', '\u0000', 'A', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '<', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '<', '\u0000', 'd', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '(', '\u0000', '\u001e', '\u0000', '2', '\u0000', '(', '\u0000', '2', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', 'd', '\u0000', 'A', '\u0000', 'd', '\u00ff', '\u00c1', '\b', '\u0000', '\u0012', '\u0000', '\u001e', '\u0000', '\u0012', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u0012', '\u0000', '<', '\u0000', ':', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u001c', '\u0000', 'F', '\u0000', '?', '\u0000', '\u001e', '\u00ff', '\u00c1', '\f', '\u0000', '\u000f', '\u0000', 'd', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', 'A', '\u0000', '\u001e', '\u00ff', '\u00c1', '\u0014', '\u0000', 'F', '\u0000', '\u001e', '\u0000', 'F', '\u0000', 'd', '\u0000', '(', '\u0000', 'A', '\u0000', '\n', '\u0000', 'd', '\u0000', '\n', '\u0000', '\u001e', '\u00ff', '\u00c1', '\u0010', '\u0000', 'F', '\u0000', 'd', '\u0000', 'F', '\u0000', '\u001e', '\u0000', '\n', '\u0000', 'd', '\u0000', '\n', '\u0000', '\u001e', '\u00ff', '\u00c1', '$', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'Z', '\u0000', '\u0019', '\u0000', 'd', '\u0000', '7', '\u0000', 'd', '\u0000', 'A', '\u0000', 'Z', '\u0000', 'A', '\u0000', '(', '\u00ff', '\u00c1', '\u001c', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', 'd', '\u0000', '7', '\u0000', 'd', '\u0000', 'A', '\u0000', 'Z', '\u0000', 'A', '\u0000', 'K', '\u0000', '7', '\u0000', 'A', '\u0000', '\u000f', '\u0000', 'A', '\u00ff', '\u00c1', '$', '\u0000', '7', '\u0000', 'd', '\u0000', 'A', '\u0000', 'Z', '\u0000', 'A', '\u0000', '2', '\u0000', '-', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'Z', '\u0000', '\u0019', '\u0000', 'd', '\u0000', '7', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u00c1', '\b', '\u0000', '(', '\u0000', '7', '\u0000', 'A', '\u0000', '\u001e', '\u00ff', '\u00c1', '\u001c', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', 'd', '\u0000', '7', '\u0000', 'd', '\u0000', 'A', '\u0000', 'Z', '\u0000', 'A', '\u0000', 'K', '\u0000', '7', '\u0000', 'A', '\u0000', '\u000f', '\u0000', 'A', '\u00c1', '\b', '\u0000', '\u001e', '\u0000', 'A', '\u0000', 'A', '\u0000', '\u001e', '\u00ff', '\u00c1', '0', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '7', '\u0000', '\u001e', '\u0000', 'A', '\u0000', '(', '\u0000', 'A', '\u0000', '<', '\u0000', '<', '\u0000', 'A', '\u0000', '\u0019', '\u0000', 'A', '\u0000', '\u000f', '\u0000', 'K', '\u0000', '\u000f', '\u0000', 'Z', '\u0000', '\u0019', '\u0000', 'd', '\u0000', '7', '\u0000', 'd', '\u0000', 'A', '\u0000', 'Z', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\n', '\u0000', 'd', '\u0000', 'F', '\u0000', 'd', '\u00ff', '\u00c1', '\u0018', '\u0000', 'A', '\u0000', 'd', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'd', '\u00ff', '\u00c1', '\f', '\u0000', '\n', '\u0000', 'd', '\u0000', '(', '\u0000', '\u001e', '\u0000', 'F', '\u0000', 'd', '\u00ff', '\u00c1', '\u001c', '\u0000', '\n', '\u0000', 'd', '\u0000', '\n', '\u0000', '(', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '(', '\u0000', '2', '\u0000', '<', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '(', '\u0000', 'F', '\u0000', 'd', '\u00c1', '\b', '\u0000', '(', '\u0000', 'F', '\u0000', '(', '\u0000', '2', '\u00ff', '\u00c1', '\b', '\u0000', '\n', '\u0000', '\u001e', '\u0000', 'F', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\n', '\u0000', 'd', '\u0000', 'F', '\u0000', '\u001e', '\u00ff', '\u00c1', '\f', '\u0000', '\u000f', '\u0000', 'd', '\u0000', '(', '\u0000', 'A', '\u0000', 'A', '\u0000', 'd', '\u00c1', '\b', '\u0000', '(', '\u0000', 'A', '\u0000', '(', '\u0000', '\u001e', '\u00ff', '\u00c1', '\u0010', '\u0000', '\n', '\u0000', 'd', '\u0000', 'F', '\u0000', 'd', '\u0000', '\n', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '\u001e', '\u00ff', '\u00c1', '\u0010', '\u0000', '2', '\u0000', 'd', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '\u001e', '\u0000', '\n', '\u0000', '2', '\u0000', '\n', '\u00ff', '\u00c1', '\b', '\u0000', '\n', '\u0000', 'd', '\u0000', 'F', '\u0000', '(', '\u00ff', '\u00c1', '\u0010', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '2', '\u0000', 'd', '\u0000', '2', '\u0000', '\n', '\u0000', '\u001e', '\u0000', '\n', '\u00ff', '\u00c1', '\f', '\u0000', '\u001e', '\u0000', '_', '\u0000', '(', '\u0000', 'i', '\u0000', '2', '\u0000', '_', '\u00ff', '\u00c1', '\b', '\u0000', '\u0000', '\u0000', '\n', '\u0000', 'P', '\u0000', '\n', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', '_', '\u0000', '\u0014', '\u0000', 'i', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u0019', '\u0000', 'K', '\u0000', '-', '\u0000', 'K', '\u0000', '7', '\u0000', 'A', '\u0000', '7', '\u0000', '(', '\u0000', 'A', '\u0000', '\u001e', '\u00c1', '\u001c', '\u0000', '7', '\u0000', '(', '\u0000', '-', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', '-', '\u0000', '\u0019', '\u0000', '7', '\u0000', '7', '\u0000', '7', '\u00ff', '\u00c1', ' ', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '2', '\u0000', '\u001e', '\u0000', '<', '\u0000', '(', '\u0000', '<', '\u0000', 'A', '\u0000', '2', '\u0000', 'K', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '\u0014', '\u0000', 'A', '\u00ff', '\u00c1', ' ', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u00ff', '\u00c1', '$', '\u0000', '<', '\u0000', 'd', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u00c1', '\b', '\u0000', '<', '\u0000', '(', '\u0000', 'F', '\u0000', '\u001e', '\u00ff', '\u00c1', '(', '\u0000', '<', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '7', '\u0000', '7', '\u0000', '2', '\u0000', '\u0014', '\u0000', '2', '\u00ff', '\u00c1', '\u0010', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', 'Z', '\u0000', '2', '\u0000', 'd', '\u0000', 'F', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'K', '\u0000', '<', '\u0000', 'K', '\u00ff', '\u00c1', '4', '\u0000', '\u0014', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '\n', '\u0000', '2', '\u0000', '\n', '\u0000', '<', '\u0000', '\u0014', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '(', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '2', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '(', '\u00c1', '\b', '\u0000', '<', '\u0000', 'A', '\u0000', 'F', '\u0000', 'K', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', 'd', '\u00c1', '\u0014', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '\u001e', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', 'K', '\u00c1', '\u0014', '\u0000', '(', '\u0000', 'f', '\u0000', '*', '\u0000', 'd', '\u0000', '(', '\u0000', 'b', '\u0000', '&', '\u0000', 'd', '\u0000', '(', '\u0000', 'f', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '-', '\u0000', '\u0014', '\u0000', '7', '\u0000', '\u001e', '\u0000', '7', '\u0000', 'K', '\u00c1', '\u0014', '\u0000', '7', '\u0000', 'f', '\u0000', '9', '\u0000', 'd', '\u0000', '7', '\u0000', 'b', '\u0000', '5', '\u0000', 'd', '\u0000', '7', '\u0000', 'f', '\u00ff', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', '-', '\u0000', '7', '\u0000', 'K', '\u00c1', '\b', '\u0000', '#', '\u0000', '7', '\u0000', 'A', '\u0000', '\u001e', '\u00ff', '\u00c1', '\f', '\u0000', '(', '\u0000', 'd', '\u0000', '(', '\u0000', '\u001e', '\u0000', '2', '\u0000', '\u001e', '\u00ff', '\u00c1', '\u0018', '\u0000', '\n', '\u0000', '\u001e', '\u0000', '\n', '\u0000', 'A', '\u0000', '\u0014', '\u0000', 'K', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '(', '\u0000', 'A', '\u0000', '(', '\u0000', '\u001e', '\u00c1', '\u0014', '\u0000', '(', '\u0000', 'A', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'K', '\u0000', 'F', '\u0000', 'A', '\u0000', 'F', '\u0000', '\u001e', '\u00ff', '\u00c1', '\f', '\u0000', '\n', '\u0000', 'K', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u0014', '\u0000', '\u001e', '\u00c1', '\u0014', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '\u001e', '\u00ff', '\u00c1', '$', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u00ff', '\u00c1', ' ', '\u0000', '\u0014', '\u0000', '\n', '\u0000', '\u0014', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u00ff', '\u00c1', '0', '\u0000', 'F', '\u0000', '\u000f', '\u0000', 'A', '\u0000', '\u000f', '\u0000', '7', '\u0000', '\n', '\u0000', '7', '\u0000', '(', '\u0000', '-', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'A', '\u0000', '\u0019', '\u0000', 'K', '\u0000', '-', '\u0000', 'K', '\u0000', '7', '\u0000', 'A', '\u0000', '7', '\u0000', '(', '\u00ff', '\u00c1', '\u0010', '\u0000', '\u000f', '\u0000', 'K', '\u0000', '\u0019', '\u0000', 'A', '\u0000', '#', '\u0000', 'K', '\u0000', '<', '\u0000', 'K', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', 'A', '\u0000', '\u0019', '\u0000', '\u001e', '\u00ff', '\u00c1', '$', '\u0000', '<', '\u0000', 'K', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', '7', '\u0000', '7', '\u0000', '7', '\u0000', '<', '\u0000', '2', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '\u001e', '\u00ff', '\u00c1', '\u0010', '\u0000', '(', '\u0000', 'd', '\u0000', '(', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '<', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'K', '\u0000', '<', '\u0000', 'K', '\u00ff', '\u00c1', '\u0018', '\u0000', '9', '\u0000', 'K', '\u0000', '9', '\u0000', '(', '\u0000', '/', '\u0000', '\u001e', '\u0000', '\u001b', '\u0000', '\u001e', '\u0000', '\u0011', '\u0000', '(', '\u0000', '\u0011', '\u0000', 'K', '\u00c1', '\b', '\u0000', '9', '\u0000', '(', '\u0000', 'C', '\u0000', '\u001e', '\u00ff', '\u00c1', '\f', '\u0000', '\u000f', '\u0000', 'K', '\u0000', '(', '\u0000', '\u001e', '\u0000', 'A', '\u0000', 'K', '\u00ff', '\u00c1', '\u0014', '\u0000', '\n', '\u0000', 'K', '\u0000', '\n', '\u0000', '(', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '(', '\u0000', '2', '\u0000', '(', '\u0000', '<', '\u00c1', '\u0010', '\u0000', '(', '\u0000', '2', '\u0000', '<', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '(', '\u0000', 'F', '\u0000', 'K', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '<', '\u0000', 'K', '\u00c1', '\b', '\u0000', '<', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', 'K', '\u00ff', '\u00c1', '\f', '\u0000', '\u0014', '\u0000', 'K', '\u0000', '\u0014', '\u0000', '2', '\u0000', '(', '\u0000', '\u001e', '\u00c1', '\f', '\u0000', '<', '\u0000', 'K', '\u0000', '<', '\u0000', '2', '\u0000', '\u0014', '\u0000', '\n', '\u00ff', '\u00c1', '\u0010', '\u0000', '\u0014', '\u0000', 'K', '\u0000', '<', '\u0000', 'K', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '<', '\u0000', '\u001e', '\u00ff', '\u00c1', '$', '\u0000', '2', '\u0000', 'd', '\u0000', '(', '\u0000', 'd', '\u0000', '\u001e', '\u0000', 'Z', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '\u0014', '\u0000', '<', '\u0000', '\u001e', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '(', '\u0000', '\u0014', '\u0000', '2', '\u0000', '\u0014', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', 'd', '\u0000', '(', '\u0000', '\u0014', '\u00ff', '\u00c1', '$', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '(', '\u0000', 'd', '\u0000', '2', '\u0000', 'Z', '\u0000', '2', '\u0000', 'F', '\u0000', '<', '\u0000', '<', '\u0000', '2', '\u0000', '2', '\u0000', '2', '\u0000', '\u001e', '\u0000', '(', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '\u0014', '\u00ff', '\u00c1', '\u0018', '\u0000', '\u0019', '\u0000', '_', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '%', '\u0000', 'd', '\u0000', '+', '\u0000', '_', '\u0000', '2', '\u0000', '_', '\u0000', '7', '\u0000', 'd', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00ff', '\u00c1', '\u0014', '\u0000', '(', '\u0000', 'P', '\u0000', '%', '\u0000', 'M', '\u0000', '(', '\u0000', 'J', '\u0000', '+', '\u0000', 'M', '\u0000', '(', '\u0000', 'P', '\u00c1', '\b', '\u0000', '(', '\u0000', '2', '\u0000', '(', '\u0000', '\n', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', 'd', '\u0000', '(', '\u0000', '-', '\u00c1', '\u0018', '\u0000', '2', '\u0000', 'Z', '\u0000', '\u001e', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', 'P', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', '7', '\u0000', '2', '\u0000', '7', '\u00ff', '\u00c1', ' ', '\u0000', '<', '\u0000', 'Z', '\u0000', '2', '\u0000', 'd', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '\u0014', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', 'P', '\u0000', '\u001e', '\u0000', '<', '\u0000', '\u001e', '\u0000', '(', '\u0000', '\u0014', '\u0000', '\u001e', '\u00c1', '\u0010', '\u0000', '\u001e', '\u0000', '(', '\u0000', '(', '\u0000', '\u001e', '\u0000', '7', '\u0000', '\u001e', '\u0000', 'A', '\u0000', '(', '\u00c1', '\b', '\u0000', '\u000f', '\u0000', '<', '\u0000', '-', '\u0000', '<', '\u00ff', '\u00c1', '\u0018', '\u0000', '\u000f', '\u0000', 'Z', '\u0000', '\u0019', '\u0000', 'P', '\u0000', '7', '\u0000', 'P', '\u0000', '7', '\u0000', '2', '\u0000', '\u0019', '\u0000', '2', '\u0000', '\u0019', '\u0000', 'P', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', '2', '\u0000', '\u000f', '\u0000', '(', '\u00c1', '\b', '\u0000', '7', '\u0000', '2', '\u0000', 'A', '\u0000', '(', '\u00c1', '\b', '\u0000', '7', '\u0000', 'P', '\u0000', 'A', '\u0000', 'Z', '\u00ff', '\u00c1', '\f', '\u0000', '\u000f', '\u0000', 'd', '\u0000', '(', '\u0000', 'A', '\u0000', 'A', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', 'A', '\u0000', '7', '\u0000', 'A', '\u00c1', '\b', '\u0000', '7', '\u0000', '7', '\u0000', '\u0019', '\u0000', '7', '\u00c1', '\b', '\u0000', '(', '\u0000', 'A', '\u0000', '(', '\u0000', '\u001e', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', '\u0014', '\u0000', '(', '\u0000', '2', '\u00c1', '\b', '\u0000', '(', '\u0000', 'F', '\u0000', '(', '\u0000', 'd', '\u00ff', '\u00c1', '8', '\u0000', 'A', '\u0000', '_', '\u0000', '7', '\u0000', 'i', '\u0000', '\u0019', '\u0000', 'i', '\u0000', '\u000f', '\u0000', '_', '\u0000', '\u000f', '\u0000', 'U', '\u0000', '\u0019', '\u0000', 'K', '\u0000', '7', '\u0000', 'K', '\u0000', 'A', '\u0000', 'A', '\u0000', 'A', '\u0000', '7', '\u0000', '7', '\u0000', '-', '\u0000', '\u0019', '\u0000', '-', '\u0000', '\u000f', '\u0000', '7', '\u0000', '\u000f', '\u0000', 'A', '\u0000', '\u0019', '\u0000', 'K', '\u00c1', '\u0018', '\u0000', '7', '\u0000', '-', '\u0000', 'A', '\u0000', '#', '\u0000', 'A', '\u0000', '\u0019', '\u0000', '7', '\u0000', '\u000f', '\u0000', '\u0019', '\u0000', '\u000f', '\u0000', '\u000f', '\u0000', '\u0019', '\u00ff', '\u00c1', '\b', '\u0000', '\u001c', '\u0000', '_', '\u0000', '\u001c', '\u0000', 'i', '\u00c1', '\b', '\u0000', '4', '\u0000', 'i', '\u0000', '4', '\u0000', '_', '\u00ff', '\u00c1', '$', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '2', '\u0000', 'd', '\u0000', '<', '\u0000', 'Z', '\u0000', '<', '\u0000', 'A', '\u0000', '2', '\u0000', '7', '\u0000', '\u001e', '\u0000', '7', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u0014', '\u0000', 'Z', '\u0000', '\u001e', '\u0000', 'd', '\u00c1', ' ', '\u0000', '2', '\u0000', 'F', '\u0000', '-', '\u0000', 'A', '\u0000', '#', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '\u001e', '\u0000', 'U', '\u0000', '#', '\u0000', 'Z', '\u0000', '-', '\u0000', 'Z', '\u0000', '2', '\u0000', 'U', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '-', '\u0000', 'd', '\u0000', '2', '\u0000', '_', '\u0000', '2', '\u0000', 'K', '\u0000', '7', '\u0000', 'F', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', '<', '\u0000', '7', '\u0000', '<', '\u00c1', '\u001c', '\u0000', '2', '\u0000', 'K', '\u0000', '-', '\u0000', 'F', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '\u0019', '\u0000', 'K', '\u0000', '\u0019', '\u0000', 'P', '\u0000', '\u001e', '\u0000', 'U', '\u0000', '2', '\u0000', 'U', '\u00ff', '\u00c1', '\f', '\u0000', '(', '\u0000', 'K', '\u0000', '\u0011', '\u0000', '5', '\u0000', '(', '\u0000', '\u001e', '\u00c1', '\f', '\u0000', '?', '\u0000', 'K', '\u0000', '(', '\u0000', '5', '\u0000', '?', '\u0000', '\u001e', '\u00ff', '\u00c1', '\f', '\u0000', '\u000f', '\u0000', 'K', '\u0000', 'A', '\u0000', 'K', '\u0000', 'A', '\u0000', '7', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '7', '\u0000', '<', '\u0000', '7', '\u00ff', '\u00c1', '$', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '2', '\u0000', 'd', '\u0000', '<', '\u0000', 'Z', '\u0000', '<', '\u0000', 'A', '\u0000', '2', '\u0000', '7', '\u0000', '\u001e', '\u0000', '7', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u0014', '\u0000', 'Z', '\u0000', '\u001e', '\u0000', 'd', '\u00c1', '\u001c', '\u0000', '\u001e', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'Z', '\u0000', '-', '\u0000', 'Z', '\u0000', '2', '\u0000', 'U', '\u0000', '2', '\u0000', 'P', '\u0000', '-', '\u0000', 'K', '\u0000', '\u001e', '\u0000', 'K', '\u00c1', '\b', '\u0000', '(', '\u0000', 'K', '\u0000', '2', '\u0000', 'A', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '<', '\u0000', 'd', '\u00ff', '\u00c1', '$', '\u0000', '2', '\u0000', '_', '\u0000', '2', '\u0000', 'U', '\u0000', '-', '\u0000', 'P', '\u0000', '#', '\u0000', 'P', '\u0000', '\u001e', '\u0000', 'U', '\u0000', '\u001e', '\u0000', '_', '\u0000', '#', '\u0000', 'd', '\u0000', '-', '\u0000', 'd', '\u0000', '2', '\u0000', '_', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', 'Z', '\u0000', '(', '\u0000', '2', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '(', '\u0000', '<', '\u0000', '(', '\u00c1', '\b', '\u0000', '<', '\u0000', 'F', '\u0000', '\u0014', '\u0000', 'F', '\u00ff', '\u00c1', ' ', '\u0000', '\u001e', '\u0000', 'Z', '\u0000', '\u001e', '\u0000', '_', '\u0000', '#', '\u0000', 'd', '\u0000', '-', '\u0000', 'd', '\u0000', '2', '\u0000', '_', '\u0000', '2', '\u0000', 'Z', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '2', '\u0000', 'F', '\u00ff', '\u00c1', '$', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '2', '\u0000', 'd', '\u0000', '(', '\u0000', 'Z', '\u0000', '-', '\u0000', 'Z', '\u0000', '2', '\u0000', 'U', '\u0000', '2', '\u0000', 'K', '\u0000', '-', '\u0000', 'F', '\u0000', '#', '\u0000', 'F', '\u0000', '\u001e', '\u0000', 'K', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', '_', '\u0000', '<', '\u0000', 'i', '\u00ff', '\u00c1', '\f', '\u0000', 'B', '\u0000', 'K', '\u0000', '9', '\u0000', '(', '\u0000', 'A', '\u0000', '\u001e', '\u00c1', '\u0014', '\u0000', '9', '\u0000', '(', '\u0000', '/', '\u0000', '\u001e', '\u0000', '\u001b', '\u0000', '\u001e', '\u0000', '\u0011', '\u0000', '(', '\u0000', '\n', '\u0000', '\n', '\u00c1', '\b', '\u0000', '\u0011', '\u0000', '(', '\u0000', '\u0019', '\u0000', 'K', '\u00ff', '\u00c1', '\u001c', '\u0000', '<', '\u0000', '\u001e', '\u0000', '<', '\u0000', 'Z', '\u0000', '\u001e', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', 'P', '\u0000', '\u0014', '\u0000', 'F', '\u0000', '\u001e', '\u0000', '<', '\u0000', '<', '\u0000', '<', '\u00c1', '\b', '\u0000', '(', '\u0000', 'Z', '\u0000', '(', '\u0000', '\u001e', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', '\u001e', '\u0000', '\u001e', '\u00ff', '\u00c1', '\u0018', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', '\u0019', '\u0000', '-', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u000f', '\u0000', '#', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\u0014', '\u00ff', '\u00c1', '\u0010', '\u0000', '\u001e', '\u0000', 'Z', '\u0000', '(', '\u0000', 'd', '\u0000', '(', '\u0000', 'F', '\u0000', '2', '\u0000', 'F', '\u00c1', '\b', '\u0000', '(', '\u0000', 'F', '\u0000', '\u001e', '\u0000', 'F', '\u00ff', '\u00c1', '$', '\u0000', '\u0019', '\u0000', '_', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '2', '\u0000', 'd', '\u0000', '7', '\u0000', '_', '\u0000', '7', '\u0000', 'K', '\u0000', '2', '\u0000', 'F', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '\u0019', '\u0000', 'K', '\u0000', '\u0019', '\u0000', '_', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', '<', '\u0000', '7', '\u0000', '<', '\u00ff', '\u00c1', '\f', '\u0000', '\u0011', '\u0000', 'K', '\u0000', '(', '\u0000', '5', '\u0000', '\u0011', '\u0000', '\u001e', '\u00c1', '\f', '\u0000', '(', '\u0000', 'K', '\u0000', '?', '\u0000', '5', '\u0000', '(', '\u0000', '\u001e', '\u00ff', '\u00c1', '\b', '\u0000', '\n', '\u0000', '\u001e', '\u0000', 'F', '\u0000', 'd', '\u00c1', '\f', '\u0000', '\n', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '\u0014', '\u0000', 'F', '\u00c1', '\b', '\u0000', '\n', '\u0000', 'F', '\u0000', '\u001e', '\u0000', 'F', '\u00c1', '\u0014', '\u0000', '<', '\u0000', '\u001e', '\u0000', '<', '\u0000', '<', '\u0000', '-', '\u0000', '(', '\u0000', '-', '\u0000', '#', '\u0000', 'A', '\u0000', '#', '\u00ff', '\u00c1', '\b', '\u0000', '\n', '\u0000', '\u001e', '\u0000', 'F', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '\n', '\u0000', 'F', '\u00c1', ' ', '\u0000', 'F', '\u0000', '\u001e', '\u0000', '2', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '2', '\u0000', 'F', '\u0000', '7', '\u0000', 'A', '\u0000', '<', '\u0000', '7', '\u0000', '<', '\u0000', '2', '\u0000', '7', '\u0000', '2', '\u0000', '2', '\u00c1', '\f', '\u0000', '\u0014', '\u0000', 'F', '\u0000', '\u0014', '\u0000', 'd', '\u0000', '\n', '\u0000', 'Z', '\u00ff', '\u00c1', '\b', '\u0000', '\n', '\u0000', '\u001e', '\u0000', 'F', '\u0000', 'd', '\u00c1', '\u0014', '\u0000', '<', '\u0000', '\u001e', '\u0000', '<', '\u0000', '<', '\u0000', '-', '\u0000', '(', '\u0000', '-', '\u0000', '#', '\u0000', 'A', '\u0000', '#', '\u00c1', '$', '\u0000', '\n', '\u0000', 'd', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '\u0014', '\u0000', 'Z', '\u0000', '\u0019', '\u0000', 'Z', '\u0000', '\u001e', '\u0000', 'U', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '\u0019', '\u0000', 'F', '\u0000', '\u000f', '\u0000', 'F', '\u0000', '\n', '\u0000', 'K', '\u00ff', '\u00c1', '\u0014', '\u0000', '(', '\u0000', 'P', '\u0000', '%', '\u0000', 'M', '\u0000', '(', '\u0000', 'J', '\u0000', '+', '\u0000', 'M', '\u0000', '(', '\u0000', 'P', '\u00c1', ' ', '\u0000', '(', '\u0000', '<', '\u0000', '(', '\u0000', '-', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\n', '\u0000', '2', '\u0000', '\n', '\u0000', '7', '\u0000', '\u000f', '\u0000', '7', '\u0000', '\u001a', '\u00ff', '\u00c1', '\u0014', '\u0000', '\n', '\u0000', '\u001e', '\u0000', '\n', '\u0000', '7', '\u0000', '(', '\u0000', 'U', '\u0000', 'F', '\u0000', '8', '\u0000', 'F', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', 'F', '\u0000', '-', '\u0000', '\n', '\u0000', '-', '\u00c1', '\b', '\u0000', '(', '\u0000', '_', '\u0000', '\u0014', '\u0000', 'i', '\u00ff', '\u00c1', '\u0014', '\u0000', '\n', '\u0000', '\u001e', '\u0000', '\n', '\u0000', '7', '\u0000', '(', '\u0000', 'U', '\u0000', 'F', '\u0000', '8', '\u0000', 'F', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', 'F', '\u0000', '-', '\u0000', '\n', '\u0000', '-', '\u00c1', '\b', '\u0000', '(', '\u0000', '_', '\u0000', '<', '\u0000', 'i', '\u00ff', '\u00c1', '\u0014', '\u0000', '\n', '\u0000', '\u001e', '\u0000', '\n', '\u0000', '7', '\u0000', '(', '\u0000', 'U', '\u0000', 'F', '\u0000', '8', '\u0000', 'F', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', 'F', '\u0000', '-', '\u0000', '\n', '\u0000', '-', '\u00c1', '\f', '\u0000', '\u001e', '\u0000', '_', '\u0000', '(', '\u0000', 'i', '\u0000', '2', '\u0000', '_', '\u00ff', '\u00c1', '\u0014', '\u0000', '\n', '\u0000', '\u001e', '\u0000', '\n', '\u0000', '7', '\u0000', '(', '\u0000', 'U', '\u0000', 'F', '\u0000', '8', '\u0000', 'F', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', 'F', '\u0000', '-', '\u0000', '\n', '\u0000', '-', '\u00c1', '\u0018', '\u0000', '\u0019', '\u0000', '_', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '%', '\u0000', 'd', '\u0000', '+', '\u0000', '_', '\u0000', '2', '\u0000', '_', '\u0000', '7', '\u0000', 'd', '\u00ff', '\u00c1', '\u0014', '\u0000', '\n', '\u0000', '\u001e', '\u0000', '\n', '\u0000', '7', '\u0000', '(', '\u0000', 'U', '\u0000', 'F', '\u0000', '8', '\u0000', 'F', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', 'F', '\u0000', '-', '\u0000', '\n', '\u0000', '-', '\u00c1', '\b', '\u0000', '4', '\u0000', '_', '\u0000', '4', '\u0000', 'i', '\u00c1', '\b', '\u0000', '\u001c', '\u0000', '_', '\u0000', '\u001c', '\u0000', 'i', '\u00ff', '\u00c1', '\u0014', '\u0000', '\n', '\u0000', '\u001e', '\u0000', '\n', '\u0000', '7', '\u0000', '(', '\u0000', 'U', '\u0000', 'F', '\u0000', '8', '\u0000', 'F', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', 'F', '\u0000', '-', '\u0000', '\n', '\u0000', '-', '\u00c1', '$', '\u0000', '%', '\u0000', 'i', '\u0000', '*', '\u0000', 'i', '\u0000', '/', '\u0000', 'd', '\u0000', '/', '\u0000', '_', '\u0000', '*', '\u0000', 'Z', '\u0000', '%', '\u0000', 'Z', '\u0000', ' ', '\u0000', '_', '\u0000', ' ', '\u0000', 'd', '\u0000', '%', '\u0000', 'i', '\u00ff', '\u00c1', '\u0010', '\u0000', 'F', '\u0000', '\u001e', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', 'd', '\u0000', '\n', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '(', '\u0000', 'd', '\u0000', 'F', '\u0000', 'd', '\u00c1', '\b', '\u0000', '(', '\u0000', 'A', '\u0000', '<', '\u0000', 'A', '\u00c1', '\b', '\u0000', '(', '\u0000', '2', '\u0000', '\u0013', '\u0000', '2', '\u00ff', '\u00c1', ' ', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'Z', '\u0000', '\u0019', '\u0000', 'd', '\u0000', '7', '\u0000', 'd', '\u0000', 'A', '\u0000', 'Z', '\u00c1', '\u0018', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', '\u0019', '\u0000', '-', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u000f', '\u0000', '#', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\u0014', '\u00ff', '\u00c1', '\u0010', '\u0000', 'A', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', 'U', '\u0000', 'A', '\u0000', 'U', '\u00c1', '\b', '\u0000', '7', '\u0000', ':', '\u0000', '\u000f', '\u0000', ':', '\u00c1', '\b', '\u0000', '(', '\u0000', '_', '\u0000', '\u0014', '\u0000', 'i', '\u00ff', '\u00c1', '\u0010', '\u0000', 'A', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', 'U', '\u0000', 'A', '\u0000', 'U', '\u00c1', '\b', '\u0000', '7', '\u0000', ':', '\u0000', '\u000f', '\u0000', ':', '\u00c1', '\b', '\u0000', '(', '\u0000', '_', '\u0000', '<', '\u0000', 'i', '\u00ff', '\u00c1', '\u0010', '\u0000', 'A', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', 'U', '\u0000', 'A', '\u0000', 'U', '\u00c1', '\b', '\u0000', '7', '\u0000', ':', '\u0000', '\u000f', '\u0000', ':', '\u00c1', '\f', '\u0000', '\u001e', '\u0000', '_', '\u0000', '(', '\u0000', 'i', '\u0000', '2', '\u0000', '_', '\u00ff', '\u00c1', '\u0010', '\u0000', 'A', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', 'U', '\u0000', 'A', '\u0000', 'U', '\u00c1', '\b', '\u0000', '7', '\u0000', ':', '\u0000', '\u000f', '\u0000', ':', '\u00c1', '\b', '\u0000', '4', '\u0000', '_', '\u0000', '4', '\u0000', 'i', '\u00c1', '\b', '\u0000', '\u001c', '\u0000', '_', '\u0000', '\u001c', '\u0000', 'i', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '<', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', 'U', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'U', '\u0000', '<', '\u0000', 'U', '\u00c1', '\b', '\u0000', '(', '\u0000', '_', '\u0000', '\u0014', '\u0000', 'i', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '<', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', 'U', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'U', '\u0000', '<', '\u0000', 'U', '\u00c1', '\b', '\u0000', '(', '\u0000', '_', '\u0000', '<', '\u0000', 'i', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '<', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', 'U', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'U', '\u0000', '<', '\u0000', 'U', '\u00c1', '\f', '\u0000', '\u001e', '\u0000', '_', '\u0000', '(', '\u0000', 'i', '\u0000', '2', '\u0000', '_', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '<', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', 'U', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'U', '\u0000', '<', '\u0000', 'U', '\u00c1', '\b', '\u0000', '\u001c', '\u0000', '_', '\u0000', '\u001c', '\u0000', 'h', '\u00c1', '\b', '\u0000', '4', '\u0000', 'h', '\u0000', '4', '\u0000', '_', '\u00ff', '\u00c1', '\u0018', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '7', '\u0000', '\u001e', '\u0000', 'A', '\u0000', '(', '\u0000', 'A', '\u0000', 'Z', '\u0000', '7', '\u0000', 'd', '\u0000', '\u000f', '\u0000', 'd', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', 'd', '\u0000', '\u0019', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '\n', '\u0000', 'B', '\u0000', '(', '\u0000', 'B', '\u00ff', '\u00c1', '\u0010', '\u0000', 'F', '\u0000', 'U', '\u0000', 'F', '\u0000', '\u001e', '\u0000', '\n', '\u0000', 'U', '\u0000', '\n', '\u0000', '\u001e', '\u00c1', '\u0018', '\u0000', '\u0019', '\u0000', '_', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '%', '\u0000', 'd', '\u0000', '+', '\u0000', '_', '\u0000', '2', '\u0000', '_', '\u0000', '7', '\u0000', 'd', '\u00ff', '\u00c1', '$', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'K', '\u0000', '\u0019', '\u0000', 'U', '\u0000', '7', '\u0000', 'U', '\u0000', 'A', '\u0000', 'K', '\u0000', 'A', '\u0000', '(', '\u00c1', '\b', '\u0000', '(', '\u0000', '_', '\u0000', '\u0014', '\u0000', 'i', '\u00ff', '\u00c1', '$', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'K', '\u0000', '\u0019', '\u0000', 'U', '\u0000', '7', '\u0000', 'U', '\u0000', 'A', '\u0000', 'K', '\u0000', 'A', '\u0000', '(', '\u00c1', '\b', '\u0000', '(', '\u0000', '_', '\u0000', '<', '\u0000', 'i', '\u00ff', '\u00c1', '$', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'K', '\u0000', '\u0019', '\u0000', 'U', '\u0000', '7', '\u0000', 'U', '\u0000', 'A', '\u0000', 'K', '\u0000', 'A', '\u0000', '(', '\u00c1', '\f', '\u0000', '\u001e', '\u0000', '_', '\u0000', '(', '\u0000', 'i', '\u0000', '2', '\u0000', '_', '\u00ff', '\u00c1', '$', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'K', '\u0000', '\u0019', '\u0000', 'U', '\u0000', '7', '\u0000', 'U', '\u0000', 'A', '\u0000', 'K', '\u0000', 'A', '\u0000', '(', '\u00c1', '\u0018', '\u0000', '\u0019', '\u0000', '_', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '%', '\u0000', 'd', '\u0000', '+', '\u0000', '_', '\u0000', '2', '\u0000', '_', '\u0000', '7', '\u0000', 'd', '\u00ff', '\u00c1', '$', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'K', '\u0000', '\u0019', '\u0000', 'U', '\u0000', '7', '\u0000', 'U', '\u0000', 'A', '\u0000', 'K', '\u0000', 'A', '\u0000', '(', '\u00c1', '\b', '\u0000', '4', '\u0000', '_', '\u0000', '4', '\u0000', 'i', '\u00c1', '\b', '\u0000', '\u001c', '\u0000', 'i', '\u0000', '\u001c', '\u0000', '_', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '#', '\u0000', '<', '\u0000', 'P', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'P', '\u0000', '<', '\u0000', '#', '\u00ff', '\u00c1', '$', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'Z', '\u0000', '\u0019', '\u0000', 'd', '\u0000', '7', '\u0000', 'd', '\u0000', 'A', '\u0000', 'Z', '\u0000', 'A', '\u0000', '(', '\u00c1', '\b', '\u0000', 'F', '\u0000', 'n', '\u0000', '\n', '\u0000', '\u0014', '\u00ff', '\u00c1', '\u0018', '\u0000', 'A', '\u0000', 'U', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'U', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'i', '\u0000', '(', '\u0000', '_', '\u00ff', '\u00c1', '\u0018', '\u0000', 'A', '\u0000', 'U', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'U', '\u00c1', '\b', '\u0000', '<', '\u0000', 'i', '\u0000', '(', '\u0000', '_', '\u00ff', '\u00c1', '\u0018', '\u0000', 'A', '\u0000', 'U', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'U', '\u00c1', '\f', '\u0000', '\u001e', '\u0000', '_', '\u0000', '(', '\u0000', 'i', '\u0000', '2', '\u0000', '_', '\u00ff', '\u00c1', '\u0018', '\u0000', 'A', '\u0000', 'U', '\u0000', 'A', '\u0000', '(', '\u0000', '7', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', 'U', '\u00c1', '\b', '\u0000', '4', '\u0000', '_', '\u0000', '4', '\u0000', 'i', '\u00c1', '\b', '\u0000', '\u001c', '\u0000', '_', '\u0000', '\u001c', '\u0000', 'i', '\u00ff', '\u00c1', '\f', '\u0000', '\u000f', '\u0000', 'U', '\u0000', '(', '\u0000', '7', '\u0000', 'A', '\u0000', 'U', '\u00c1', '\b', '\u0000', '(', '\u0000', '7', '\u0000', '(', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '(', '\u0000', '_', '\u0000', '<', '\u0000', 'i', '\u00ff', '\u00c1', '\b', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', 'd', '\u00c1', '\u0018', '\u0000', '\u000f', '\u0000', 'S', '\u0000', '7', '\u0000', 'S', '\u0000', 'A', '\u0000', 'I', '\u0000', 'A', '\u0000', '9', '\u0000', '7', '\u0000', '/', '\u0000', '\u000f', '\u0000', '/', '\u00ff', '\u00c1', ',', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '7', '\u0000', '\u001e', '\u0000', 'A', '\u0000', '(', '\u0000', 'A', '\u0000', '<', '\u0000', '7', '\u0000', 'F', '\u0000', 'A', '\u0000', 'P', '\u0000', 'A', '\u0000', 'Z', '\u0000', '7', '\u0000', 'd', '\u0000', '\u001e', '\u0000', 'd', '\u0000', '\u0014', '\u0000', 'Z', '\u0000', '\u0014', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '\u001f', '\u0000', 'F', '\u0000', '7', '\u0000', 'F', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u0019', '\u0000', 'K', '\u0000', '-', '\u0000', 'K', '\u0000', '7', '\u0000', 'A', '\u0000', '7', '\u0000', '(', '\u0000', 'A', '\u0000', '\u001e', '\u00c1', '\u001c', '\u0000', '7', '\u0000', '(', '\u0000', '-', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', '-', '\u0000', '\u0019', '\u0000', '7', '\u0000', '7', '\u0000', '7', '\u00c1', '\b', '\u0000', '#', '\u0000', 'U', '\u0000', '\u000f', '\u0000', '_', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u0019', '\u0000', 'K', '\u0000', '-', '\u0000', 'K', '\u0000', '7', '\u0000', 'A', '\u0000', '7', '\u0000', '(', '\u0000', 'A', '\u0000', '\u001e', '\u00c1', '\u001c', '\u0000', '7', '\u0000', '(', '\u0000', '-', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', '-', '\u0000', '\u0019', '\u0000', '7', '\u0000', '7', '\u0000', '7', '\u00c1', '\b', '\u0000', '#', '\u0000', 'U', '\u0000', '7', '\u0000', '_', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u0019', '\u0000', 'K', '\u0000', '-', '\u0000', 'K', '\u0000', '7', '\u0000', 'A', '\u0000', '7', '\u0000', '(', '\u0000', 'A', '\u0000', '\u001e', '\u00c1', '\u001c', '\u0000', '7', '\u0000', '(', '\u0000', '-', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', '-', '\u0000', '\u0019', '\u0000', '7', '\u0000', '7', '\u0000', '7', '\u00c1', '\f', '\u0000', '\u001b', '\u0000', 'U', '\u0000', '%', '\u0000', '_', '\u0000', '/', '\u0000', 'U', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u0019', '\u0000', 'K', '\u0000', '-', '\u0000', 'K', '\u0000', '7', '\u0000', 'A', '\u0000', '7', '\u0000', '(', '\u0000', 'A', '\u0000', '\u001e', '\u00c1', '\u001c', '\u0000', '7', '\u0000', '(', '\u0000', '-', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', '-', '\u0000', '\u0019', '\u0000', '7', '\u0000', '7', '\u0000', '7', '\u00c1', '\u0018', '\u0000', '\u0014', '\u0000', 'U', '\u0000', '\u0019', '\u0000', 'Z', '\u0000', '!', '\u0000', 'Z', '\u0000', '%', '\u0000', 'U', '\u0000', '-', '\u0000', 'U', '\u0000', '2', '\u0000', 'Y', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u0019', '\u0000', 'K', '\u0000', '-', '\u0000', 'K', '\u0000', '7', '\u0000', 'A', '\u0000', '7', '\u0000', '(', '\u0000', 'A', '\u0000', '\u001e', '\u00c1', '\u001c', '\u0000', '7', '\u0000', '(', '\u0000', '-', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', '-', '\u0000', '\u0019', '\u0000', '7', '\u0000', '7', '\u0000', '7', '\u00c1', '\b', '\u0000', '2', '\u0000', 'U', '\u0000', '2', '\u0000', '_', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', 'U', '\u0000', '\u0019', '\u0000', '_', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u0019', '\u0000', 'K', '\u0000', '-', '\u0000', 'K', '\u0000', '7', '\u0000', 'A', '\u0000', '7', '\u0000', '(', '\u0000', 'A', '\u0000', '\u001e', '\u00c1', '\u001c', '\u0000', '7', '\u0000', '(', '\u0000', '-', '\u0000', '\u001e', '\u0000', '\u0019', '\u0000', '\u001e', '\u0000', '\u000f', '\u0000', '(', '\u0000', '\u000f', '\u0000', '-', '\u0000', '\u0019', '\u0000', '7', '\u0000', '7', '\u0000', '7', '\u00c1', '$', '\u0000', '#', '\u0000', 'd', '\u0000', '(', '\u0000', 'd', '\u0000', '-', '\u0000', '_', '\u0000', '-', '\u0000', 'Z', '\u0000', '(', '\u0000', 'U', '\u0000', '#', '\u0000', 'U', '\u0000', '\u001e', '\u0000', 'Z', '\u0000', '\u001e', '\u0000', '_', '\u0000', '#', '\u0000', 'd', '\u00ff', '\u00c1', ',', '\u0000', '(', '\u0000', '7', '\u0000', '(', '\u0000', '(', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '\u001e', '\u0000', '\n', '\u0000', '(', '\u0000', '\n', '\u0000', '-', '\u0000', '\u0014', '\u0000', '7', '\u0000', '(', '\u0000', '7', '\u0000', '(', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '\u0014', '\u0000', 'K', '\u00c1', '\u001c', '\u0000', '(', '\u0000', '2', '\u0000', 'A', '\u0000', '2', '\u0000', 'F', '\u0000', '7', '\u0000', 'F', '\u0000', 'A', '\u0000', '<', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '(', '\u0000', 'A', '\u00c1', '\f', '\u0000', '(', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', 'F', '\u0000', '\u001e', '\u00ff', '\u00c1', ' ', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u00c1', '\u0018', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', '\u0019', '\u0000', '-', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u000f', '\u0000', '#', '\u0000', '\u000f', '\u0000', '\u001e', '\u0000', '\u0014', '\u00ff', '\u00c1', '(', '\u0000', '<', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '7', '\u0000', '7', '\u0000', '2', '\u0000', '\u0014', '\u0000', '2', '\u00c1', '\b', '\u0000', '(', '\u0000', 'U', '\u0000', '\u0014', '\u0000', '_', '\u00ff', '\u00c1', '(', '\u0000', '<', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '7', '\u0000', '7', '\u0000', '2', '\u0000', '\u0014', '\u0000', '2', '\u00c1', '\b', '\u0000', '(', '\u0000', 'U', '\u0000', '<', '\u0000', '_', '\u00ff', '\u00c1', '(', '\u0000', '<', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '7', '\u0000', '7', '\u0000', '2', '\u0000', '\u0014', '\u0000', '2', '\u00c1', '\f', '\u0000', '\u001e', '\u0000', 'U', '\u0000', '(', '\u0000', '_', '\u0000', '2', '\u0000', 'U', '\u00ff', '\u00c1', '(', '\u0000', '<', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '7', '\u0000', '7', '\u0000', '2', '\u0000', '\u0014', '\u0000', '2', '\u00c1', '\b', '\u0000', '\u001b', '\u0000', 'U', '\u0000', '\u001b', '\u0000', '_', '\u00c1', '\b', '\u0000', '4', '\u0000', 'U', '\u0000', '4', '\u0000', '_', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', 'K', '\u00c1', '\b', '\u0000', '(', '\u0000', 'U', '\u0000', '\u0014', '\u0000', '_', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', 'K', '\u00c1', '\b', '\u0000', '(', '\u0000', 'U', '\u0000', '<', '\u0000', '_', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', 'K', '\u00c1', '\f', '\u0000', '\u001e', '\u0000', 'U', '\u0000', '(', '\u0000', '_', '\u0000', '2', '\u0000', 'U', '\u00ff', '\u00c1', '\b', '\u0000', '(', '\u0000', '\u001e', '\u0000', '(', '\u0000', 'K', '\u00c1', '\b', '\u0000', '\u001c', '\u0000', '_', '\u0000', '\u001c', '\u0000', 'U', '\u00c1', '\b', '\u0000', '4', '\u0000', 'U', '\u0000', '4', '\u0000', '_', '\u00ff', '\u00c1', '(', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '\u0014', '\u0000', 'i', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'U', '\u0000', '(', '\u0000', 'i', '\u00ff', '\u00c1', '\f', '\u0000', '\n', '\u0000', 'K', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u0014', '\u0000', '\u001e', '\u00c1', '\u0014', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '\u001e', '\u00c1', '\u0018', '\u0000', '\u0019', '\u0000', 'U', '\u0000', '\u001e', '\u0000', 'Z', '\u0000', '%', '\u0000', 'Z', '\u0000', '+', '\u0000', 'U', '\u0000', '2', '\u0000', 'U', '\u0000', '7', '\u0000', 'Z', '\u00ff', '\u00c1', '$', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '_', '\u0000', '(', '\u0000', 'U', '\u00ff', '\u00c1', '$', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u00c1', '\b', '\u0000', '(', '\u0000', 'U', '\u0000', '<', '\u0000', '_', '\u00ff', '\u00c1', '$', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u00c1', '\f', '\u0000', '\u001e', '\u0000', 'U', '\u0000', '(', '\u0000', '_', '\u0000', '2', '\u0000', 'U', '\u00ff', '\u00c1', '$', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u00c1', '\u0018', '\u0000', '\u0019', '\u0000', 'U', '\u0000', '\u001e', '\u0000', 'Z', '\u0000', '%', '\u0000', 'Z', '\u0000', '+', '\u0000', 'U', '\u0000', '2', '\u0000', 'U', '\u0000', '7', '\u0000', 'Z', '\u00ff', '\u00c1', '$', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u00c1', '\b', '\u0000', '4', '\u0000', '_', '\u0000', '4', '\u0000', 'U', '\u00c1', '\b', '\u0000', '\u001c', '\u0000', 'U', '\u0000', '\u001c', '\u0000', '_', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', 'F', '\u0000', '<', '\u0000', 'F', '\u00c1', '\u0014', '\u0000', '-', '\u0000', '7', '\u0000', '(', '\u0000', '2', '\u0000', '#', '\u0000', '7', '\u0000', '(', '\u0000', '<', '\u0000', '-', '\u0000', '7', '\u00c1', '\u0014', '\u0000', '-', '\u0000', 'U', '\u0000', '(', '\u0000', 'P', '\u0000', '#', '\u0000', 'U', '\u0000', '(', '\u0000', 'Z', '\u0000', '-', '\u0000', 'U', '\u00ff', '\u00c1', '$', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u0000', '\u0014', '\u0000', 'A', '\u0000', '\u001e', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u00c1', '\b', '\u0000', '@', '\u0000', 'P', '\u0000', '\u0011', '\u0000', '\u0019', '\u00ff', '\u00c1', '\u0018', '\u0000', '9', '\u0000', 'K', '\u0000', '9', '\u0000', '(', '\u0000', '/', '\u0000', '\u001e', '\u0000', '\u001b', '\u0000', '\u001e', '\u0000', '\u0011', '\u0000', '(', '\u0000', '\u0011', '\u0000', 'K', '\u00c1', '\b', '\u0000', '9', '\u0000', '(', '\u0000', 'C', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '\u0011', '\u0000', '_', '\u0000', '%', '\u0000', 'V', '\u00ff', '\u00c1', '\u0018', '\u0000', '9', '\u0000', 'K', '\u0000', '9', '\u0000', '(', '\u0000', '/', '\u0000', '\u001e', '\u0000', '\u001b', '\u0000', '\u001e', '\u0000', '\u0011', '\u0000', '(', '\u0000', '\u0011', '\u0000', 'K', '\u00c1', '\b', '\u0000', '9', '\u0000', '(', '\u0000', 'C', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '%', '\u0000', 'U', '\u0000', '9', '\u0000', '_', '\u00ff', '\u00c1', '\u0018', '\u0000', '9', '\u0000', 'K', '\u0000', '9', '\u0000', '(', '\u0000', '/', '\u0000', '\u001e', '\u0000', '\u001b', '\u0000', '\u001e', '\u0000', '\u0011', '\u0000', '(', '\u0000', '\u0011', '\u0000', 'K', '\u00c1', '\b', '\u0000', '9', '\u0000', '(', '\u0000', 'C', '\u0000', '\u001e', '\u00c1', '\f', '\u0000', '/', '\u0000', 'U', '\u0000', '%', '\u0000', '_', '\u0000', '\u001b', '\u0000', 'U', '\u00ff', '\u00c1', '\u0018', '\u0000', '9', '\u0000', 'K', '\u0000', '9', '\u0000', '(', '\u0000', '/', '\u0000', '\u001e', '\u0000', '\u001b', '\u0000', '\u001e', '\u0000', '\u0011', '\u0000', '(', '\u0000', '\u0011', '\u0000', 'K', '\u00c1', '\b', '\u0000', '9', '\u0000', '(', '\u0000', 'C', '\u0000', '\u001e', '\u00c1', '\b', '\u0000', '\u0019', '\u0000', 'U', '\u0000', '\u0019', '\u0000', '_', '\u00c1', '\b', '\u0000', '2', '\u0000', 'U', '\u0000', '2', '\u0000', '_', '\u00ff', '\u00c1', '\f', '\u0000', '\u0014', '\u0000', 'K', '\u0000', '\u0014', '\u0000', '2', '\u0000', '(', '\u0000', '\u001e', '\u00c1', '\f', '\u0000', '<', '\u0000', 'K', '\u0000', '<', '\u0000', '2', '\u0000', '\u0014', '\u0000', '\n', '\u00c1', '\b', '\u0000', '(', '\u0000', 'U', '\u0000', '<', '\u0000', '_', '\u00ff', '\u00c1', '\b', '\u0000', '\u0014', '\u0000', '\n', '\u0000', '\u0014', '\u0000', 'd', '\u00c1', '\u001c', '\u0000', '\u0014', '\u0000', 'K', '\u0000', '2', '\u0000', 'K', '\u0000', '<', '\u0000', 'A', '\u0000', '<', '\u0000', '(', '\u0000', '2', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u001e', '\u0000', '\u0014', '\u0000', '(', '\u00ff', '\u00c1', '\f', '\u0000', '\u0014', '\u0000', 'K', '\u0000', '\u0014', '\u0000', '2', '\u0000', '(', '\u0000', '\u001e', '\u00c1', '\f', '\u0000', '<', '\u0000', 'K', '\u0000', '<', '\u0000', '2', '\u0000', '\u0014', '\u0000', '\n', '\u00c1', '\b', '\u0000', '4', '\u0000', '_', '\u0000', '4', '\u0000', 'U', '\u00c1', '\b', '\u0000', '\u001c', '\u0000', '_', '\u0000', '\u001c', '\u0000', 'U', '\u00ff'}; + public static final int MARKER_WIDTH = 24; + public static final int MARKER_HEIGHT = 32; + static char[] marker_data = new char[]{'\u00c1', '\b', '\u0000', '\u0006', '\u0000', '\n', '\u0000', '\u0012', '\u0000', '\u0016', '\u00c1', '\b', '\u0000', '\u0012', '\u0000', '\n', '\u0000', '\u0006', '\u0000', '\u0016', '\u00ff', '\u00c1', '\b', '\u0000', '\f', '\u0000', '\n', '\u0000', '\f', '\u0000', '\u0016', '\u00c1', '\b', '\u0000', '\u0006', '\u0000', '\u0010', '\u0000', '\u0012', '\u0000', '\u0010', '\u00ff', '\u00c1', '\u0014', '\u0000', '\f', '\u0000', '\n', '\u0000', '\u0006', '\u0000', '\u0010', '\u0000', '\f', '\u0000', '\u0016', '\u0000', '\u0012', '\u0000', '\u0010', '\u0000', '\f', '\u0000', '\n', '\u00ff', '\u00c1', '\u0014', '\u0000', '\u0006', '\u0000', '\n', '\u0000', '\u0006', '\u0000', '\u0016', '\u0000', '\u0012', '\u0000', '\u0016', '\u0000', '\u0012', '\u0000', '\n', '\u0000', '\u0006', '\u0000', '\n', '\u00ff', '\u00c1', '\b', '\u0000', '\u0006', '\u0000', '\n', '\u0000', '\u0012', '\u0000', '\u0016', '\u00c1', '\b', '\u0000', '\u0012', '\u0000', '\n', '\u0000', '\u0006', '\u0000', '\u0016', '\u00c1', '\b', '\u0000', '\f', '\u0000', '\n', '\u0000', '\f', '\u0000', '\u0016', '\u00ff', '\u00c1', '\b', '\u0000', '\u0006', '\u0000', '\n', '\u0000', '\u0012', '\u0000', '\u0016', '\u00c1', '\b', '\u0000', '\u0012', '\u0000', '\n', '\u0000', '\u0006', '\u0000', '\u0016', '\u00c1', '\b', '\u0000', '\f', '\u0000', '\n', '\u0000', '\f', '\u0000', '\u0016', '\u00c1', '\b', '\u0000', '\u0006', '\u0000', '\u0010', '\u0000', '\u0012', '\u0000', '\u0010', '\u00ff', 'h', '\u00c0', '\u00c1', '\u0014', '\u0000', '\f', '\u0000', '\n', '\u0000', '\u0006', '\u0000', '\u0010', '\u0000', '\f', '\u0000', '\u0016', '\u0000', '\u0012', '\u0000', '\u0010', '\u0000', '\f', '\u0000', '\n', '`', '\u0000', '\u00ff', 'h', '\u00c0', '\u00c1', '\u0014', '\u0000', '\u0006', '\u0000', '\n', '\u0000', '\u0006', '\u0000', '\u0016', '\u0000', '\u0012', '\u0000', '\u0016', '\u0000', '\u0012', '\u0000', '\n', '\u0000', '\u0006', '\u0000', '\n', '`', '\u0000', '\u00ff', 'h', '\u00c0', '\u00c1', '\u0014', '\u0000', '\u000b', '\u0000', '\u000f', '\u0000', '\r', '\u0000', '\u000f', '\u0000', '\r', '\u0000', '\u0011', '\u0000', '\u000b', '\u0000', '\u0011', '\u0000', '\u000b', '\u0000', '\u000f', '`', '\u0000', '\u00ff', '\u00c5', '\u0018', '\u0000', '\t', '\u0000', '\u0010', '\u0000', '\t', '\u0000', '\u0013', '\u0000', '\u000f', '\u0000', '\u0013', '\u0000', '\u000f', '\u0000', '\r', '\u0000', '\t', '\u0000', '\r', '\u0000', '\t', '\u0000', '\u0010', '\u00ff'}; +} + diff --git a/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java b/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java index 959d36d..f72b973 100644 --- a/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java +++ b/lib3270j/src/main/java/org/lib3270j/input/InputProcessor.java @@ -31,6 +31,12 @@ public class InputProcessor { this.fsm = fsm; } + private org.lib3270j.graphics.GraphicsPlane graphicsPlane; + + public void setGraphicsPlane(org.lib3270j.graphics.GraphicsPlane gp) { + this.graphicsPlane = gp; + } + public enum OiaStatus { NOT_CONNECTED("OFFLINE"), X_SYSTEM("X SYSTEM"), @@ -160,6 +166,9 @@ public class InputProcessor { if (aidCode == AID_CLEAR) { screen.clear(); screen.markAllChanged(); + if (graphicsPlane != null) { + graphicsPlane.clear(); + } // Send just the AID byte[] data = new byte[] { (byte) aidCode }; sendAidResponse(data); diff --git a/lib3270j/src/main/java/org/lib3270j/protocol/DS3270Constants.java b/lib3270j/src/main/java/org/lib3270j/protocol/DS3270Constants.java index 7ba9565..4b42708 100644 --- a/lib3270j/src/main/java/org/lib3270j/protocol/DS3270Constants.java +++ b/lib3270j/src/main/java/org/lib3270j/protocol/DS3270Constants.java @@ -202,16 +202,25 @@ public final class DS3270Constants { public static final int QR_SUMMARY = 0x80; public static final int QR_USABLE_AREA = 0x81; public static final int QR_IMAGE = 0x82; - public static final int QR_TEXT_PART = 0x83; + public static final int QR_TEXT_PART = 0x83; public static final int QR_ALPHA_PART = 0x84; public static final int QR_CHARSETS = 0x85; public static final int QR_COLOR = 0x86; public static final int QR_HIGHLIGHTING = 0x87; public static final int QR_REPLY_MODES = 0x88; + public static final int QR_SAVE_RESTORE = 0x8c; public static final int QR_DBCS_ASIA = 0x91; public static final int QR_DDM = 0x95; + public static final int QR_TRANSPARENCY = 0x99; public static final int QR_RPQNAMES = 0xa1; public static final int QR_IMP_PART = 0xa6; + public static final int QR_RPQ_NAMES = 0xa8; + public static final int QR_GRAPHICS = 0xb0; // 3270-PC Vector Graphics + public static final int QR_GIMAGE = 0xb1; // Image / Graphics Planes + public static final int QR_AUX_DEV = 0xb2; // Auxiliary Device + public static final int QR_OEM_FMT = 0xb3; // OEM Format + public static final int QR_GCOLOR = 0xb4; // Graphic Color Table + public static final int QR_GSYMBOLS = 0xb6; // Graphic Symbol Sets public static final int QR_NULL = 0xff; // ========== Screen model sizes ========== diff --git a/lib3270j/src/main/java/org/lib3270j/telnet/TelnetConnection.java b/lib3270j/src/main/java/org/lib3270j/telnet/TelnetConnection.java index ded53a8..0d967f7 100644 --- a/lib3270j/src/main/java/org/lib3270j/telnet/TelnetConnection.java +++ b/lib3270j/src/main/java/org/lib3270j/telnet/TelnetConnection.java @@ -28,26 +28,42 @@ public class TelnetConnection { private final TelnetFSM fsm; private final ConnectionConfig config; + private javax.net.ssl.SSLSession sslSession; + public TelnetConnection(ConnectionConfig config, TelnetFSM fsm) { this.config = config; this.fsm = fsm; } + public javax.net.ssl.SSLSession getSslSession() { + return sslSession; + } + /** * Connect to the host. Blocks until connection is established or fails. */ public void connect() throws IOException { if (config.isUseTls()) { - log.info("Connecting with TLS to " + config.getHost() + ":" + config.getPort()); - javax.net.ssl.SSLSocketFactory ssf = (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(); - javax.net.ssl.SSLSocket sslSocket = (javax.net.ssl.SSLSocket) ssf.createSocket(); - sslSocket.setKeepAlive(true); - sslSocket.setOOBInline(true); - sslSocket.setTcpNoDelay(true); - sslSocket.connect(new InetSocketAddress(config.getHost(), config.getPort()), - config.getConnectTimeoutMs()); - sslSocket.startHandshake(); - socket = sslSocket; + log.info("Connecting with TLS to " + config.getHost() + ":" + config.getPort() + + " (verifyCert=" + config.isTlsVerifyCert() + ")"); + try { + javax.net.ssl.SSLContext sslContext = org.lib3270j.tls.TlsTrustManager.createSSLContext(config); + javax.net.ssl.SSLSocketFactory ssf = sslContext.getSocketFactory(); + javax.net.ssl.SSLSocket sslSocket = (javax.net.ssl.SSLSocket) ssf.createSocket(); + sslSocket.setKeepAlive(true); + sslSocket.setTcpNoDelay(true); + sslSocket.connect(new InetSocketAddress(config.getHost(), config.getPort()), + config.getConnectTimeoutMs()); + sslSocket.startHandshake(); + socket = sslSocket; + sslSession = sslSocket.getSession(); + log.info("TLS session active: protocol=" + sslSession.getProtocol() + + " cipher=" + sslSession.getCipherSuite()); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException("TLS setup failure: " + e.getMessage(), e); + } } else { log.info("Connecting to " + config.getHost() + ":" + config.getPort()); socket = new Socket(); diff --git a/lib3270j/src/main/java/org/lib3270j/tls/TlsCertificateVerifier.java b/lib3270j/src/main/java/org/lib3270j/tls/TlsCertificateVerifier.java new file mode 100644 index 0000000..b629490 --- /dev/null +++ b/lib3270j/src/main/java/org/lib3270j/tls/TlsCertificateVerifier.java @@ -0,0 +1,22 @@ +package org.lib3270j.tls; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; + +/** + * Callback interface for validating TLS server certificates. + * Used when standard certificate path validation fails or when custom verification is needed. + */ +@FunctionalInterface +public interface TlsCertificateVerifier { + + /** + * Determine whether to trust an unverified server certificate chain. + * + * @param chain The peer certificate chain presented by the server. + * @param authType The key exchange algorithm (e.g., "RSA", "ECDHE_RSA"). + * @param exception The CertificateException thrown by standard validation (or null if called proactively). + * @return true to trust the certificate and proceed with the connection; false to abort. + */ + boolean shouldTrust(X509Certificate[] chain, String authType, CertificateException exception); +} diff --git a/lib3270j/src/main/java/org/lib3270j/tls/TlsTrustManager.java b/lib3270j/src/main/java/org/lib3270j/tls/TlsTrustManager.java new file mode 100644 index 0000000..2eeafd7 --- /dev/null +++ b/lib3270j/src/main/java/org/lib3270j/tls/TlsTrustManager.java @@ -0,0 +1,114 @@ +package org.lib3270j.tls; + +import org.lib3270j.ConnectionConfig; + +import javax.net.ssl.*; +import java.security.KeyStore; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Custom X509TrustManager that supports: + * 1. Standard certificate verification using the JVM default TrustManager. + * 2. Unverified / trust-all mode when tlsVerifyCert is false. + * 3. Interactive/custom certificate verifier callbacks (e.g. GUI prompts for self-signed certificates). + */ +public class TlsTrustManager implements X509TrustManager { + + private static final Logger log = Logger.getLogger(TlsTrustManager.class.getName()); + + private final ConnectionConfig config; + private X509TrustManager defaultTrustManager; + + public TlsTrustManager(ConnectionConfig config) { + this.config = config; + try { + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init((KeyStore) null); + for (TrustManager tm : tmf.getTrustManagers()) { + if (tm instanceof X509TrustManager) { + this.defaultTrustManager = (X509TrustManager) tm; + break; + } + } + } catch (Exception e) { + log.log(Level.WARNING, "Failed to initialize default TrustManagerFactory", e); + } + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + if (defaultTrustManager != null) { + defaultTrustManager.checkClientTrusted(chain, authType); + } + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + if (config != null && !config.isTlsVerifyCert()) { + log.fine("Certificate verification bypassed (tlsVerifyCert=false)"); + return; + } + + if (chain == null || chain.length == 0) { + CertificateException ex = new CertificateException("null or zero-length certificate chain"); + if (config != null && config.getCertificateVerifier() != null) { + if (config.getCertificateVerifier().shouldTrust(chain, authType, ex)) { + log.info("Server certificate accepted via TlsCertificateVerifier callback"); + return; + } + } + throw ex; + } + + try { + if (defaultTrustManager != null) { + defaultTrustManager.checkServerTrusted(chain, authType); + } else { + throw new CertificateException("No default X509TrustManager available"); + } + } catch (Exception ex) { + CertificateException certEx = (ex instanceof CertificateException) + ? (CertificateException) ex + : new CertificateException("Certificate validation failed: " + ex.getMessage(), ex); + + log.log(Level.FINE, "Standard certificate validation failed: " + certEx.getMessage(), certEx); + + if (config != null && config.getCertificateVerifier() != null) { + boolean accepted = config.getCertificateVerifier().shouldTrust(chain, authType, certEx); + if (accepted) { + log.info("Server certificate accepted via TlsCertificateVerifier callback"); + return; + } else { + log.warning("Server certificate rejected by TlsCertificateVerifier callback"); + throw new CertificateException("Certificate rejected by user/verifier: " + certEx.getMessage(), certEx); + } + } + throw certEx; + } + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + if (defaultTrustManager != null) { + return defaultTrustManager.getAcceptedIssuers(); + } + return new X509Certificate[0]; + } + + /** + * Create an initialized SSLContext configured for the given ConnectionConfig. + */ + public static SSLContext createSSLContext(ConnectionConfig config) throws Exception { + String protocol = (config != null && config.getSslProtocol() != null) + ? config.getSslProtocol() + : "TLS"; + SSLContext sslContext = SSLContext.getInstance(protocol); + TlsTrustManager trustManager = new TlsTrustManager(config); + sslContext.init(null, new TrustManager[] { trustManager }, new SecureRandom()); + return sslContext; + } +} diff --git a/lib3270j/src/test/java/org/lib3270j/datastream/QueryReplyBuilderTest.java b/lib3270j/src/test/java/org/lib3270j/datastream/QueryReplyBuilderTest.java index 478cfcf..222cb79 100644 --- a/lib3270j/src/test/java/org/lib3270j/datastream/QueryReplyBuilderTest.java +++ b/lib3270j/src/test/java/org/lib3270j/datastream/QueryReplyBuilderTest.java @@ -3,6 +3,7 @@ package org.lib3270j.datastream; import org.junit.jupiter.api.Test; import org.lib3270j.TerminalModel; import org.lib3270j.charset.EbcdicTranslator; +import org.lib3270j.graphics.GraphicsMode; import org.lib3270j.screen.ScreenBuffer; import static org.junit.jupiter.api.Assertions.*; import static org.lib3270j.protocol.DS3270Constants.*; @@ -14,11 +15,43 @@ public class QueryReplyBuilderTest { private final QueryReplyBuilder qrBuilder = new QueryReplyBuilder(screen); @Test - public void testBuildAllQueryReplies() { + public void testBuildAllQueryRepliesDefaultNone() { + assertEquals(GraphicsMode.NONE, qrBuilder.getGraphicsMode()); byte[] replies = qrBuilder.buildAllQueryReplies(80, 43, 80 * 43); assertNotNull(replies); assertTrue(replies.length > 0); assertEquals((byte) AID_SF, replies[0]); + + // In GraphicsMode.NONE, Vector Graphics QR 0xB0 must NOT be present + boolean hasB0 = false; + for (int i = 0; i < replies.length - 3; i++) { + if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GRAPHICS) { + hasB0 = true; + break; + } + } + assertFalse(hasB0); + } + + @Test + public void testBuildAllQueryRepliesWithVectorGraphics() { + qrBuilder.setGraphicsMode(GraphicsMode.VECTOR_GRAPHICS); + byte[] replies = qrBuilder.buildAllQueryReplies(80, 43, 80 * 43); + assertNotNull(replies); + + // Vector Graphics QR 0xB0 and 0xB4 must be present + boolean hasB0 = false; + boolean hasB4 = false; + for (int i = 0; i < replies.length - 3; i++) { + if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GRAPHICS) { + hasB0 = true; + } + if ((replies[i] & 0xFF) == 0x81 && (replies[i + 1] & 0xFF) == QR_GCOLOR) { + hasB4 = true; + } + } + assertTrue(hasB0); + assertTrue(hasB4); } @Test @@ -52,4 +85,29 @@ public class QueryReplyBuilderTest { assertEquals(0x81, replies[3] & 0xFF); assertEquals(QR_NULL, replies[4] & 0xFF); } + + @Test + public void testImplicitPartitionModel4() { + byte[] requested = new byte[] { (byte) QR_IMP_PART }; + byte[] replies = qrBuilder.buildQueryReplies(requested, 80, 43, 80 * 43); + + assertNotNull(replies); + assertEquals((byte) AID_SF, replies[0]); + int len = ((replies[1] & 0xFF) << 8) | (replies[2] & 0xFF); + assertEquals(0x81, replies[3] & 0xFF); // SFID_QREPLY + assertEquals(QR_IMP_PART, replies[4] & 0xFF); // 0xA6 + + // Payload starts at index 5: 22 bytes total + // Default size = 80 x 24 + int defCols = ((replies[10] & 0xFF) << 8) | (replies[11] & 0xFF); + int defRows = ((replies[12] & 0xFF) << 8) | (replies[13] & 0xFF); + assertEquals(80, defCols); + assertEquals(24, defRows); + + // Alt size = 80 x 43 + int altCols = ((replies[14] & 0xFF) << 8) | (replies[15] & 0xFF); + int altRows = ((replies[16] & 0xFF) << 8) | (replies[17] & 0xFF); + assertEquals(80, altCols); + assertEquals(43, altRows); + } } diff --git a/lib3270j/src/test/java/org/lib3270j/graphics/GocaDecoderTest.java b/lib3270j/src/test/java/org/lib3270j/graphics/GocaDecoderTest.java new file mode 100644 index 0000000..90a9a1c --- /dev/null +++ b/lib3270j/src/test/java/org/lib3270j/graphics/GocaDecoderTest.java @@ -0,0 +1,237 @@ +package org.lib3270j.graphics; + +import org.junit.jupiter.api.Test; +import java.io.ByteArrayOutputStream; +import static org.junit.jupiter.api.Assertions.*; + +public class GocaDecoderTest { + + @Test + public void testBasicLineAndColorOrders() { + GraphicsPlane plane = new GraphicsPlane(400, 300); + GocaDecoder decoder = new GocaDecoder(plane); + + assertFalse(plane.hasContent()); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // Set Color to Red (GOCA Color 2) - 2 bytes: order, color + out.write(GocaConstants.G_GSCOL); + out.write(0x02); // Red + + // Line from (1000, 1000) to (2000, 2000) - long order: order, len, x1, y1, x2, y2 + out.write(GocaConstants.G_GLINE); + out.write(0x08); // 8 bytes: (x1, y1, x2, y2) + out.write((1000 >> 8) & 0xFF); out.write(1000 & 0xFF); + out.write((1000 >> 8) & 0xFF); out.write(1000 & 0xFF); + out.write((2000 >> 8) & 0xFF); out.write(2000 & 0xFF); + out.write((2000 >> 8) & 0xFF); out.write(2000 & 0xFF); + + byte[] stream = out.toByteArray(); + decoder.decodeStream(stream, 0, stream.length); + + assertTrue(plane.hasContent()); + } + + @Test + public void testAreaAndPatternFill() { + GraphicsPlane plane = new GraphicsPlane(400, 300); + GocaDecoder decoder = new GocaDecoder(plane); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // Set Pattern to Solid - 2 bytes: order, pattern + out.write(GocaConstants.G_GSPT); + out.write(GocaConstants.PT_SOLID); + + // Begin Area - 2 bytes: order, flags + out.write(GocaConstants.G_GBAR); + out.write(0x00); + + // Polyline forming a triangle: (500, 500) -> (1500, 500) -> (1000, 1500) -> (500, 500) + out.write(GocaConstants.G_GLINE); + out.write(0x10); // 4 points * 4 bytes = 16 bytes + out.write((500 >> 8) & 0xFF); out.write(500 & 0xFF); + out.write((500 >> 8) & 0xFF); out.write(500 & 0xFF); + + out.write((1500 >> 8) & 0xFF); out.write(1500 & 0xFF); + out.write((500 >> 8) & 0xFF); out.write(500 & 0xFF); + + out.write((1000 >> 8) & 0xFF); out.write(1000 & 0xFF); + out.write((1500 >> 8) & 0xFF); out.write(1500 & 0xFF); + + out.write((500 >> 8) & 0xFF); out.write(500 & 0xFF); + out.write((500 >> 8) & 0xFF); out.write(500 & 0xFF); + + // End Area - 1 byte + out.write(GocaConstants.G_GEAR); + + byte[] stream = out.toByteArray(); + decoder.decodeStream(stream, 0, stream.length); + + assertTrue(plane.hasContent()); + } + + @Test + public void testMarkerAndVectorText() { + GraphicsPlane plane = new GraphicsPlane(400, 300); + GocaDecoder decoder = new GocaDecoder(plane); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // Set Marker Type to Circle - 2 bytes: order, type + out.write(GocaConstants.G_GSMT); + out.write(GocaConstants.MK_CIRCLE); + + // Draw Marker at (2000, 2000) + out.write(GocaConstants.G_GMRK); + out.write(0x04); + out.write((2000 >> 8) & 0xFF); out.write(2000 & 0xFF); + out.write((2000 >> 8) & 0xFF); out.write(2000 & 0xFF); + + // Draw Vector Stroked Character String: "IBM" (EBCDIC: 0xC9, 0xC2, 0xD4) + out.write(GocaConstants.G_GCHST); + out.write(0x07); // 4 bytes pos + 3 bytes chars + out.write((1000 >> 8) & 0xFF); out.write(1000 & 0xFF); + out.write((1000 >> 8) & 0xFF); out.write(1000 & 0xFF); + out.write(0xC9); out.write(0xC2); out.write(0xD4); + + byte[] stream = out.toByteArray(); + decoder.decodeStream(stream, 0, stream.length); + + assertTrue(plane.hasContent()); + } + + @Test + public void testProcedureOrdersAndErase() { + GraphicsPlane plane = new GraphicsPlane(400, 300); + GocaDecoder decoder = new GocaDecoder(plane); + + // Draw something first + ByteArrayOutputStream out = new ByteArrayOutputStream(); + out.write(GocaConstants.G_GLINE); + out.write(0x08); + out.write(0x00); out.write(0x00); + out.write(0x00); out.write(0x00); + out.write(0x00); out.write(0x64); + out.write(0x00); out.write(0x64); + byte[] drawStream = out.toByteArray(); + decoder.decodeStream(drawStream, 0, drawStream.length); + assertTrue(plane.hasContent()); + + // Process procedure order 0x0A (Erase presentation space) + byte[] procOrders = new byte[] { (byte) GocaConstants.P_ERASE }; + decoder.processProcedureOrders(procOrders, 0, procOrders.length); + assertFalse(plane.hasContent()); + } + + @Test + public void testSegmentAndRelativeLineOrders() { + GraphicsPlane plane = new GraphicsPlane(720, 688); + plane.setScreenDimensions(80, 43); + GocaDecoder decoder = new GocaDecoder(plane); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // 0x70: Begin Segment (14 bytes total) + out.write(GocaConstants.G_BEGSEGM); + out.write(0x0C); + out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x01); // Seg ID 1 + out.write(0x74); out.write(0x70); + out.write(0x00); out.write(0x1E); out.write(0x00); out.write(0x00); out.write(0x00); out.write(0x00); + + // 0x3E: End Prologue + out.write(GocaConstants.G_ENDPROLOGUE); + out.write(0x00); + + // 0x0A: Color (7 = White) + out.write(GocaConstants.G_GSCOL); + out.write(0x07); + + // 0xE1: Relative Line (Absolute Start (300, 200), deltas: (+10, -5), (-10, +5)) + out.write(GocaConstants.G_GRLINE); + out.write(0x08); // 4 bytes start coord + 4 bytes (2 deltas) = 8 bytes + out.write(0x01); out.write(0x2C); // Start X = 300 + out.write(0x00); out.write(0xC8); // Start Y = 200 + out.write(0x0A); out.write(0xFB); // dx = +10, dy = -5 + out.write(0xF6); out.write(0x05); // dx = -10, dy = +5 + + // 0x71: End Segment + out.write(GocaConstants.G_ENDSEGM); + out.write(0x00); + + byte[] stream = out.toByteArray(); + decoder.decodeStream(stream, 0, stream.length); + assertTrue(plane.hasContent()); + } + + @Test + public void testPartialOrderBufferingAcrossStreams() { + GraphicsPlane plane = new GraphicsPlane(400, 300); + GocaDecoder decoder = new GocaDecoder(plane); + + // First packet sends G_GLINE order code and half of its coordinates + ByteArrayOutputStream chunk1 = new ByteArrayOutputStream(); + chunk1.write(GocaConstants.G_GLINE); + chunk1.write(0x08); // 8 bytes: 2 points + chunk1.write(0x00); chunk1.write(0x64); // x1 = 100 + chunk1.write(0x00); chunk1.write(0x64); // y1 = 100 + + byte[] b1 = chunk1.toByteArray(); + decoder.decodeStream(b1, 0, b1.length); + // Order is not complete yet, so no line drawn yet + assertFalse(plane.hasContent()); + + // Second packet sends the rest of the order: x2 = 200, y2 = 200 + ByteArrayOutputStream chunk2 = new ByteArrayOutputStream(); + chunk2.write(0x00); chunk2.write(0xC8); // x2 = 200 + chunk2.write(0x00); chunk2.write(0xC8); // y2 = 200 + + byte[] b2 = chunk2.toByteArray(); + decoder.decodeStream(b2, 0, b2.length); + // Now the combined order executes! + assertTrue(plane.hasContent()); + } + + @Test + public void testSetCurrentPositionAndRelativeLine() { + GraphicsPlane plane = new GraphicsPlane(400, 300); + GocaDecoder decoder = new GocaDecoder(plane); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + // 0x21: Set Current Position to (50, 50) + out.write(GocaConstants.G_GSCP); + out.write(0x04); // len = 4 + out.write(0x00); out.write(0x32); // x = 50 + out.write(0x00); out.write(0x32); // y = 50 + + // 0xA1: Relative Line from Current Position: (+10, +20), (-5, -10) + out.write(GocaConstants.G_GCRLIN); + out.write(0x04); // len = 4 (2 steps) + out.write(0x0A); out.write(0x14); // dx = +10, dy = +20 + out.write(0xFB); out.write(0xF6); // dx = -5, dy = -10 + + byte[] stream = out.toByteArray(); + decoder.decodeStream(stream, 0, stream.length); + + assertTrue(plane.hasContent()); + assertEquals(55, decoder.getCurX()); + assertEquals(60, decoder.getCurY()); + } + + @Test + public void test3179GCoordinateMapping() { + GraphicsPlane plane = new GraphicsPlane(720, 688); + plane.setScreenDimensions(80, 43); + + // Screen center (0, 0) should map to canvas center (360, 343) + assertEquals(360, plane.mapX(0)); + assertEquals(343, plane.mapY(0)); + + // Left edge (-360) should map to 0 + assertEquals(0, plane.mapX(-360)); + // Right edge (+359) should map to 719 + assertEquals(719, plane.mapX(359)); + + // Top edge (+343) should map to 0 + assertEquals(0, plane.mapY(343)); + // Bottom edge (-344) should map to 687 + assertEquals(687, plane.mapY(-344)); + } +} diff --git a/lib3270j/src/test/java/org/lib3270j/graphics/GraphicsModeTest.java b/lib3270j/src/test/java/org/lib3270j/graphics/GraphicsModeTest.java new file mode 100644 index 0000000..e1bcaa2 --- /dev/null +++ b/lib3270j/src/test/java/org/lib3270j/graphics/GraphicsModeTest.java @@ -0,0 +1,57 @@ +package org.lib3270j.graphics; + +import org.junit.jupiter.api.Test; +import org.lib3270j.ConnectionConfig; +import static org.junit.jupiter.api.Assertions.*; + +public class GraphicsModeTest { + + @Test + public void testDefaultsAndParsing() { + assertEquals(GraphicsMode.NONE, GraphicsMode.fromString(null)); + assertEquals(GraphicsMode.NONE, GraphicsMode.fromString("")); + assertEquals(GraphicsMode.NONE, GraphicsMode.fromString("NONE")); + assertEquals(GraphicsMode.NONE, GraphicsMode.fromString("off")); + assertEquals(GraphicsMode.NONE, GraphicsMode.fromString("false")); + + assertEquals(GraphicsMode.PROGRAMMED_SYMBOLS, GraphicsMode.fromString("ps")); + assertEquals(GraphicsMode.PROGRAMMED_SYMBOLS, GraphicsMode.fromString("programmed_symbols")); + assertEquals(GraphicsMode.PROGRAMMED_SYMBOLS, GraphicsMode.fromString("apl")); + + assertEquals(GraphicsMode.VECTOR_GRAPHICS, GraphicsMode.fromString("vector")); + assertEquals(GraphicsMode.VECTOR_GRAPHICS, GraphicsMode.fromString("goca")); + assertEquals(GraphicsMode.VECTOR_GRAPHICS, GraphicsMode.fromString("vector_graphics")); + + assertEquals(GraphicsMode.BOTH, GraphicsMode.fromString("both")); + assertEquals(GraphicsMode.BOTH, GraphicsMode.fromString("all")); + assertEquals(GraphicsMode.BOTH, GraphicsMode.fromString("true")); + assertEquals(GraphicsMode.BOTH, GraphicsMode.fromString("on")); + } + + @Test + public void testFlags() { + assertFalse(GraphicsMode.NONE.isProgrammedSymbolsEnabled()); + assertFalse(GraphicsMode.NONE.isVectorGraphicsEnabled()); + + assertTrue(GraphicsMode.PROGRAMMED_SYMBOLS.isProgrammedSymbolsEnabled()); + assertFalse(GraphicsMode.PROGRAMMED_SYMBOLS.isVectorGraphicsEnabled()); + + assertFalse(GraphicsMode.VECTOR_GRAPHICS.isProgrammedSymbolsEnabled()); + assertTrue(GraphicsMode.VECTOR_GRAPHICS.isVectorGraphicsEnabled()); + + assertTrue(GraphicsMode.BOTH.isProgrammedSymbolsEnabled()); + assertTrue(GraphicsMode.BOTH.isVectorGraphicsEnabled()); + } + + @Test + public void testConnectionConfigDefault() { + ConnectionConfig config = new ConnectionConfig(); + assertEquals(GraphicsMode.NONE, config.getGraphicsMode()); + + config.setGraphicsMode(GraphicsMode.BOTH); + assertEquals(GraphicsMode.BOTH, config.getGraphicsMode()); + + config.setGraphicsMode(null); + assertEquals(GraphicsMode.NONE, config.getGraphicsMode()); + } +} diff --git a/lib3270j/src/test/java/org/lib3270j/graphics/ProgramSymbolManagerTest.java b/lib3270j/src/test/java/org/lib3270j/graphics/ProgramSymbolManagerTest.java new file mode 100644 index 0000000..0214ac7 --- /dev/null +++ b/lib3270j/src/test/java/org/lib3270j/graphics/ProgramSymbolManagerTest.java @@ -0,0 +1,144 @@ +package org.lib3270j.graphics; + +import org.junit.jupiter.api.Test; +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import static org.junit.jupiter.api.Assertions.*; + +public class ProgramSymbolManagerTest { + + @Test + public void testSetAllocationAndLookup() { + ProgramSymbolManager manager = new ProgramSymbolManager(); + // Loadable set can be queried after loadps or clearAll + assertNull(manager.getSymbolSet(0x40)); + assertNull(manager.getSymbol(0x40, 0x41)); + + manager.clearAll(); + assertNull(manager.getSymbolSet(0x40)); + } + + @Test + public void testSinglePlaneLoadPsFormat1() { + ProgramSymbolManager manager = new ProgramSymbolManager(); + + // Build Format 1 Load PS payload for LCID 0x40, code point 0x41 ('A') + // Format 1 header: + // byte 0: flags (0x01: format 1, single plane) + // byte 1: LCID (0x40) + // byte 2: Start code point (0x41) + // byte 3: RWS (0x02: loadable slot 2) + // followed by 18 bytes per symbol for 9x16 cell: + // Byte 0-1: Col 0 for rows 0..15 (0x80 = bit for row 0 col 0) + // Bytes 2-17: Cols 1..8 for rows 0..15 (0xFF = bits for row 0 cols 1..8) + byte[] payload = new byte[4 + 18]; + payload[0] = 0x01; // Format 1 + payload[1] = 0x40; // LCID 0x40 + payload[2] = 0x41; // Code point 0x41 + payload[3] = 0x02; // RWS 2 + + payload[4] = (byte) 0x80; // Row 0 Col 0 bit + payload[5] = (byte) 0x00; // Rows 8..15 Col 0 + for (int r = 0; r < 16; r++) { + payload[6 + r] = (byte) 0xFF; // Cols 1..8 on all rows + } + + manager.loadps(payload); + + ProgramSymbolSet set = manager.getSymbolSet(0x40); + assertNotNull(set); + assertEquals(0x40, set.getLcid()); + assertFalse(set.isTriplePlane()); + + ProgramSymbolSet.SymbolSlot slot = manager.getSymbol(0x40, 0x41); + assertNotNull(slot); + assertEquals(9, slot.getWidth()); + assertEquals(16, slot.getHeight()); + assertFalse(slot.isTriplePlane()); + + // Verify pixel data (Row 0 Col 0 is 1) + byte[] pixels = slot.getPixelData(); + assertNotNull(pixels); + assertEquals(1, pixels[0]); // Row 0 Col 0 is 1 + + // Test rendering into BufferedImage + BufferedImage canvas = new BufferedImage(80, 24, BufferedImage.TYPE_INT_ARGB); + Graphics2D g2 = canvas.createGraphics(); + boolean drawn = manager.drawSymbol(g2, 0x40, 0x41, 0, 0, 10, 20, Color.GREEN, Color.BLACK); + g2.dispose(); + assertTrue(drawn); + } + + @Test + public void testTriplePlaneMultiColorComposite() { + ProgramSymbolManager manager = new ProgramSymbolManager(); + + // Build Format 1 Load PS payload for LCID 0x42, triple plane + // flags = 0x01 (Format 1) + // RWS = 4 (Triple Plane) + byte[] payload = new byte[4 + 18]; + payload[0] = 0x01; // Format 1 + payload[1] = 0x42; // LCID 0x42 + payload[2] = 0x45; // Code point 0x45 + payload[3] = 0x04; // RWS 4 (Triple Plane) + + // Set pixel at row 0 col 0 + payload[4] = (byte) 0x80; // Row 0 Col 0 + payload[5] = (byte) 0x00; + for (int r = 0; r < 16; r++) { + payload[6 + r] = (byte) 0x00; + } + + manager.loadps(payload); + + ProgramSymbolSet set = manager.getSymbolSet(0x42); + assertNotNull(set); + assertTrue(set.isTriplePlane()); + + ProgramSymbolSet.SymbolSlot slot = manager.getSymbol(0x42, 0x45); + assertNotNull(slot); + assertTrue(slot.isTriplePlane()); + + // When loaded with colorPlane=0 (default), all 3 planes are set (val 7 = white composite) + byte[] pixels = slot.getPixelData(); + assertEquals(7, pixels[0] & 0xFF); + + // Render image + BufferedImage img = slot.getImage(Color.WHITE, Color.BLACK); + assertNotNull(img); + + // Pixel (0,0) should be white composite (Red=255, Green=255, Blue=255) + int rgb = img.getRGB(0, 0); + Color pixelColor = new Color(rgb, true); + assertEquals(255, pixelColor.getRed()); + assertEquals(255, pixelColor.getGreen()); + assertEquals(255, pixelColor.getBlue()); + } + + @Test + public void testFormat3Bitstream() { + ProgramSymbolManager manager = new ProgramSymbolManager(); + + // Format 3 payload + // byte 0: 0x03 (Format 3) + // byte 1: LCID (0x44) + // byte 2: code point (0x43) + // byte 3: RWS (2) + // followed by (9 * 16 + 7) / 8 = 18 bytes + byte[] payload = new byte[4 + 18]; + payload[0] = 0x03; // Format 3 + payload[1] = 0x44; // LCID 0x44 + payload[2] = 0x43; // Code point 0x43 + payload[3] = 0x02; // RWS 2 + payload[4] = (byte) 0x80; // First pixel is set + + manager.loadps(payload); + + ProgramSymbolSet set = manager.getSymbolSet(0x44); + assertNotNull(set); + ProgramSymbolSet.SymbolSlot slot = manager.getSymbol(0x44, 0x43); + assertNotNull(slot); + assertEquals(1, slot.getPixelData()[0]); + } +} diff --git a/lib3270j/src/test/java/org/lib3270j/tls/TlsConfigTest.java b/lib3270j/src/test/java/org/lib3270j/tls/TlsConfigTest.java new file mode 100644 index 0000000..4a11bcf --- /dev/null +++ b/lib3270j/src/test/java/org/lib3270j/tls/TlsConfigTest.java @@ -0,0 +1,108 @@ +package org.lib3270j.tls; + +import org.junit.jupiter.api.Test; +import org.lib3270j.ConnectionConfig; +import org.lib3270j.TerminalModel; + +import javax.net.ssl.SSLContext; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.jupiter.api.Assertions.*; + +public class TlsConfigTest { + + @Test + public void testConnectionConfigDefaults() { + ConnectionConfig config = new ConnectionConfig("localhost", 23); + assertFalse(config.isUseTls()); + assertTrue(config.isTlsVerifyCert()); + assertEquals("TLS", config.getSslProtocol()); + assertNull(config.getCertificateVerifier()); + } + + @Test + public void testParseHostStringTlsPrefixes() { + // L: prefix + ConnectionConfig c1 = ConnectionConfig.parseHostString("L:mainframe.example.com", 0, TerminalModel.IBM_3279_4); + assertTrue(c1.isUseTls()); + assertEquals("mainframe.example.com", c1.getHost()); + assertEquals(992, c1.getPort()); + + // ssl: prefix with explicit port + ConnectionConfig c2 = ConnectionConfig.parseHostString("ssl:zos.local:2323", 0, TerminalModel.IBM_3279_2); + assertTrue(c2.isUseTls()); + assertEquals("zos.local", c2.getHost()); + assertEquals(2323, c2.getPort()); + + // y: prefix + ConnectionConfig c3 = ConnectionConfig.parseHostString("y:vm370.net:992", 23, TerminalModel.IBM_3279_4); + assertTrue(c3.isUseTls()); + assertEquals("vm370.net", c3.getHost()); + assertEquals(992, c3.getPort()); + + // Plain host + ConnectionConfig c4 = ConnectionConfig.parseHostString("plain.host.com", 23, TerminalModel.IBM_3279_4); + assertFalse(c4.isUseTls()); + assertEquals("plain.host.com", c4.getHost()); + assertEquals(23, c4.getPort()); + + // IPv6 host with brackets + ConnectionConfig c5 = ConnectionConfig.parseHostString("L:[2001:db8::1]:992", 0, TerminalModel.IBM_3279_4); + assertTrue(c5.isUseTls()); + assertEquals("2001:db8::1", c5.getHost()); + assertEquals(992, c5.getPort()); + } + + @Test + public void testTlsTrustManagerBypass() { + ConnectionConfig config = new ConnectionConfig("untrusted.host", 992); + config.setUseTls(true); + config.setTlsVerifyCert(false); + + TlsTrustManager trustManager = new TlsTrustManager(config); + // With tlsVerifyCert=false, checkServerTrusted must not throw even with null/empty chain + assertDoesNotThrow(() -> trustManager.checkServerTrusted(new X509Certificate[0], "RSA")); + } + + @Test + public void testTlsTrustManagerVerifierCallback() { + ConnectionConfig config = new ConnectionConfig("selfsigned.host", 992); + config.setUseTls(true); + config.setTlsVerifyCert(true); + + AtomicBoolean verifierInvoked = new AtomicBoolean(false); + config.setCertificateVerifier((chain, authType, exception) -> { + verifierInvoked.set(true); + return true; // Accept certificate + }); + + TlsTrustManager trustManager = new TlsTrustManager(config); + // Standard verification will fail on empty cert chain, triggering our callback + assertDoesNotThrow(() -> trustManager.checkServerTrusted(new X509Certificate[0], "RSA")); + assertTrue(verifierInvoked.get(), "Expected verifier callback to be invoked"); + } + + @Test + public void testTlsTrustManagerVerifierRejection() { + ConnectionConfig config = new ConnectionConfig("badcert.host", 992); + config.setUseTls(true); + config.setTlsVerifyCert(true); + + config.setCertificateVerifier((chain, authType, exception) -> false); // Reject certificate + + TlsTrustManager trustManager = new TlsTrustManager(config); + assertThrows(CertificateException.class, () -> trustManager.checkServerTrusted(new X509Certificate[0], "RSA")); + } + + @Test + public void testCreateSSLContext() throws Exception { + ConnectionConfig config = new ConnectionConfig("secure.host", 992); + config.setUseTls(true); + + SSLContext context = TlsTrustManager.createSSLContext(config); + assertNotNull(context); + assertNotNull(context.getSocketFactory()); + } +}