From b38e25910574fe54cf2c60af3b637d9bdd244d35 Mon Sep 17 00:00:00 2001 From: Marcio T Date: Tue, 23 Nov 2021 11:50:09 -0700 Subject: [PATCH] Added initial Macintosh source. --- README.md | 10 +- mac-cpp-source/.finf/Iomega Zip Tester.cpp | Bin 0 -> 32 bytes mac-cpp-source/.finf/Iomega Zip Tester.µ | Bin 0 -> 32 bytes mac-cpp-source/.finf/macos | Bin 0 -> 32 bytes mac-cpp-source/.finf/scsi | Bin 0 -> 32 bytes mac-cpp-source/.finf/tip | Bin 0 -> 32 bytes mac-cpp-source/.rsrc/Iomega Zip Tester.cpp | Bin 0 -> 410 bytes mac-cpp-source/.rsrc/Iomega Zip Tester.µ | Bin 0 -> 10399 bytes mac-cpp-source/Iomega Zip Tester.cpp | 176 ++++++ mac-cpp-source/Iomega Zip Tester.µ | Bin 0 -> 92262 bytes mac-cpp-source/macos/.finf/mac_vol.cpp | Bin 0 -> 32 bytes mac-cpp-source/macos/.finf/mac_vol.h | Bin 0 -> 32 bytes mac-cpp-source/macos/.rsrc/mac_vol.cpp | Bin 0 -> 410 bytes mac-cpp-source/macos/.rsrc/mac_vol.h | Bin 0 -> 410 bytes mac-cpp-source/macos/mac_vol.cpp | 38 ++ mac-cpp-source/macos/mac_vol.h | 2 + mac-cpp-source/scsi/.finf/iomega_cmds.cpp | Bin 0 -> 32 bytes mac-cpp-source/scsi/.finf/iomega_cmds.h | Bin 0 -> 32 bytes mac-cpp-source/scsi/.finf/mac_scsi.cpp | Bin 0 -> 32 bytes mac-cpp-source/scsi/.finf/mac_scsi.h | Bin 0 -> 32 bytes mac-cpp-source/scsi/.rsrc/iomega_cmds.cpp | Bin 0 -> 410 bytes mac-cpp-source/scsi/.rsrc/iomega_cmds.h | Bin 0 -> 410 bytes mac-cpp-source/scsi/.rsrc/mac_scsi.cpp | Bin 0 -> 410 bytes mac-cpp-source/scsi/.rsrc/mac_scsi.h | Bin 0 -> 410 bytes mac-cpp-source/scsi/iomega_cmds.cpp | 50 ++ mac-cpp-source/scsi/iomega_cmds.h | 8 + mac-cpp-source/scsi/mac_scsi.cpp | 116 ++++ mac-cpp-source/scsi/mac_scsi.h | 87 +++ mac-cpp-source/tip/.finf/tip.cpp | Bin 0 -> 32 bytes mac-cpp-source/tip/.finf/tip.h | Bin 0 -> 32 bytes mac-cpp-source/tip/.finf/tip_aspi.cpp | Bin 0 -> 32 bytes mac-cpp-source/tip/.finf/tip_main.cpp | Bin 0 -> 32 bytes mac-cpp-source/tip/.finf/tip_text.cpp | Bin 0 -> 32 bytes mac-cpp-source/tip/.rsrc/tip.cpp | Bin 0 -> 410 bytes mac-cpp-source/tip/.rsrc/tip.h | Bin 0 -> 410 bytes mac-cpp-source/tip/.rsrc/tip_aspi.cpp | Bin 0 -> 410 bytes mac-cpp-source/tip/.rsrc/tip_main.cpp | Bin 0 -> 410 bytes mac-cpp-source/tip/.rsrc/tip_text.cpp | Bin 0 -> 410 bytes mac-cpp-source/tip/tip.cpp | 383 ++++++++++++ mac-cpp-source/tip/tip.h | 212 +++++++ mac-cpp-source/tip/tip_aspi.cpp | 684 +++++++++++++++++++++ mac-cpp-source/tip/tip_main.cpp | 370 +++++++++++ mac-cpp-source/tip/tip_text.cpp | 145 +++++ 43 files changed, 2278 insertions(+), 3 deletions(-) create mode 100644 mac-cpp-source/.finf/Iomega Zip Tester.cpp create mode 100644 mac-cpp-source/.finf/Iomega Zip Tester.µ create mode 100644 mac-cpp-source/.finf/macos create mode 100644 mac-cpp-source/.finf/scsi create mode 100644 mac-cpp-source/.finf/tip create mode 100644 mac-cpp-source/.rsrc/Iomega Zip Tester.cpp create mode 100644 mac-cpp-source/.rsrc/Iomega Zip Tester.µ create mode 100644 mac-cpp-source/Iomega Zip Tester.cpp create mode 100644 mac-cpp-source/Iomega Zip Tester.µ create mode 100644 mac-cpp-source/macos/.finf/mac_vol.cpp create mode 100644 mac-cpp-source/macos/.finf/mac_vol.h create mode 100644 mac-cpp-source/macos/.rsrc/mac_vol.cpp create mode 100644 mac-cpp-source/macos/.rsrc/mac_vol.h create mode 100644 mac-cpp-source/macos/mac_vol.cpp create mode 100644 mac-cpp-source/macos/mac_vol.h create mode 100644 mac-cpp-source/scsi/.finf/iomega_cmds.cpp create mode 100644 mac-cpp-source/scsi/.finf/iomega_cmds.h create mode 100644 mac-cpp-source/scsi/.finf/mac_scsi.cpp create mode 100644 mac-cpp-source/scsi/.finf/mac_scsi.h create mode 100644 mac-cpp-source/scsi/.rsrc/iomega_cmds.cpp create mode 100644 mac-cpp-source/scsi/.rsrc/iomega_cmds.h create mode 100644 mac-cpp-source/scsi/.rsrc/mac_scsi.cpp create mode 100644 mac-cpp-source/scsi/.rsrc/mac_scsi.h create mode 100644 mac-cpp-source/scsi/iomega_cmds.cpp create mode 100644 mac-cpp-source/scsi/iomega_cmds.h create mode 100644 mac-cpp-source/scsi/mac_scsi.cpp create mode 100644 mac-cpp-source/scsi/mac_scsi.h create mode 100644 mac-cpp-source/tip/.finf/tip.cpp create mode 100644 mac-cpp-source/tip/.finf/tip.h create mode 100644 mac-cpp-source/tip/.finf/tip_aspi.cpp create mode 100644 mac-cpp-source/tip/.finf/tip_main.cpp create mode 100644 mac-cpp-source/tip/.finf/tip_text.cpp create mode 100644 mac-cpp-source/tip/.rsrc/tip.cpp create mode 100644 mac-cpp-source/tip/.rsrc/tip.h create mode 100644 mac-cpp-source/tip/.rsrc/tip_aspi.cpp create mode 100644 mac-cpp-source/tip/.rsrc/tip_main.cpp create mode 100644 mac-cpp-source/tip/.rsrc/tip_text.cpp create mode 100644 mac-cpp-source/tip/tip.cpp create mode 100644 mac-cpp-source/tip/tip.h create mode 100644 mac-cpp-source/tip/tip_aspi.cpp create mode 100644 mac-cpp-source/tip/tip_main.cpp create mode 100644 mac-cpp-source/tip/tip_text.cpp diff --git a/README.md b/README.md index a6f76b7..79fe958 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,6 @@ This tool is meant for Macintosh computers with a SCSI port, which range from the Macintosh Plus, released in 1986, through the "Beige" Power Macintosh G3, released in 1997. -If you have consulting work, particular contract work pertaining -to vintage computers, please hit me via my GitHub account! - Disclaimer ---------- @@ -30,6 +27,12 @@ Right now the port is in early testing, so I am not releasing any compiled binaries. There is always a risk of data loss with a tool like this, so please contact me directly if you want to beta test. +Got projects? +------------- + +If you have consulting work, particular contract work pertaining +to vintage computers or THREE.js, please hit me via my [GitHub account]! + About the code -------------- @@ -59,6 +62,7 @@ his code in this repository with his permission. +[GitHub account]: https://github.com/marciot [mac-screenshot1]: https://github.com/marciot/mac-tip/raw/main/images/mac-tip1.png "TIP" [win-screenshot1]: https://github.com/marciot/mac-tip/raw/main/images/win-tip1.gif "TIP" [win-screenshot2]: https://github.com/marciot/mac-tip/raw/main/images/win-tip2.gif "TIP" diff --git a/mac-cpp-source/.finf/Iomega Zip Tester.cpp b/mac-cpp-source/.finf/Iomega Zip Tester.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4da4961f9e4da664dd64ee824aa2876ceb63dc2c GIT binary patch literal 32 acmWG>jRv-`kCEa3pZ^~YJ21cj0E?stzW@LL literal 0 HcmV?d00001 diff --git a/mac-cpp-source/.finf/scsi b/mac-cpp-source/.finf/scsi new file mode 100644 index 0000000000000000000000000000000000000000..c1566d8272d3354bf5d7f4c9b1521795b902cd5c GIT binary patch literal 32 gcmZRSW3Xa8$f(QAz>vxy%gFHm&;NIa9T?yM0CLm@y8r+H literal 0 HcmV?d00001 diff --git a/mac-cpp-source/.finf/tip b/mac-cpp-source/.finf/tip new file mode 100644 index 0000000000000000000000000000000000000000..3973766048214a9fa44540283e50f610fe919228 GIT binary patch literal 32 gcmZQ9WAI}VWL(S4z+lWM%E<8l&;JjH9T?yM0Ch+P!2kdN literal 0 HcmV?d00001 diff --git a/mac-cpp-source/.rsrc/Iomega Zip Tester.cpp b/mac-cpp-source/.rsrc/Iomega Zip Tester.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eb2b58db75e0035b1eb714af72e9f77d6b58e6ec GIT binary patch literal 410 zcmZut!AiqG5S=7FD5wXiH<9^*;K7p@p|Qn+g`$fQZ`)+sY>An$*($w=enIbo_yvl; zAc%kACkXyQog^*w;K0m#GYd2CEr0_6j^I*eCze*BO%&#a#wxOSt8G#ZCVBa`)1_-Q zl6a+a6soYw_)(s_;DTshiXdEKw_WaqhqBahzrdGI+yH0>eDx9gsgi}lfzjCr1Bz8B zjnQP#Qgw7_D6{^BNOZAMBnym;VlgoerYJL{G*$+0lqocEH~hQF-#jLq(u9o^Y=p4( zf!ZD&ibS%g6Ikz2CL>x1{104cz?-x0K6$exe!Y+H0M^1fx6;aQgTIqW$ya~sy*__5 h4|uc-9dLU6^E2k#y|C3{-1ENYbH*yXpNgNB%`cmzVoCr2 literal 0 HcmV?d00001 diff --git a/mac-cpp-source/.rsrc/Iomega Zip Tester.µ b/mac-cpp-source/.rsrc/Iomega Zip Tester.µ new file mode 100644 index 0000000000000000000000000000000000000000..651e6f01ea49a44c77ffc627aea6a8826280d605 GIT binary patch literal 10399 zcmeHNe{2)i9sjv@VV+Qcg57 zk|FB(Q6-;KL?x@=vKQ$ilK7BX5c?I~P_*b-0R%`UQ@x3Sy`3`@;c{@wF`4`q;Pk@} z<$z@l18jOx5r?yqJR;`DjI4?`pj+zJJ&la2i}-J36fvzFmd3IM=K6?{FNg(A&C#9r z_VdazrYK9fT;33e5iS3yk{0ua58EJCyJ1C^#&ktg4T49Z1XzH-0hIL@m_IOEW-QMw zZL!e=2>Afo0ztbKH(re%jTMr%R+OuC4}yU34Oo2~fBoF48j{5q5XFyn-Y8Bjw|`So zlEt1raohG0F>zO-tu1=iskibtWdZxg&iAw9Y_afdVwO?__3IPT?A3-Jaz%TwW(2P& zEMQP9I_-)@Mv01Rt>%CjoXA=?@De*WzF@pOfS~RaQ#@*aRW=E5R(~6)ov5w{*Y) z(vhpUPwZB6BZ^kh4gz+IR4P%WMa!;Ks=KmVz2zb7u&YqW*4wYLJ>evi-McIKmE~}^ zIxOxTE#x&rMAa&*c{!Qt?sBb|#q@jU?d{ygMIDx3#*5-la7pEaAdkRzsPQgPQ@QZ_b6}*7_<0#`^I9{?kb=G)goT6)9 zOP(g|W#M>BvlPcYCF!!1tv>LC6NnZh-Bl8cSqF~nrCBY#Ok1yDv><0ZW};slA^Nc$ zuuRK_UgcY5441-zL{H6qqlp@IdGA2!ygcRtUl(f#TWh-JxwiZO5{{n94%ccMPIcDM z3UQ3FdBGuB0#US5EulQ>Q&?Bi2Y%)~+_vreTy0RVhpUP+Padvk_XDDa||%33&|wx@KL=M#8JcOaazb$ZHLI6gh4 zJB(?| z(Oi->8TUJrJ&&&uwaSxA9se)y;Z>XL_hhL1W=0M&37C#!`kY`{gyWMYpC zc-<^ND`yc(!y`x@==RFVdY!746HZprbup12EvVVbaE*!h77hk0J?`bF%Ep<~)Afum z^;$QzrEzfZ)akhbYuVYvTajK3QNzp3wFTlWf+7$8F^uAcfUy`1V)@YY3oy9lszn1# z0u08@KTqRzEpEomzr1tDA&mGfg9`}pUCiM@Bv^NR;`!gf;GKKm&<8_czIz_b$H(L5 zk3yVhejk?uAzAohQ+$Hj$0vULE*$*Rv2rX#HJ>%x;8%X zI@V5F_^{$(xPVtlHo|-S3JmVNjQP*8s|i#5p8tfCT+Ch-@X%n7p!SHwcZ;xFJGi zo+7(0Ib^k*#s2mduaj*YDhn1K>xf;i*002W4|Mnlrm*cP)CuKSg*!w9+(*0PwH7eMPrC+-nBKt4n zbsi->=m*x3KE zR-pbU5A~0%w`>D|`cpigY_Ph*1{iGBBu!QH82lI8fbIC|`}1dm&&M{UwER&;HpD(d zGE{tZrG0aA?BhJqSuJQ^fyvF+%jSXH{CDJA^FUtt7S^9Hn+J09<+6EzYsYgQ$gARf zjQBjlag>{nm(7EX>_nLj;&A!+3DZ&-@(Ck7nMit)tRKRIWicHVIP zScHGoF=tKw=K#*p^L4H7D`xLrI73XdLLA`0B$N zi3~II9aqDd=U?V$v0K>*5DE|Sc*UB6hQWz7u(7YdcLR=VS*S<-2{a)D`aS^huW%b? zVWRJQ9qz*HAkp7h2sQeM@qk=)R*cj@iG6{@T6p zJ-p`+h;Dxxo<^FVE5DwG7m$9R_(NCV6;y5`(OTEfY@tE9~daYRit_QJD-9N0oHN7ww+_B>$+R1jq!W7 z6t(_<=#FW&8elzdvoFY6k-kX$$Br>P&)t5F=!5@Y+W|J5CK{&b*}C-{kqy^5&E;I? zoHx++I-Wt$2_I2qI|{tqDx@?;w!=wF9S%RZEVI7^aDq)vc|g)~YVL?0=F!DcENTe` z +#include +#include +#include +#include + +#include "tip.h" +#include "mac_vol.h" +#include "mac_scsi.h" +#include "iomega_cmds.h" + +bool process_command(); +void printn( unsigned char *c, int n ); +void print_help(); +void scan_bus(); +void dev_info( int id ); +void show_spares( int id ); + +void main() { + SIOUXSettings.autocloseonquit = TRUE; + SIOUXSettings.asktosaveonclose = FALSE; + SIOUXSettings.standalone = FALSE; + + printf( "SCSI and Iomega Zip/Jaz Toolkit V0.1\n" ); + printf( "(c) 2021 Marcio Teixeira\n\n" ); + printf( "TIP based on source code provided by Steve Gibson (http://grc.com)\n" ); + + SIOUXSetTitle("\pCommand Console"); + + print_help(); + + do { + EventRecord event; + if (WaitNextEvent(everyEvent, &event, GetCaretTime(), NULL)) + SIOUXHandleOneEvent(&event); + + } while (process_command()); + + printf( "Goodbye.\n" ); +} + +bool process_command() { + short int arg_val = 0; + char cmd[80]; + printf( "\n> " ); + gets( cmd ); + printf("\n"); + + char *arg_str = strchr(cmd, ' '); + if(arg_str) arg_val = atoi(arg_str); + + switch( tolower(cmd[0]) ) { + case 'h': print_help(); break; + case 'l': scan_bus(); break; + case 's': iomega_spin_up_cartridge(arg_val); break; + case 'r': scsi_reset(); break; + case 'e': iomega_eject_cartridge(arg_val); break; + case 'i': dev_info(arg_val); break; + case 'v': mac_list_volumes(); break; + case 'u': mac_unmount(arg_val); break; + case 't': run_tip(); break; + case 'q': return false; + } + return true; +} + +void print_help() { + printf( + "\nGeneral commands:\n" + " help : print this help\n" + " quit : quit the program\n" + + "\nMacintosh commands (please unmount Zip prior to testing):\n" + " volumes : list Mac volumes\n" + " unmount [n] : unmount a volume\n" + + "\nGeneral SCSI operations:\n" + " reset : reset the SCSI bus\n" + " list : list devices on the SCSI bus\n" + " info [n] : display SCSI inquiry\n" + + "\nIomega device operations on SCSI device:\n" + " spin [n] : spin up a cartridge\n" + " eject [n] : eject cartridge\n" + " tip [n] : run Steve Gibson's TIP 2.1\n" + ); +} + +void scan_bus() { + short err, id; + scsi_inq_reply reply; + + for( id=0; id<8; id++ ) { + err = scsi_inquiry( id, 0, &reply); + if( err != 0 ) { + printf( " %hd: (Not installed)\n", id ); + } else { + printf( " %hd: ", id ); + printn( reply.prod, 16 ); + printf( " " ); + printn( reply.vend, 8 ); + printf( " " ); + printn( reply.rvsn, 4 ); + putchar( '\n' ); + } + } +} + +void dev_info( int id ) { + short err, lun; + scsi_inq_reply reply; + + for( lun = 0; lun < 8; lun++ ) { + err = scsi_inquiry( id, lun, &reply); + if( err ) { + printf( "Device not installed\n" ); + return; + } + + printf( " LUN %hd: ", lun ); + switch( (reply.inf1 & 0xE0) >> 5 ) { + case 0x00: printf( "supported and connected\n" ); break; + case 0x01: printf( "not connected\n" ); continue; + case 0x03: printf( "not supported\n" ); continue; + } + + printf( " Device class (%02X): ", reply.inf1 & 0x1F ); + switch( reply.inf1 & 0x1F ) { + case 0x00: printf( "Disk drive\n" ); break; + case 0x01: printf( "Tape drive\n" ); break; + case 0x02: printf( "Printer\n" ); break; + case 0x03: printf( "Processor device\n" ); break; + case 0x04: printf( "WORM drive\n" ); break; + case 0x05: printf( "CD-ROM drive\n" ); break; + case 0x06: printf( "Scanner\n" ); break; + case 0x07: printf( "Optical disk\n" ); break; + case 0x08: printf( "Media changer\n" ); break; + case 0x09: printf( "Communication device\n" ); break; + case 0x1F: printf( "Unknown device\n" ); break; + default: printf( "Reserved\n" ); + } + + printf( " ANSI version (%02X): ", reply.vers & 0x07 ); + switch( reply.vers & 0x07 ) { + case 0x00: printf( "SCSI-1\n" ); break; + case 0x01: printf( "SCSI-1 w/ CCS\n" ); break; + case 0x02: printf( "SCSI-2\n" ); break; + default: printf( "???\n" ); + } + printf( " ISO version (%02X)\n", reply.vers & 0xC0 >> 6 ); + printf( " ECMA version (%02X)\n", reply.vers & 0x78 >> 3 ); + + printf( " Flags: " ); + if( reply.flg1 & 0x80 ) printf( "rmb " ); + if( reply.flg2 & 0x80 ) printf( "rel " ); + if( reply.flg2 & 0x40 ) printf( "w32 " ); + if( reply.flg2 & 0x20 ) printf( "w16 " ); + if( reply.flg2 & 0x10 ) printf( "syn " ); + if( reply.flg2 & 0x08 ) printf( "lnk " ); + if( reply.flg2 & 0x04 ) printf( "??? " ); + if( reply.flg2 & 0x02 ) printf( "que " ); + if( reply.flg2 & 0x01 ) printf( "sfR " ); + if( reply.inf2 & 0x80 ) printf( "aen " ); + if( reply.inf2 & 0x40 ) printf( "tio " ); + printf( "\n" ); + } +} + +void printn( unsigned char *c, int n ) { + while( n-- ) { + putchar( isprint(*c) ? *c : '.' ); + c++; + } +} + + diff --git a/mac-cpp-source/Iomega Zip Tester.µ b/mac-cpp-source/Iomega Zip Tester.µ new file mode 100644 index 0000000000000000000000000000000000000000..f13a045048bfbf83fe54c08d458242fe3c7fee28 GIT binary patch literal 92262 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 literal 0 HcmV?d00001 diff --git a/mac-cpp-source/macos/.rsrc/mac_vol.h b/mac-cpp-source/macos/.rsrc/mac_vol.h new file mode 100644 index 0000000000000000000000000000000000000000..c77a27445913c75ca9b3ea0213a8ac3dc5cde90c GIT binary patch literal 410 zcmZQzU}RumU;2Vi1gVW?r?WBSaL3#0Gu zy3P$$Z2-mKa0IGi1TlaJDNLD{M9YYL01C+f! +#include +#include "mac_vol.h" + +void mac_list_volumes() { + HParamBlockRec paramBlock; + Str255 volName; + + paramBlock.volumeParam.ioCompletion = 0; + paramBlock.volumeParam.ioNamePtr = volName; + paramBlock.volumeParam.ioVRefNum = 0; + paramBlock.volumeParam.ioVolIndex = 0; + for (;;) { + OSErr err = PBHGetVInfo(¶mBlock, false); + if (err == nsvErr) break; + printf(" %d: %#s\n", paramBlock.volumeParam.ioVolIndex, paramBlock.volumeParam.ioNamePtr); + paramBlock.volumeParam.ioVolIndex++; + } +} + +void mac_unmount(int id) { + HParamBlockRec paramBlock; + paramBlock.volumeParam.ioCompletion = 0; + paramBlock.volumeParam.ioNamePtr = 0; + paramBlock.volumeParam.ioVRefNum = 0; + paramBlock.volumeParam.ioVolIndex = id; + OSErr err = PBHGetVInfo(¶mBlock, false); + if (err == nsvErr) { + printf("No such volume\n"); + return; + } + err = UnmountVol(0, paramBlock.volumeParam.ioVRefNum); + switch (err) { + case noErr: printf("Okay\n"); break; + case fBsyErr: printf("One or more files are open\n"); break; + default: printf("Failed %d\n", err); + } +} \ No newline at end of file diff --git a/mac-cpp-source/macos/mac_vol.h b/mac-cpp-source/macos/mac_vol.h new file mode 100644 index 0000000..a861572 --- /dev/null +++ b/mac-cpp-source/macos/mac_vol.h @@ -0,0 +1,2 @@ +void mac_list_volumes(); +void mac_unmount(int id); \ No newline at end of file diff --git a/mac-cpp-source/scsi/.finf/iomega_cmds.cpp b/mac-cpp-source/scsi/.finf/iomega_cmds.cpp new file mode 100644 index 0000000000000000000000000000000000000000..204eae6a44c1cd67bee562d495f1e2b2ef9242ab GIT binary patch literal 32 XcmWG>jRjRjRjRO5PG%m=VxZ$Z7&v|N^AeNu(Hx4*Wny4q z$YS7R`oNR{qwgND{s~kf3dP`X1gc^LF;K&lxg{n?WD-!F41*g3qi;ZP5Rfh88}8%; Vq~)03{{Ii8!GQS#nC%1N001HVQvd(} literal 0 HcmV?d00001 diff --git a/mac-cpp-source/scsi/.rsrc/iomega_cmds.h b/mac-cpp-source/scsi/.rsrc/iomega_cmds.h new file mode 100644 index 0000000000000000000000000000000000000000..f2af8c74d679fbcced71cda9a6c78e61cf8b6df6 GIT binary patch literal 410 zcmZ{gyGjE=7=_R5rm>KKT8h1m&6IYL1O*pF1F;Y!Bu*k2x9|ya>D73hsCfdxSJ(&G z+A4X3fNx;V859)phuQDU%s2n;4qyP7DjymZ=e$SPg+foT7JZSs1Lqb`)6Pk&e%$CZ zFE-kdj+GCqTeY3C`k?!FuH~OK&5Ey_zw@mlb!Gp{&>gt4_LGHcG0!m+`8_uxom-Lq zoyZ>d*5*0WgGm2Tr0+%gBa!}-NdMVd@9DpYTp4o@*v3MVp4S^G^M4hgfSFm2viR+r y(`<7>ef?{Md!%I)e&1<4I67My(pyCZCfPgKXI@Hbr4qRo&%(bj0`W)4yUKI<{%Q>X literal 0 HcmV?d00001 diff --git a/mac-cpp-source/scsi/.rsrc/mac_scsi.cpp b/mac-cpp-source/scsi/.rsrc/mac_scsi.cpp new file mode 100644 index 0000000000000000000000000000000000000000..67b81ebb2e50e9510158706213d05f6075f3a4c5 GIT binary patch literal 410 zcmZQzU}RumUGfN1-e~ zGetqGz^ODXEwu>dfRNPUl8}s4m(1d94VYL!QEEYAQEEuOQ)+r<9!N!IUOHUXsWi91 zwWuh+$T`0>ucTN5><_3wN~)%HgkC^^vz||864W6u%7cN^H$N{iIUgo~g=S)4VOYjs z%jC#(0Y=|F6f%*4flVBOSyVvb2vo%g3Ih-u5vI)R<}MNW02Gp8aARQf4G0bbvW0xZ Xot%KQ9P``%|A90ZFn<8EeLx%lI{;gn literal 0 HcmV?d00001 diff --git a/mac-cpp-source/scsi/.rsrc/mac_scsi.h b/mac-cpp-source/scsi/.rsrc/mac_scsi.h new file mode 100644 index 0000000000000000000000000000000000000000..c3c6d3cfd2f34a99de772bcca21495168505aa4f GIT binary patch literal 410 zcmZQzU}RumUS L`2(2k1L6Px*NIOK literal 0 HcmV?d00001 diff --git a/mac-cpp-source/scsi/iomega_cmds.cpp b/mac-cpp-source/scsi/iomega_cmds.cpp new file mode 100644 index 0000000..a7ff3c0 --- /dev/null +++ b/mac-cpp-source/scsi/iomega_cmds.cpp @@ -0,0 +1,50 @@ +#include "mac_scsi.h" +#include "iomega_cmds.h" + +// Iomega commands + +OSErr iomega_spin_up_cartridge( int id ) { + // issue an Asynchronous START command to induce spinup + char cmd[6] = { + SCSI_Cmd_StartStopUnit, + 1, // set the IMMED bit for offline + 0, + 0, + 1, // start the disk spinning + 0 + }; + return scsi_cmd(id, cmd, sizeof(cmd), 0, 0, 0, 0); +} + +OSErr iomega_spin_down_and_eject( int id ) { + // issue an Asynchronous STOP command to induce spindown and ejection + char cmd[6] = { + SCSI_Cmd_StartStopUnit, + 1, // set the IMMED bit for offline + 0, + 0, + 2, // eject a Jaz disk after stopping + 0 + }; + return scsi_cmd(id, cmd, sizeof(cmd), 0, 0, 0, 0); +} + +OSErr iomega_set_prevent_removal( int id, bool lock) { + OSErr err; + char cmd[6] = { + SCSI_Cmd_PreventAllow, + 0, + 0, + 0, + lock ? 1 : 0, + 0 + }; + return scsi_cmd(id, cmd, sizeof(cmd), 0, 0, 0, 0); +} + +OSErr iomega_eject_cartridge( int id ) { + OSErr err; + err = iomega_set_prevent_removal(id, false); + if (err != noErr) return err; + return iomega_spin_down_and_eject(id); +} \ No newline at end of file diff --git a/mac-cpp-source/scsi/iomega_cmds.h b/mac-cpp-source/scsi/iomega_cmds.h new file mode 100644 index 0000000..8083bb9 --- /dev/null +++ b/mac-cpp-source/scsi/iomega_cmds.h @@ -0,0 +1,8 @@ +#include + +typedef Boolean bool; + +OSErr iomega_spin_up_cartridge(int id); +OSErr iomega_spin_down_and_eject(int id); +OSErr iomega_set_prevent_removal(int id, bool lock); +OSErr iomega_eject_cartridge(int id); \ No newline at end of file diff --git a/mac-cpp-source/scsi/mac_scsi.cpp b/mac-cpp-source/scsi/mac_scsi.cpp new file mode 100644 index 0000000..6771e68 --- /dev/null +++ b/mac-cpp-source/scsi/mac_scsi.cpp @@ -0,0 +1,116 @@ +#include +#include "mac_scsi.h" + +#include +#include +#include + +#define READ_TIMEOUT 300 /* 300 ticks = 5 seconds */ + +OSErr scsi_reset() { + return SCSIReset(); +} + +OSErr scsi_cmd(int id, void *cmd, size_t clen, void *buff, size_t siz, size_t cnt, int flags, char *status) { + SCSIInstr TIB[3]; /* Transfer instruction block */ + OSErr err; + + /* Set up the TIB (used by Macintosh SCSI Manager) */ + + if( cnt != 0 ) { + /* Transfer cnt continguous blocks of size siz, one at a time. */ + /* Use this sort of transfer when doing blind transfers to a */ + /* disk drive, in order to force the SCSI to poll the bus */ + /* between each block [Inside Macintosh: Devices, 3-22] */ + + TIB[0].scOpcode = scInc; + TIB[0].scParam1 = (long) buff; + TIB[0].scParam2 = siz; + TIB[1].scOpcode = scLoop; + TIB[1].scParam1 = -10; + TIB[1].scParam2 = cnt; + TIB[2].scOpcode = scStop; + TIB[2].scParam1 = 0; + TIB[2].scParam2 = 0; + } else { + /* Transfer siz bytes in one fell swoop. */ + + TIB[0].scOpcode = scInc; + TIB[0].scParam1 = (long) buff; + TIB[0].scParam2 = siz; + TIB[1].scOpcode = scStop; + TIB[1].scParam1 = 0; + TIB[1].scParam2 = 0; + } + + /* Arbitrate for the bus and select the device. */ + err = SCSIGet(); + if (err != noErr) {printf("SCSIGet Error: %d\n", err); return err;} + + err = SCSISelect(id); + if (err != noErr) {/*printf("SCSISelect Error: %d\n", err);*/ return err;} + + /* Send the command to the SCSI device and perform the requested I/O */ + err = SCSICmd( (Ptr) cmd, clen ); + if (err == noErr) { + switch(flags) { + case SCSI_WRITE | SCSI_BLIND: err = SCSIWBlind( (Ptr) TIB ); break; + case SCSI_READ | SCSI_BLIND: err = SCSIRBlind( (Ptr) TIB ); break; + case SCSI_WRITE: err = SCSIWrite( (Ptr) TIB ); break; + case SCSI_READ: err = SCSIRead( (Ptr) TIB ); break; + default: break; + } + if (err != noErr) { + printf("SCSI Read/Write Error: %d\n", err); + } + } else { + printf("SCSICmd Error: %d", err); + } + + /* Complete the transaction and release the bus */ + short cstat, cmsg; + OSErr comperr = SCSIComplete( &cstat, &cmsg, READ_TIMEOUT ); + if (comperr != noErr) {printf("SCSIComplete Error: %d\n", err); return err;} + if(status) *status = cstat; + + return err; +} + +OSErr scsi_inquiry(int id, int lun, scsi_inq_reply *reply) { + short err; + unsigned char cmd[6] = { SCSI_Cmd_Inquiry, lun << 5, 0x00, 0x00, 0x05, 0x00 }; + + memset(reply, 0, sizeof(scsi_inq_reply)); + + /* First we issue a dummy command to get the additional data field, then */ + /* we use that number to issue a second command with the corrected length. */ + + if( (err = scsi_cmd(id, cmd, sizeof(cmd), reply, cmd[4], 0, SCSI_READ)) != noErr) { + return err; + } + cmd[4] += reply->alen; + return scsi_cmd(id, cmd, sizeof(cmd), reply, cmd[4], 0, SCSI_READ); +} + +OSErr scsi_request_sense_data(int id, scsi_sense_reply *reply) { + short err; + unsigned char cmd[6] = { SCSI_Cmd_RequestSense, 0x00, 0x00, 0x00, 0x08, 0x00 }; + + memset(reply, 0, sizeof(scsi_sense_reply)); + + /* First we issue a dummy command to get the additional data field, then */ + /* we use that number to issue a second command with the corrected length. */ + + if( (err = scsi_cmd(id, cmd, sizeof(cmd), reply, cmd[4], 0, SCSI_READ)) != noErr) { + return err; + } + cmd[4] += reply->alen; + return scsi_cmd(id, cmd, sizeof(cmd), reply, cmd[4], 0, SCSI_READ); +} + +OSErr scsi_get_class(int id, int lun, int &dev ) { + scsi_inq_reply reply; + OSErr err = scsi_inquiry(id, lun, &reply ); + dev = reply.inf1 & 0x1F; + return err; +} diff --git a/mac-cpp-source/scsi/mac_scsi.h b/mac-cpp-source/scsi/mac_scsi.h new file mode 100644 index 0000000..88f8ad2 --- /dev/null +++ b/mac-cpp-source/scsi/mac_scsi.h @@ -0,0 +1,87 @@ +#include + +typedef Boolean bool; + +enum { + SCSI_Cmd_RequestSense = 0x03, + SCSI_Cmd_FormatUnit = 0x04, + SCSI_Cmd_NonSenseData = 0x06, + SCSI_Cmd_Read = 0x08, + SCSI_Cmd_Write = 0x0a, + SCSI_Cmd_CartProtect = 0x0c, + SCSI_Cmd_Inquiry = 0x12, + SCSI_Cmd_ModeSelect = 0x15, + SCSI_Cmd_ModeSense = 0x1a, + SCSI_Cmd_StartStopUnit = 0x1b, + SCSI_Cmd_SendDiagnostic = 0x1d, + SCSI_Cmd_PreventAllow = 0x1e, + SCSI_Cmd_TranslateLBA = 0x22, + SCSI_Cmd_FormatTest = 0x24, + SCSI_Cmd_ReadMany = 0x28, + SCSI_Cmd_WriteMany = 0x2a, + SCSI_Cmd_Verify = 0x2f, + SCSI_Cmd_ReadDefectData = 0x37, + SCSI_Cmd_ReadLong = 0x3e, + SCSI_Cmd_WriteLong = 0x3f +}; + +enum { + SCSI_DISK = 0x00, + SCSI_TAPE = 0x01, + SCSI_PRINTER = 0x02, + SCSI_CPU_DEVICE = 0x03, + SCSI_WORM = 0x04, + SCSI_CDROM = 0x05, + SCSI_SCANNER = 0x06, + SCSI_OPTICAL = 0x07, + SCSI_MEDIA_CHANGER = 0x08, + SCSI_COM = 0x09, + SCSI_UNKNOWN = 0x1F +}; + +typedef struct { /* | 7 6 5 4 3 2 1 0 | */ + unsigned char inf1; /* 0 | qualifier | device class | */ + unsigned char flg1; /* 1 |RMB| reserved | */ + unsigned char vers; /* 2 | ISO | ECMA | ANSI | */ + unsigned char inf2; /* 3 |AEN|TIO| rsv | data format | */ + unsigned char alen; /* 4 | additional length | */ + unsigned char rsv1[2]; /* 5 | reserved | */ + unsigned char flg2; /* 7 |rel|W32|W16|Syn|Lnk|Rsv|Que|SRs| */ + unsigned char vend[8]; /* 8 | manufacturer | */ + unsigned char prod[16]; /* 16 | product | */ + unsigned char rvsn[4]; /* 32 | revision | */ + unsigned char rsv2[20]; /* 36 | vendor unique | */ + unsigned char rsv3[40]; /* 56 | reserved | */ + unsigned char rsv4[160]; /* 96 | vendor unique | */ +} scsi_inq_reply; + +// Reference: https://docs.oracle.com/en/storage/tape-storage/storagetek-sl150-modular-tape-library/slorm/request-sense-03h.html#GUID-9309F2C0-ABF8-470E-AE25-E9535C821B39 + +typedef struct { /* | 7 6 5 4 3 2 1 0 | */ + unsigned char err; /* 0 |OK | error code | */ + unsigned char seg; /* 1 | segment number | */ + unsigned char key; /* 2 | reserved | sense key | */ + unsigned char inf1[4]; /* 3 | information | */ + unsigned char alen; /* 7 | additional length | */ + unsigned char inf2[4]; /* 8 | command specific information | */ + unsigned char asc; /* 12 | additional sense code | */ + unsigned char ascq; /* 13 | asc qualifier | */ + unsigned char fruc; /* 14 | field replaceable unit code | */ + unsigned char flg1; /* 15 |SKV|C/D| Rsv |bpv| bit ptr | */ + unsigned char fp[2]; /* 16 | field pointer | */ + unsigned char rsv[2]; /* 18 | reserved | */ +} scsi_sense_reply; + +/* Flags for scsi_cmd */ + +#define SCSI_READ 0x0001 +#define SCSI_WRITE 0x0002 +#define SCSI_BLIND 0x0004 + +OSErr scsi_reset(); +OSErr scsi_cmd(int id, void *cmd, size_t clen, void *buff, size_t siz, size_t cnt, int flags, char *status = 0); +OSErr scsi_inquiry(int id, int lun, scsi_inq_reply *buff); +OSErr scsi_request_sense_data(int id, scsi_sense_reply *buff); +OSErr scsi_get_class(int id, int lun, int &dev ); + + diff --git a/mac-cpp-source/tip/.finf/tip.cpp b/mac-cpp-source/tip/.finf/tip.cpp new file mode 100644 index 0000000000000000000000000000000000000000..42eba35cbb53b10b2d38497ec6856661aba07230 GIT binary patch literal 32 XcmWG>jRjRjRjRjRF($167THc z7zEU#&x^2tRF($=r*D2gd`nBFn_!|1yQck?qaNN_>0;9F2Q0#z}B xLIO2Rnb*x-A~FdmE5qQ%z~~zg90X(w`Gz|=0cknrxBveGX)s{^0A~AuH~RtFCf5KF9Rry0IVgM1$r4w3``6mt`Q;5 z;hwG_{S1v@6;m0oi-RQ_z$631-5pAz3=C2UKwD=)g}@Z!D^(!PdPXR?B(bQZv>-Sm zu_!ggCo@Se8Ce;S&G-qd@*RJ2eqM2YPO4sUNoi6shBA=K&k&Wo!Jhu15qco+01ZSl z&Vzx|H$N{iIUh|Nnajk$!Z3%?m1!^Y6&QW@;4VJ~26l4@X6gio0}C)Xfea8E5vI)R r<}Q)C02Gn|hA*RUKyVO{E#w>S`2(2k1L6Px;C(^w literal 0 HcmV?d00001 diff --git a/mac-cpp-source/tip/.rsrc/tip_aspi.cpp b/mac-cpp-source/tip/.rsrc/tip_aspi.cpp new file mode 100644 index 0000000000000000000000000000000000000000..36a6019943cfb27da6cb844c6379870ff48f41ef GIT binary patch literal 410 zcmZQzU}RumU>|7(Ek4PI>NWnS!QMfyB` zyZ*-UI503p_~)mD_RHA~Vr-ee^|9uJ>3>*ppF&(-d|3A3C z`~OPGtAK5hp5wd!^PGS1E>HkE%7cN^H$N{iIUnjkY!nj%3&U}S2~3ZfSHkGKheApi z7@Wl+*uDW2jzCq6pa2E25n;-_ZtfD14?rOq1~&#q-+h($ literal 0 HcmV?d00001 diff --git a/mac-cpp-source/tip/.rsrc/tip_main.cpp b/mac-cpp-source/tip/.rsrc/tip_main.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d3672295a94575b7e5339f92d572b85aaf30966c GIT binary patch literal 410 zcmZQzU}RumUNU{K!*v~?DyEU_XW+c>r{Ft7tLLlpD6c`hQ8fGin?C{AS0VKH!(TCI43nl zAuT^ACAA1y62uk+5fizAs^ZJ?b3l$@0tz79@E-_3N*D@31WN=10|$r!vWtO*;qI=J z2k!2=JsB(nBRm*5ee?5x*1`m^&`dzhN0}ZlonUo@(RU9{Zew6z>IUMOK=Z$X!V##7 x5fq@PVamL2u84>UP*w&QzKp&B!9hT_kZ-t?6Ofi;ehYOmki+}|%=Q5?82~>CNa_Fp literal 0 HcmV?d00001 diff --git a/mac-cpp-source/tip/tip.cpp b/mac-cpp-source/tip/tip.cpp new file mode 100644 index 0000000..03795cf --- /dev/null +++ b/mac-cpp-source/tip/tip.cpp @@ -0,0 +1,383 @@ +/******************************************************************************* + * TIP.CPP TROUBLE IN PARADISE 05/22/98 * + ******************************************************************************/ + +#include +#include +#include + +#include "tip.h" + +// ----------------------- Test Monitor Panel Definitions ------------------- +#define SET_RECT(LEFT, TOP, RIGHT, BOTTOM) {TOP, LEFT, BOTTOM, RIGHT} + +Rect CS_Stat = SET_RECT(114, 8, 242, 28); +Rect TP_Perc = SET_RECT( 12, 56, 409, 72); +Rect SS_Jaz = SET_RECT( 12, 94, 409, 126); +Rect SS_Sid0 = SET_RECT( 12, 94, 409, 110); +Rect TL_Sect = SET_RECT( 75, 154, 203, 170); +Rect ES_Read = SET_RECT(346, 154, 409, 170); +Rect SE_Rect = SET_RECT(222, 154, 255, 221); + +/******************************************************************************* + * WndProc + * + * This is the system's main window procedure + */ +void WndProc(long iMessage, long wParam) { + // ------------------------------------------------------------------------- + // WM_PAINT + // ------------------------------------------------------------------------- + if (iMessage == WM_PAINT) { + // Draw the Lower Horz Button Divider + + SetColor(GRAY_COLOR); + MoveTo(15, 289); + LineTo(446, 289); + SetColor(WHITE_COLOR); + LineTo(446, 290); + LineTo(14, 290); + + // Paint the Copyright Notice + SetColor(GRAY_COLOR); + TextOut(15, 298, szCopyright_1); + TextOut(15, 311, szCopyright_2); + } + // ------------------------------------------------------------------------- + // WM_COMMAND : a button was pressed + // ------------------------------------------------------------------------- + else if (iMessage == WM_COMMAND) { + switch(wParam) { + case IDB_QUIT: + if (TestingPhase < TESTING_STARTUP) { + PostQuitMessage(); + } + break; + case IDB_TEST: + switch(CartridgeStatus) { + case DISK_SPUN_DOWN: + SpinUpIomegaCartridge(CurrentDevice); + break; + case DISK_AT_SPEED: + printf("Testing the disk\n"); + if(TestingPhase != READY_TO_TEST) { + PrepareToBeginTesting(); + } + TestTheDisk(); + printf("Test finished\n"); + break; + case DISK_TEST_UNDERWAY: + UserInterrupt = 1; + break; + case DISK_Z_TRACK_FAILURE: + case DISK_TEST_FAILURE: + case DISK_PROTECTED: + //EjectIomegaCartridge(); + break; + case DISK_LOW_SPARES: + PrepareToBeginTesting(); + break; + } + break; + default: + break; + } + } +} + +BtnList tipBtns[] = { + {IDB_TEST, szPressToStart, 200, 301, 160, 24, true}, // Added by MLT + {IDB_BACK, szBack, 185-28, 301, 80, 24, false}, + {IDB_NEXT, szNext, 264-28, 301, 80, 24, false}, + {IDB_QUIT, szQuit, 367+35, 301, 45, 24, true}, + {0, 0, 0, 0, 0, 0, 0} +}; + +/******************************************************************************* + * SUNKEN FIELDS + *******************************************************************************/ +void SunkenFields(Rect *pFirstRect, long count, long yspacing) { + Rect drawRect; + drawRect = *pFirstRect; // make a local copy + do { + DrawEdge(&drawRect, BDR_SUNKENOUTER, BF_RECT); + drawRect.top += yspacing; + drawRect.bottom += yspacing; + } while(--count); +} + +/******************************************************************************* + * PAINT TEXT ARRAY + *******************************************************************************/ +void PaintTextArray(TextList *list, long color) { + SetColor(color); + for(int i = 0; list[i].str; i++) { + TextOut(list[i].x, list[i].y, list[i].str); + } +} + +/******************************************************************************* + * PAINT TEST PHASE + *******************************************************************************/ +void PaintTestPhase() { + DrawLed(320, 5, TestingPhase == READING_DATA ? GREEN_COLOR : BACK_COLOR); + DrawLed(336, 5, TestingPhase == WRITING_PATT ? RED_COLOR : BACK_COLOR); + DrawLed(336, 21, TestingPhase == READING_PATT ? GREEN_COLOR : BACK_COLOR); + DrawLed(320, 21, TestingPhase == WRITING_DATA ? RED_COLOR : BACK_COLOR); +} + +/******************************************************************************* + * PAINT CART STATUS + * + * Paints the textual cartridge status window + *******************************************************************************/ +void PaintCartStatus() { + // blank out any previous status display + SetColor(BACK_COLOR); + Rectangle(115, 9, 241, 27); + // display the new cartridge status + long eax = CartridgeStatus - 1; + if(eax < 0 || eax >= LAST_CART_STATUS) { + eax = 0; + } + // pickup the pointer to the string + const char *esi; + //if (DriveCount) { + esi = CartStatStrings[eax]; + //} else { + // esi = szNoIomegaDrives; + //} + SetColor(BLACK_COLOR); + TextOutCentered(115, 9, 241 - 115, 27 - 9, esi); +} + +/******************************************************************************* + * PAINT BAR GRAPH + *******************************************************************************/ +void PaintBarGraph(int Xleft, int Ytop, int XWidth, int YHeight, long BarColor, long BarValue, char *pRightText, bool Active) { + // fill the entire rectangle with background gray + SetColor(BACK_COLOR); + Rectangle(Xleft, Ytop, Xleft + XWidth, Ytop + YHeight); + if (Active) { // now fleshout the bar ONLY IF we're active + // if RightText string is non-null, paint it in darker gray + if(pRightText) { + SetColor(GRAY_COLOR); + TextOutCentered(Xleft, Ytop, XWidth, YHeight, pRightText); + } + // now paint the active portion + const long AbsoluteBarWidth = XWidth * BarValue / 100; + SetColor(BarColor, BLACK_COLOR); + Rectangle(Xleft, Ytop, Xleft + AbsoluteBarWidth, Ytop + YHeight); + // now place the floating percentage into the middle (if it fits there) + if(BarValue) { + char PercentString[8]; + sprintf(PercentString, szBarChartPercent, BarValue); + SetColor(WHITE_COLOR); + TextOutCentered(Xleft, Ytop, AbsoluteBarWidth, YHeight, PercentString); + + } + } +} + +/******************************************************************************* + * PAINT THE BAR GRAPHS + * + * This paints the two or three bar graphs on the test monitor window. + *******************************************************************************/ +void PaintTheBarGraphs(bool Active) { + long ebx, ecx; + PaintBarGraph(13, 57, 395, 14, BLUE_COLOR, PercentComplete, NULL, Active); + if(JazDrive) { + ebx = Side_0_SparesCount; + ecx = MAXIMUM_JAZ_SPARES; + if(ebx > ecx) { // if Spares > MAXIMUM + ebx = ecx; // clip Spares down to MAX + } + PaintBarGraph(13, 95, 395, 30, RED_COLOR, ebx*100/ecx, NULL, Active); + } else { + ebx = Side_0_SparesCount; + ecx = MAXIMUM_ZIP_SPARES; + PaintBarGraph(13, 95, 395, 14, RED_COLOR, ebx*100/ecx, "Side 0", Active); + ebx = Side_1_SparesCount; + ecx = MAXIMUM_ZIP_SPARES; + PaintBarGraph(13, 111, 395, 14, RED_COLOR, ebx*100/ecx, "Side 1", Active); + } +} + +/******************************************************************************* + * PAINT CENTERED STRING + * + * Paubts a string int a rectangular region + *******************************************************************************/ +void PaintCenteredString(int Xleft, int Ytop, int XWidth, int YHeight, const char *pText, bool Active) { + // fill the entire rectangle with background gray + SetColor(BACK_COLOR); + Rectangle(Xleft, Ytop, Xleft + XWidth, Ytop + YHeight); + // now fleshout the bitmap ONLY IF we're active + if(Active) { + SetColor(BLACK_COLOR); + TextOutCentered(Xleft, Ytop, XWidth, YHeight, pText); + } +} + +/******************************************************************************* + * PAINT CENTERED VALUE + * + * Paints a decimal-value into a rectangular region + *******************************************************************************/ +void PaintCenteredValue(int Xleft, int Ytop, int XWidth, int YHeight, long value, bool Active) { + char szString[40]; + // convert our value into a string + sprintf(szString, szCenteredDecimal, value); + // and paint it's value + PaintCenteredString(Xleft, Ytop, XWidth, YHeight, szString, Active); +} + +/******************************************************************************* + * PAINT TEST STATISTICS + * + * This paints the two columns of testing statistics on the test minitor window. + *******************************************************************************/ +void PaintTestStatistics(bool Active) { + char szString[40]; + // assemble and paint the sector testing range + long eax = SingleTransferLBA; + if (eax) { + sprintf(szString, szCenteredDecimal, eax); + } else { + eax = FirstLBASector; + if (!TestingPhase) { + eax = 0; + } + sprintf(szString, szCenteredDecimal, eax); + strcat(szString, szSpaceDashSpace); + eax = FirstLBASector + NumberOfLBAs; + if (TestingPhase == READY_TO_TEST) { + eax = LastLBAOnCartridge; + } + sprintf(szString + strlen(szString), szCenteredDecimal, eax); + } + PaintCenteredString(76, 155, 126, 14, szString, Active); + + // show the LastError + char *errStr = 0; + for (int i = 0; errorTypeList[i].str; i++) { + errStr = errorTypeList[i].str; + if (errorTypeList[i].code == LastError) break; + } + PaintCenteredString(76, 172, 126, 14, errStr, Active); + + // show the elapsed time + CvrtSecondsToHMSstring(szString, SecondsElapsed); + PaintCenteredString(76, 189, 126, 14, szString, Active); + + // see if it's time for us to estimate... + if(SecondsElapsed - ElapsedTimeOfLastEstimate > 15 && PercentComplete > 0) { + // assemble the remaining time + ElapsedTimeOfLastEstimate = SecondsElapsed; + CurrentTotalTimeEstimate = SecondsElapsed * 100 / PercentComplete; + } + + // given the current estimate time, show the remaining time! + eax = CurrentTotalTimeEstimate; + if(eax) { + eax -= SecondsElapsed; + if(eax < 0) eax = 0; + CvrtSecondsToHMSstring(szString, eax); + } else { + strcpy(szString, szEstimating); + } + PaintCenteredString(76, 206, 126, 14, szString, Active); + + // now show the error accumulations... + long TotalErrors = SoftErrors + FirmErrors + HardErrors; + PaintCenteredValue(347, 155, 61, 14, SoftErrors, Active); + PaintCenteredValue(347, 172, 61, 14, FirmErrors, Active); + PaintCenteredValue(347, 189, 61, 14, HardErrors, Active); + PaintCenteredValue(347, 206, 61, 14, TotalErrors, Active); +} + +void CvrtSecondsToHMSstring(char *szString, long seconds) { + const long h = (seconds / 3600); + const long m = (seconds / 60) % 60; + const long s = (seconds % 60); + sprintf(szString, szHoursMinsSecs, h, m, s); +} + +/******************************************************************************* + * TEST MONITOR WND PROC + *******************************************************************************/ +void TestMonitorWndProc() { + long eax; + SetColor(BLACK_COLOR); + TextOut(12, 9, szCartStatus); + DrawEdge(&CS_Stat, BDR_SUNKENOUTER, BF_RECT); + PaintCartStatus(); + + // draw the sunken rectangles + DrawEdge(&TP_Perc, BDR_SUNKENOUTER, BF_RECT); + DrawEdge(&SE_Rect, BDR_SUNKENOUTER, BF_RECT); + SunkenFields(&TL_Sect, 4, 17); + SunkenFields(&ES_Read, 4, 17); + if(JazDrive) { // draw a single LARGE rectangle + DrawEdge(&SS_Jaz, BDR_SUNKENOUTER, BF_RECT); + } else { // draw a pair of smaller rectangles + SunkenFields(&SS_Sid0, 2, 16); + } + if((CartridgeStatus == DISK_AT_SPEED) || (CartridgeStatus == DISK_SPUN_DOWN) || (CartridgeStatus >= DISK_LOW_SPARES)) { + PaintTheBarGraphs(true); + PaintTestStatistics(true); + eax = BLACK_COLOR; + } else { + PaintTheBarGraphs(false); + PaintTestStatistics(false); + eax = GRAY_COLOR; + } + PaintTextArray(TestBlackText, eax); + PaintTextArray(TestGrayText, GRAY_COLOR); + PaintTestPhase(); + + // TODO: paint the little speaker icon +} + +/******************************************************************************* + * UPDATE RUN TIME DISPLAY + *******************************************************************************/ + +void UpdateRunTimeDisplay() { + GetDC(hTestMonitor); + PaintTestPhase(); + PaintTheBarGraphs(true); + PaintTestStatistics(true); + PaintCartStatus();// Added by MLT + ReleaseDC(hTestMonitor); +} + +/******************************************************************************* + * UPDATE CURRENT SECTOR + *******************************************************************************/ + +void UpdateCurrentSector() { + GetDC(hTestMonitor); + char szString[40]; + sprintf(szString, szCenteredDecimal, SingleTransferLBA); + PaintCenteredString(76, 155, 126, 14, szString, true); + ReleaseDC(hTestMonitor); +} + +/******************************************************************************* + * UPDATE RUN PHASE DISPLAY + *******************************************************************************/ + +void UpdateRunPhaseDisplay() { + GetDC(hTestMonitor); + PaintTestPhase(); + ReleaseDC(hTestMonitor); +} + +/******************************************************************************* + * ERROR SOUND + *******************************************************************************/ + +void ErrorSound() { +} \ No newline at end of file diff --git a/mac-cpp-source/tip/tip.h b/mac-cpp-source/tip/tip.h new file mode 100644 index 0000000..c3e3df2 --- /dev/null +++ b/mac-cpp-source/tip/tip.h @@ -0,0 +1,212 @@ + +typedef Boolean bool; + +extern WindowPtr tipWindow; + +void run_tip(); + +#define MINIMUM_JAZ_SPARES 500 +#define MAXIMUM_JAZ_SPARES 2557 +#define MINIMUM_ZIP_SPARES 50 +#define MAXIMUM_ZIP_SPARES 126 + +extern long CurrentDevice; +extern bool JazDrive; // true if the current drive +extern long CartridgeStatus; +extern long LastLBAOnCartridge; +extern unsigned long StartingInstant; +extern long NumberOfLBAs; +extern long Side_0_SparesCount; // JAZ has only one count +extern long Side_1_SparesCount; // ZIP has counts for both sides +extern long Initial_Side_0_Spares; +extern long Initial_Side_1_Spares; +extern long TestingPhase; // 0 = not testing, no data ... +extern long PercentComplete; +extern long FirstLBASector; +extern long NumberOfLBAs; +extern long SecondsElapsed; +extern long SoftErrors; +extern long FirmErrors; +extern long HardErrors; +extern long ElapsedTimeOfLastEstimate; +extern long CurrentTotalTimeEstimate; +extern bool UserInterrupt; +extern long LastError; +extern long SingleTransferLBA; + +// ----------------------- Macintosh Compatibility ----------------------- + +enum { + BACK_COLOR = -1, + BLACK_COLOR = 0x000000, + LTGRAY_COLOR = 0xc0c0c0, + GRAY_COLOR = 0x808080, + WHITE_COLOR = 0xffffff, + BLUE_COLOR = 0x0000ff, + RED_COLOR = 0xff0000, + GREEN_COLOR = 0x00ff00, +}; + +#define BDR_SUNKENOUTER 1 +#define BF_RECT 1 +#define WM_PAINT 1 +#define WM_COMMAND 2 + +void SetColor(long color); +void SetColor(long color, long monoColor); +void DrawLed(int x, int y, long color); +void StrToPascal(Str255 pStr, const char *str); +long GetTextExtent(const char *str, unsigned long len); +void TextOut(int x, int y, Str255 str); +void TextOut(int x, int y, const char *str); +void TextOutCentered(int x, int y, int w, int h, const char *str); +void SetButtonText(const char *str); +void Rectangle(int left, int top, int right, int bottom); +void DrawEdge(Rect *qrc, int edge, int grfFlags); +void PostQuitMessage(); +unsigned long GetSystemTime(); + +#define hTestMonitor -20, -10 +#define hMainWnd 0, 40 + +#define GetDC(h) {GrafPtr oldPort; \ + GetPort(&oldPort); \ + SetPort(tipWindow); \ + SetOrigin(h); + +#define ReleaseDC(h) SetOrigin(0,0); \ + SetPort(oldPort);} + + +// ------------------------------ Cartridge Status ------------------------------- + +enum { + DISK_STATUS_UNKNOWN = 1, + DISK_AT_SPEED = 2, + DISK_SPINNING_UP = 3, + DISK_NOT_PRESENT = 4, + DISK_SPUN_DOWN = 5, + DISK_STALLED = 6, + DISK_Z_TRACK_FAILURE = 7, + DISK_PROTECTED = 8, + DISK_LOW_SPARES = 9, + DISK_TEST_UNDERWAY = 10, + DISK_TEST_FAILURE = 11, + + LAST_CART_STATUS = 11 +}; + +// ---------------------------- Testing Phase Status ----------------------------- + +enum { + UNTESTED = 0, + READY_TO_TEST = 1, + TESTING_STARTUP = 2, + READING_DATA = 3, + WRITING_PATT = 4, + READING_PATT = 5, + WRITING_DATA = 6 +}; + +/******************************************************************************* + * STRINGS + *******************************************************************************/ + +extern const char *szWindowTitle; +extern const char *szCopyright_1; +extern const char *szCopyright_2; +extern const char *szSide0; +extern const char *szSide1; +extern const char *szSpaceDashSpace; +extern const char *szBarChartPercent; +extern const char *szCenteredDecimal; +extern const char *szCenteredHex; +extern const char *szHoursMinsSecs; +extern const char *szCartStatus; +extern const char *szEstimating; +extern const char *szOneMoment; +extern const char *szPressToStart; +extern const char *szPressToStop; +extern const char *szPressToSpin; +extern const char *szPressToEject; +extern const char *szPressToProceed; + +/************* Cartridge Status Text *************/ + +typedef struct {long code; char *str;} ErrorTypeList; +typedef struct {int x, y; char *str;} TextList; + +extern const char *szUnknownStat; +extern const char *szAtSpeedStat; +extern const char *szSpinningUp; +extern const char *szNotPresent; +extern const char *szSpunDown; +extern const char *szStalledStat; +extern const char *szZtrackFailure; +extern const char *szDiskLocked; +extern const char *szLowSpares; +extern const char *szTestUnderway; +extern const char *DriveUnderTest; +extern const char *szTestFailure; +extern const char *szNoIomegaDrives; + +extern const char *CartStatStrings[]; + +extern ErrorTypeList errorTypeList[]; +extern TextList TestBlackText[]; +extern TextList TestGrayText[]; + +/************* Window Creation Data **************/ + +extern const char *szBack; +extern const char *szNext; +extern const char *szQuit; + +#define IDB_BACK 0xFF00 +#define IDB_NEXT 0xFF01 +#define IDB_QUIT 0xFF02 +#define IDB_TEST 0xFF03 + +typedef struct {long id; const char *name; int x; int y; int w; int h; bool visible;} BtnList; +extern BtnList tipBtns[]; + +/******************************************************************************* + * FUNCTION PROTOTYPES + *******************************************************************************/ + +void PaintCenteredString(int Xleft, int Ytop, int XWidth, int YHeight, const char *pText, bool Active); +void PaintCenteredValue(int Xleft, int Ytop, int XWidth, int YHeight, long value, bool Active); +void SunkenFields(Rect *pFirstRect, long count, long yspacing); +void PaintCartStatus(); +void PaintTextArray(TextList *list, long color); +void PaintBarGraph(int Xleft, int Ytop, int XWidth, int YHeight, long BarColor, long BarValue, char *pRightText, bool Active); +void PaintTestPhase(); +void PaintTheBarGraphs(bool Active); +void PaintTestStatistics(bool Active); +void CvrtSecondsToHMSstring(char *szString, long seconds); + +void UpdateCurrentSector(); +void UpdateRunTimeDisplay(); +void UpdateRunPhaseDisplay(); +void ErrorSound(); +void ProcessPendingMessages(); +void WndProc(long iMessage, long wParam); +void TestMonitorWndProc(); +void TestButtonClicked(); + +void GetCommandDetails(char command, char &cmd_flags, char &cmd_length); +long SCSICommand(short Device, char *lpCmdBlk, void *lpIoBuf, short IoBufLen); +long GetModePage(short Device, short PageToGet, void *pBuffer, short BufLen); +long SetModePage(short Device, void *pBuffer); +void ModifyModePage(char *PageBuff, char eec, char retries); +void SetErrorRecovery(bool Retries, bool ECC, bool Testing); +long GetNonSenseData(short Device, short DataPage, void *Buffer, short BufLen); +long LockCurrentDrive(); +long UnlockCurrentDrive(); +long SpinUpIomegaCartridge(short Device); +void GetSpareSectorCounts(bool); +long PerformRegionTransfer(short XferCmd, void *pBuffer); +long TestTheDisk(); +long GetElapsedTimeInSeconds(); +void PrepareToBeginTesting(); +void BumpErrorCounts(long ErrorCode); \ No newline at end of file diff --git a/mac-cpp-source/tip/tip_aspi.cpp b/mac-cpp-source/tip/tip_aspi.cpp new file mode 100644 index 0000000..e010da6 --- /dev/null +++ b/mac-cpp-source/tip/tip_aspi.cpp @@ -0,0 +1,684 @@ +#include +#include +#include +#include "mac_scsi.h" +#include "tip.h" + +//#define DEMO + +#define BYTE_AT(a) *((char*)&(a)) +#define WORD_AT(a) *((short*)&(a)) +#define DWORD_AT(a) *((long*)&(a)) + +#define MAKE_LITTLE_ENDIAN(a) a // Don't do anything on 68000 +#define MAKE_BIG_ENDIAN(a) a // Don't do anything on 68000 + +// offsets to the various sector data images + +#define ZIP_100_PART 0x0000 +#define ZIP_100_BOOT 0x0200 +#define ZIP_250_PART 0x0400 +#define ZIP_250_BOOT 0x0600 +#define JAZ_1GB_PART 0x0800 +#define JAZ_1GB_BOOT 0x0A00 +#define JAZ_2GB_PART 0x0C00 +#define JAZ_2GB_BOOT 0x0E00 + +struct DEFECT_LIST_HEADER { + char DLH_reserved; // (00h) + char DLH_BitFlags; // [000] [P] [G] [xxx - defect list format] + char DLH_DefectListLength; +}; + +//-------------------------- Drive Array Status Flags --------------------------- + +#define JAZ_DRIVE 0x00010000 +#define MEDIA_CHANGED 0x00020000 +#define DISK_EJECTING 0x00040000 // we've asked for eject and waiting ... +#define ODD_BYTE_COMPENSATION 0x00080000 // special handling for ODD length PSWD +#define MAX_DRIVE_COUNT 16 // we can handle up to 16 Zip/Jaz drives + +#define ERROR_RECOVERY_PAGE 1 // From disassembly +#define FORMAT_STATUS_PAGE 1 +#define DISK_STATUS_PAGE 2 + +#define NEW_DISK_STATUS_OFFSET 3 // newer offset of the Disk Status Byte +#define OLD_DISK_STATUS_OFFSET 1 // older offset of the Disk Status Byte + +#define JAZ_SPARES_COUNT_OFFSET 68 // offsets into DiskStat tbl +#define NEW_ZIP_SIDE_0_SPARES_COUNT_OFFSET 13 +#define NEW_ZIP_SIDE_1_SPARES_COUNT_OFFSET 17 +#define OLD_ZIP_SIDE_0_SPARES_COUNT_OFFSET 11 +#define OLD_ZIP_SIDE_1_SPARES_COUNT_OFFSET 15 +#define JAZ_PROTECT_MODE_OFFSET 21 +#define NEW_ZIP_PROTECT_MODE_OFFSET 21 +#define OLD_ZIP_PROTECT_MODE_OFFSET 19 +#define JAZ_LAST_LBA_OFFSET 5 +#define NEW_ZIP_LAST_LBA_OFFSET 5 +#define OLD_ZIP_LAST_LBA_OFFSET 3 + +#define DRIVE_A_SUPPORT_BIAS 32 // reduce total by 32 for DRIVE A support + +#define BYTES_PER_SECTOR 512 +#define MAX_SECTORS_PER_TEST 20 + +#define BADNESS_THRESHOLD 10 + +#define SS_ERR 0x00000004 // From disassembly +#define BUFFER_TOO_BIG 0x00FFFFE6 // From disassembly +#define LBA_TOO_LARGE 0x00210005 // From disassembly +#define INCOMPATIBLE_MEDIA 0x00300002 // From disassembly +#define MEDIA_NOT_PRESENT 0x003a0002 // From disassembly +#define DEFECT_LIST_READ_ERROR 0x001c0003 // From disassembly + +#define CHECK_CONDITION 0x02 + +enum { + szBadResult, + szInterrupted, + szExplainResult, + szPerfectResult +}; + +long CurrentDevice = 0; + +bool JazDrive; // true if the current drive +long CartridgeStatus = /*DISK_NOT_PRESENT*/ DISK_AT_SPEED; + +unsigned long StartingInstant; + +// ----------------------------- Run Time Variables ------------------------------ + +long Side_0_SparesCount; // JAZ has only one count +long Side_1_SparesCount; // ZIP has counts for both sides +long Initial_Side_0_Spares; +long Initial_Side_1_Spares; + +long TestingPhase; // 0 = not testing, no data ... +long PercentComplete; +long FirstLBASector; +long NumberOfLBAs; +long LastLBAOnCartridge; +long SecondsElapsed; +long SoftErrors; +long FirmErrors; +long HardErrors; +long ElapsedTimeOfLastEstimate; +long CurrentTotalTimeEstimate; +bool UserInterrupt; +long LastError; +long SingleTransferLBA; + +/******************************************************************************* + * GET COMMAND DETAILS + * + * Given a SCSI command byte, this returns the command + * block length in AL and the Command Flags in AH + *******************************************************************************/ + +#define TEN_BYTE_CMDS 0x1F +#define SRB_DIR_IN SCSI_READ +#define SRB_DIR_OUT SCSI_WRITE + +void GetCommandDetails(char command, char &cmd_flags, char &cmd_length) { + char CommandDetailsTable[] = { + SCSI_Cmd_RequestSense, SRB_DIR_IN, // 03 IN == get from drive + SCSI_Cmd_FormatUnit, 0, // 04 OUT == send to drive + SCSI_Cmd_NonSenseData, SRB_DIR_IN, // 06 + SCSI_Cmd_Read, SRB_DIR_IN, // 08 + SCSI_Cmd_Write, SRB_DIR_OUT, // 0A + SCSI_Cmd_CartProtect, SRB_DIR_OUT, // 0C + SCSI_Cmd_Inquiry, SRB_DIR_IN, // 12 + SCSI_Cmd_ModeSelect, SRB_DIR_OUT, // 15 + SCSI_Cmd_ModeSense, SRB_DIR_IN, // 1A + SCSI_Cmd_StartStopUnit, 0, // 1B + SCSI_Cmd_SendDiagnostic, 0, // 1D + SCSI_Cmd_PreventAllow, 0, // 1E + SCSI_Cmd_TranslateLBA, SRB_DIR_IN, // 22 + SCSI_Cmd_FormatTest, 0, // 24 + SCSI_Cmd_ReadMany, SRB_DIR_IN, // 28 + SCSI_Cmd_WriteMany, SRB_DIR_OUT, // 2A + SCSI_Cmd_Verify, 0, // 2F + SCSI_Cmd_ReadDefectData, SRB_DIR_IN, // 37 + SCSI_Cmd_ReadLong, SRB_DIR_IN, // 3E + SCSI_Cmd_WriteLong, SRB_DIR_OUT // 3F + }; + cmd_flags = 0; // ; if we don't locate it ... return ZERO + // search the table for the command entry + for(int i = 0; i < sizeof(CommandDetailsTable); i += 2) { + if(CommandDetailsTable[i] == command) { // if we match we're done + cmd_flags = CommandDetailsTable[i+1]; + break; + } + } + cmd_length = 6; // presume a short (6 byte) command + if(command > TEN_BYTE_CMDS) // but if it's a LONG one .... + cmd_length = 10; +} + +/******************************************************************************* + * SCSI COMMAND + * + * This executes a SCSI command through the interface. It receives a + * pointer to a standard SCSI command block (SCB) and a pointer and + * length to an IoBuffer for the command. It returns the complete + * three-byte sense code from the command. + *******************************************************************************/ +long SCSICommand(short Device, char *lpCmdBlk, void *lpIoBuf, short IoBufLen) { + char cmd_length, cmd_flags, cmd_status; + GetCommandDetails(lpCmdBlk[0], cmd_flags, cmd_length); + // call the SCSI interface to forward the command to the device + OSErr err = scsi_cmd(Device, lpCmdBlk, cmd_length, lpIoBuf, IoBufLen, 0, cmd_flags, &cmd_status); + if(err != noErr) { + return SS_ERR; + } + if(cmd_status == 0) { + printf("SCSI OK\n"); + // if the command did not generate any Sense Data, just return NULL + return 0; + } + else if(cmd_status == 2) { // Check Condition + printf("SCSI CHK CONDITION\n"); + // Request sense data + scsi_sense_reply sense_data; + scsi_request_sense_data(Device, &sense_data); + // okay, we have an SS_ERR condition, let's check the SENSE DATA + // assemble [00 ASC ASCQ SenseKey] + return (sense_data.asc << 16) || + (sense_data.ascq << 8) || + sense_data.key; + } + else { + // else, if it's *NOT* a "Sense Data" error (SS_ERR) + return cmd_status | 0x00FFFF00; // [00 FF FF er] + } +} + +/******************************************************************************* + * GET MODE PAGE + *******************************************************************************/ +long GetModePage(short Device, short PageToGet, void *pBuffer, short BufLen) { + char Scsi[6] = {0}; + Scsi[0] = SCSI_Cmd_ModeSense; + Scsi[2] = PageToGet; + Scsi[4] = BufLen; + return SCSICommand(Device, Scsi, pBuffer, BufLen); + } + +/******************************************************************************* + * SET MODE PAGE + *******************************************************************************/ +long SetModePage(short Device, void *pBuffer) { + char Scsi[6] = {0}; // init the SCSI parameter block + char* ebx = (char*) pBuffer; // get a pointer to the top of buffer + char ecx = ebx[0] + 1; // adjust it up by one + Scsi[0] = SCSI_Cmd_ModeSelect; // set the command + Scsi[1] = 0x10; // set the Page Format bit + Scsi[4] = ecx; // set the parameter list length + return SCSICommand(Device, Scsi, pBuffer, ecx); + } + +/******************************************************************************* + * SET ERROR RECOVERY + *******************************************************************************/ +void ModifyModePage(char *PageBuff, char ecc, char retries) { + long eax = PageBuff[3]; // get the Block Descriptor Length + + char *ebx = PageBuff + 4; // get just past the header address + // form ebx == the offset to the top of the page we've read ... + ebx += eax; + + ebx[0] &= ~0x80; // always turn off the PS bit (parameters savable) + ebx[2] = 0xC0 | ecc; // set the ECC fields + ebx[3] = retries; // set the common retry count + if(ebx[1] > 6) // if we have a large format page... + ebx[8] = retries; // then set the write count too +} + +void SetErrorRecovery(bool Retries, bool ECC, bool Testing) { + char PageBuff[40]; + GetModePage(CurrentDevice, ERROR_RECOVERY_PAGE, PageBuff, sizeof(PageBuff)); + + #define EARLY_RECOVERY 0x08 + #define PER 0x04 + #define SUPPRESS_ECC 0x01 + + // set the ECC fields + char ecc = SUPPRESS_ECC; // presume ECC suppression + if(ECC) { + ecc = EARLY_RECOVERY; // enable ECC and Early Recovery + if(Testing) { + ecc = EARLY_RECOVERY | PER; // we're testing, so EER & PER + } + } + + // set the retry counts + char retries = 0x16; // set retries to 22 for Zip drive + if(JazDrive) + retries = 0x64; // and to 100 for Jaz drive + if(!Retries) // But if we have no retries ... + retries = 0; + + ModifyModePage(PageBuff, ecc, retries); + long eax = SetModePage(CurrentDevice, PageBuff); + // if we had an invalid field in the CDB (the EER bit was on) + if (eax == 0x00260005) { + GetModePage(CurrentDevice, ERROR_RECOVERY_PAGE, PageBuff, sizeof(PageBuff)); + ecc &= ~0x08; // same, *BUT*NOT* Early Recovery + ModifyModePage(PageBuff, ecc, retries); + SetModePage(CurrentDevice, PageBuff); + } +} + +/******************************************************************************* + * GET NON-SENSE PAGE DATA + * + * Given Adapter, Device, DataPage, and a Buffer to receive the data, this + * fills the buffer we're given and returns with the SCSI Completion Code + *******************************************************************************/ +long GetNonSenseData(short Device, short DataPage, void *Buffer, short BufLen) { + char Scsi[6] = {0}; + Scsi[0] = SCSI_Cmd_NonSenseData; // do a Non-Sense Data Read + Scsi[2] = DataPage; // which page to read + Scsi[4] = BufLen; // tell drive page is this long + return SCSICommand(Device, Scsi, Buffer, BufLen); +} + +/******************************************************************************* + * LOCK CURRENT DRIVE + *******************************************************************************/ +long LockCurrentDrive() { + char Scsi[6] = {0}; + Scsi[0] = SCSI_Cmd_PreventAllow; + Scsi[4] = 1; // set to ONE to lock the drive + return SCSICommand(CurrentDevice, Scsi, NULL, 0); +} + +/******************************************************************************* + * UNLOCK CURRENT DRIVE + *******************************************************************************/ +long UnlockCurrentDrive() { + char Scsi[6] = {0}; + Scsi[0] = SCSI_Cmd_PreventAllow; + return SCSICommand(CurrentDevice, Scsi, NULL, 0); +} + +/******************************************************************************* +* SPIN UP IOMEGA CARTRIDGE +*******************************************************************************/ +long SpinUpIomegaCartridge(short Device) { + char Scsi[6] = {0}; + Scsi[0] = SCSI_Cmd_StartStopUnit; + Scsi[1] = 1; // set the IMMED bit for offline + Scsi[4] = 1; // start the disk spinning + return SCSICommand(Device, Scsi, NULL, 0); +} + +/******************************************************************************* + * GET SPARE SECTOR COUNTS + * + * This returns NON-ZERO if we have trouble and posted the error message + * into the RichText control, else it sets the number of spares available + *******************************************************************************/ + +void GetSpareSectorCounts(bool) { + DEFECT_LIST_HEADER DefectHeader; + long eax = 0, ebx, edx; + short ch, cl; + ListChk: + // ask for the defect list to make sure we're able to read it + char Scsi[10] = {0}; + Scsi[0] = SCSI_Cmd_ReadDefectData; + Scsi[2] = 0x1e; // 0b00011110 defect format, G/P bits + Scsi[8] = 4; // ask for only FOUR bytes + eax = SCSICommand(CurrentDevice, Scsi, &DefectHeader, sizeof(DefectHeader)); + if ((!eax) || (eax == INCOMPATIBLE_MEDIA)) { + // we could read its defect list ... so show it! + // -------------------------------------------------------------------------- + // MLT: looks like on the Iomega Zip 100, the maximum size for DiskStat is 63 + // rather than 72; it looks like this code is causing a SCSI transfer error + // here... might be better to conditionally check for Jaz drive + char DiskStat[72]; + eax = GetNonSenseData(CurrentDevice, DISK_STATUS_PAGE, DiskStat, sizeof(DiskStat)); + if (!eax) /*goto ListChk;*/ return; + // -------------------------------------------------------------------------- + ch = 0; // clear the DRIVE_A_SUPPORT + if (JazDrive) { + eax = WORD_AT(DiskStat[JAZ_SPARES_COUNT_OFFSET]); + ebx = 0; + cl = BYTE_AT(DiskStat[JAZ_PROTECT_MODE_OFFSET]); + edx = DWORD_AT(DiskStat[JAZ_LAST_LBA_OFFSET]); + } else { + if (DiskStat[0] == DISK_STATUS_PAGE) { + eax = WORD_AT(DiskStat[NEW_ZIP_SIDE_0_SPARES_COUNT_OFFSET]); + ebx = WORD_AT(DiskStat[NEW_ZIP_SIDE_1_SPARES_COUNT_OFFSET]); + cl = BYTE_AT(DiskStat[NEW_ZIP_PROTECT_MODE_OFFSET]); + edx = DWORD_AT(DiskStat[NEW_ZIP_LAST_LBA_OFFSET]); + ch--; // set the DRIVE_A_SUPPORT + } + else { + eax = WORD_AT(DiskStat[OLD_ZIP_SIDE_0_SPARES_COUNT_OFFSET]); + ebx = WORD_AT(DiskStat[OLD_ZIP_SIDE_1_SPARES_COUNT_OFFSET]); + cl = BYTE_AT(DiskStat[OLD_ZIP_PROTECT_MODE_OFFSET]); + edx = DWORD_AT(DiskStat[OLD_ZIP_LAST_LBA_OFFSET]); + } + if(ebx == 0) { + CartridgeStatus = DISK_TEST_FAILURE; + return; + } + } + //--------------------------- + // bswap edx; save the last LBA in any event + //--------------------------- + if(ch) { + edx -= DRIVE_A_SUPPORT_BIAS; + } + LastLBAOnCartridge = edx; + MAKE_LITTLE_ENDIAN(eax); // make it little endian + Side_0_SparesCount = eax; + MAKE_LITTLE_ENDIAN(ebx); // make it little endian + Side_1_SparesCount = ebx; + // compute the number of troubles we encountered during the testing + FirmErrors = Initial_Side_0_Spares - Side_0_SparesCount; + FirmErrors += Initial_Side_1_Spares - Side_1_SparesCount; + // check to see whether we have ANY spare sectors remaining + if(!Side_0_SparesCount && !Side_1_SparesCount) { + CartridgeStatus = DISK_TEST_FAILURE; + return; + } + // MLT: The code for removing the ZIP protection has been omitted + return; // return zero since no error + } + else { + // trouble of some sort ... so suppress controls and + // show the richedit control for the trouble + if (eax == DEFECT_LIST_READ_ERROR) { + CartridgeStatus = DISK_Z_TRACK_FAILURE; + return; + } + else if (eax == MEDIA_NOT_PRESENT) { + CartridgeStatus = MEDIA_NOT_PRESENT; + } + } +} + +/******************************************************************************* + * GET ELAPSED TIME IN SECONDS + *******************************************************************************/ +long GetElapsedTimeInSeconds() { + return GetSystemTime() - StartingInstant; +} + +/******************************************************************************* + * PREPARE TO BEGIN TESTING + *******************************************************************************/ +void PrepareToBeginTesting() { + // Zero all of the testing variables + TestingPhase = 0; // 0 = not testing, no data ... + PercentComplete = 0; + FirstLBASector = 0; + NumberOfLBAs = 0; + SoftErrors = 0; + FirmErrors = 0; + HardErrors = 0; + UserInterrupt = 0; + LastError = 0; + #ifdef DEMO + LastLBAOnCartridge = 99999; + SoftErrors = 6; + FirmErrors = 2; + HardErrors = 1; + UserInterrupt = 0; + LastError = 0x0C8001; + Side_0_SparesCount = 12; + Side_1_SparesCount = 20; + #endif +} + +/******************************************************************************* + * BUMP ERROR COUNTS + * + * See: https://en.wikipedia.org/wiki/Key_Code_Qualifier + *******************************************************************************/ +void BumpErrorCounts(long ErrorCode) { + long eax = ErrorCode; + if (eax == BUFFER_TOO_BIG) { // if we got BUFFER TOO BIG, halt! + UserInterrupt = 1; + } + long ebx = eax & 0x00FF00FF; // mask off the middle byte + if (ebx == 0x00150004) // if it was one of the many seek + eax = ebx; // errors, cvrt to seek error + if (eax) + LastError = eax; + if (eax == 0x320003 || eax == 0x328F03) + CartridgeStatus = DISK_LOW_SPARES; + if (eax & 0xFF == 1) // recovered error + SoftErrors++; + else + HardErrors++; +} + +/******************************************************************************* + * PERFORM REGION TRANSFER + *******************************************************************************/ +long PerformRegionTransfer(short XferCmd, void *pBuffer) { + return -1; + char Scsi[10] = {0}; // clear out the SCSI CDB + const long InitialHardErrors = HardErrors; + + SetErrorRecovery(false, false, true); // disable Retries & ECC + + Scsi[0] = XferCmd; + DWORD_AT(Scsi[2]) = MAKE_BIG_ENDIAN(FirstLBASector); // WHICH LBA's to read, BIG endian + WORD_AT(Scsi[7]) = MAKE_BIG_ENDIAN(NumberOfLBAs); // HOW MANY to read, BIG endian + long eax = SCSICommand(CurrentDevice, Scsi, pBuffer, NumberOfLBAs * BYTES_PER_SECTOR); + + return 1; + // if we failed somewhere during our transfer ... let's zero in on it + if (eax) { + if ( eax == SS_ERR || // if it's a CONTROLLER ERROR, skip! + eax == BUFFER_TOO_BIG || + eax == LBA_TOO_LARGE) { + goto Exit; + } + + //-------------------------------------------------------------------------- + // Save error and current Soft + Hard Error count to see if we do FIND the glitch ... + const long GlitchError = eax; // save the error which stopped us! + const long GlitchCount = SoftErrors + HardErrors; + char *LocalBuffer = (char*) pBuffer; + ErrorSound(); + + SingleTransferLBA = FirstLBASector; + + // Perform transfer LBA block at a time + for(long i = 0; i < NumberOfLBAs; ++i) { + UpdateCurrentSector(); + + // setup for our series of transfer tests ... + + // disable all recovery techniques + SetErrorRecovery(false, false, true); // disable Retries & ECC + + memset(Scsi, 0, sizeof(Scsi)); // clear out the SCSI CDB + Scsi[0] = XferCmd; + DWORD_AT(Scsi[2]) = MAKE_BIG_ENDIAN(SingleTransferLBA); // WHICH LBA to read, BIG endian + WORD_AT(Scsi[7]) = MAKE_BIG_ENDIAN(1); // a single sector + eax = SCSICommand(CurrentDevice, Scsi, LocalBuffer, BYTES_PER_SECTOR); + + if (eax) { + // some sort of problem encountered! + if (eax == SS_ERR) goto Exit; // if it's a CONTROLLER ERROR, skip! + if (eax & 0xFF == 1) goto PostTheError; // did we recover? + + SetErrorRecovery(true, false, true); // enable retries + eax = SCSICommand(CurrentDevice, Scsi, LocalBuffer, BYTES_PER_SECTOR); + if (eax) { + // failed with retries + if (eax == SS_ERR) goto Exit; // if it's a CONTROLLER ERROR, skip! + if (eax & 0xFF == 1) goto PostTheError; // did we recover? + + SetErrorRecovery(true, true, true); // enable retries AND EEC + eax = SCSICommand(CurrentDevice, Scsi, LocalBuffer, BYTES_PER_SECTOR); + if (eax) { + // failed with retries and EEC + if (eax == SS_ERR) goto Exit; // if it's a CONTROLLER ERROR, skip! + if (eax & 0xFF == 1) goto PostTheError; // did we recover? + } + else { // succeeded with ECC + eax = 0x180101; // "ECC & Retries" + } + } // succeeded with retries + else { + eax = 0x170101; // "Read with Retries" + if (XferCmd == SCSI_Cmd_WriteMany) + eax = 0x0C8001; // "Wrote with Retries" + } + + PostTheError: + BumpErrorCounts(eax); // given eax, count the errors + GetSpareSectorCounts(false); // update the Cart's Condition + UpdateRunTimeDisplay(); + + LocalBuffer += BYTES_PER_SECTOR; + SingleTransferLBA++; + if(UserInterrupt) break; + } + ProcessPendingMessages(); + } + + // now see whether we *did* found something to complain about ... + eax = SoftErrors + HardErrors; + if (eax == GlitchCount) { + // we missed it ... but SOMETHING happened! So let's report it ... + const long SavedSoftErrors = SoftErrors; // save the existing counts + const long SavedHardErrors = HardErrors; + eax = GlitchError; // get the error that triggered our search + long ebx = eax & 0x00FF00FF; // strip the ASCQ byte + if(ebx == 0x00110003) // if we're about to say "unrecovered read" + eax = 0x170101; // change it to: "Read with Retries" + BumpErrorCounts(eax); // given eax, count the errors + HardErrors = SavedHardErrors; // restore the counts + SoftErrors = SavedSoftErrors; + SoftErrors++; + UpdateRunTimeDisplay(); + } + SingleTransferLBA = 0; + eax = 0; // now let's return happiness to our caller + if (HardErrors != InitialHardErrors) // UNRECOVERABLE errors! + eax = -1; + } + +Exit: + SetErrorRecovery(true, true, false); // reenable Retries & ECC + return eax; +} + +/******************************************************************************* + * TEST THE DISK + *******************************************************************************/ + +long TestTheDisk() { + void *pPatternBuffer = malloc(MAX_SECTORS_PER_TEST * BYTES_PER_SECTOR); + void *pUserDataBuffer = malloc(MAX_SECTORS_PER_TEST * BYTES_PER_SECTOR); + + if(pPatternBuffer == NULL || pUserDataBuffer == NULL) { + printf("Allocation error\n"); + return -1; + } + + CartridgeStatus = DISK_TEST_UNDERWAY; + TestingPhase = TESTING_STARTUP; // inhibit stopping now + SetButtonText(szPressToStop); + + LockCurrentDrive(); // prevent media removal + + // Standard Testing Operation + StartingInstant = GetSystemTime(); + + do { + ProcessPendingMessages(); + + NumberOfLBAs = MAX_SECTORS_PER_TEST; + + if(LastLBAOnCartridge) { + if (FirstLBASector + NumberOfLBAs > LastLBAOnCartridge + 1) { + NumberOfLBAs = LastLBAOnCartridge - FirstLBASector + 1; + } + // compute the percentage complete + PercentComplete = FirstLBASector * 100 / LastLBAOnCartridge; + } + + if(NumberOfLBAs == 0) break; + + // uppdate the elapsed time + SecondsElapsed = GetElapsedTimeInSeconds(); + + // get a random pattern of data to write + const long DataPattern = rand(); + memset(pPatternBuffer, DataPattern, sizeof(pPatternBuffer)); + + // update the cartridge's status + GetSpareSectorCounts(false); // update the Cart's Condition + + TestingPhase = READING_DATA; + + UpdateRunTimeDisplay(); + + long eax = PerformRegionTransfer(SCSI_Cmd_ReadMany, pUserDataBuffer); + + if(eax == 0) { + // ------------------------------- + TestingPhase = WRITING_PATT; + UpdateRunPhaseDisplay(); + PerformRegionTransfer(SCSI_Cmd_WriteMany, pPatternBuffer); + // ------------------------------- + TestingPhase = READING_PATT; + UpdateRunPhaseDisplay(); + PerformRegionTransfer(SCSI_Cmd_Verify, pPatternBuffer); + // ------------------------------- + TestingPhase = WRITING_DATA; + UpdateRunPhaseDisplay(); + PerformRegionTransfer(SCSI_Cmd_Verify, pUserDataBuffer); + } + else if (eax == LBA_TOO_LARGE) { + // if we hit the end of the disk ... exit gracefully! + goto GetOut; + } + if (CartridgeStatus != DISK_TEST_UNDERWAY) { + break; + } + // bump the FirstLBASector up for the next transfer + FirstLBASector += NumberOfLBAs; + } while(!UserInterrupt); + // show that we're post-test + +GetOut: + free(pPatternBuffer); + free(pUserDataBuffer); + + TestingPhase = UNTESTED; + UnlockCurrentDrive(); + SetErrorRecovery(true, true, false); // reenable Retries & ECC + SetButtonText(szPressToStart); + CartridgeStatus = DISK_AT_SPEED; + UpdateRunTimeDisplay(); // added by mlt + + // compute the number of serious troubles + long errors = FirmErrors + HardErrors; + if (errors >= BADNESS_THRESHOLD) { + return szBadResult; + } + else if (UserInterrupt) { + return szInterrupted; + } + else { + // it wasn't interrupted, nor seriously bad, was it perfect? + errors += SoftErrors; + if(errors) { + return szExplainResult; + } else { + return szPerfectResult; + } + } +} diff --git a/mac-cpp-source/tip/tip_main.cpp b/mac-cpp-source/tip/tip_main.cpp new file mode 100644 index 0000000..6a3b2d5 --- /dev/null +++ b/mac-cpp-source/tip/tip_main.cpp @@ -0,0 +1,370 @@ +#include +#include +#include +#include +#include + +#include +#include + +#include "tip.h" + +WindowPtr tipWindow; + +static int gDone; +static bool AllowColor; +static ControlHandle actionBtn = 0; + +void NewTipWindow(); +void DestroyTipWindow(); +void DoEvent(EventRecord &event, RgnHandle *cursorRgn); +void DoUpdate(WindowPtr window); +void DoMouseDown(EventRecord &event); +void DoMouseMove(EventRecord &event, RgnHandle *cursorRegion); + +void run_tip(void) { + RgnHandle cursorRgn = NewRgn(); + + NewTipWindow(); + gDone = false; + do { + EventRecord event; + if (WaitNextEvent(everyEvent, &event, GetCaretTime(), cursorRgn)) { + DoEvent(event, &cursorRgn); + } + } while (!gDone); + + DestroyTipWindow(); + DisposeRgn(cursorRgn); +} + +void NewTipWindow() { + OSErr error; + SysEnvRec theWorld; + + error = SysEnvirons(1, &theWorld); + AllowColor = theWorld.hasColorQD; + + Rect rect = qd.screenBits.bounds; + InsetRect(&rect, 50, 50); + rect.bottom = rect.top + 336 - 35; + rect.right = rect.left + 467; + + Str255 title; + StrToPascal(title, szWindowTitle); + tipWindow = AllowColor ? + NewCWindow(NULL,&rect, title, true, 0, 0, true, 0) : + NewWindow(NULL,&rect, title, true, 0, 0, true, 0); + + GetDC(hMainWnd); + + if (AllowColor) { + SetColor(BACK_COLOR); + RGBColor bgColor; + GetForeColor(&bgColor); + RGBBackColor(&bgColor); + } + TextSize(10); + + for(int i = 0; tipBtns[i].name; i++) { + if (!tipBtns[i].visible) continue; + SetRect(&rect, + tipBtns[i].x, + tipBtns[i].y, + tipBtns[i].x + tipBtns[i].w, + tipBtns[i].y + tipBtns[i].h + ); + + StrToPascal(title, tipBtns[i].name); + ControlHandle h = NewControl(tipWindow, &rect, title, true, 0, 0, 0, 0, tipBtns[i].id); + if(tipBtns[i].id == IDB_TEST) { + actionBtn = h; + } + } + + ReleaseDC(hMainWnd); +} + +void DestroyTipWindow() { + DisposeWindow(tipWindow); +} + +void DoEvent(EventRecord &event, RgnHandle *cursorRgn) { + if(!SIOUXHandleOneEvent(&event)) { + // If SIOUX didn't handle the event, then handle it ourselves + switch(event.what) { + case mouseDown: DoMouseDown(event); break; + case updateEvt: DoUpdate((WindowPtr)event.message); break; + case osEvt: DoMouseMove(event, cursorRgn); break; + } + } + +} + +void DoUpdate(WindowPtr window) { + BeginUpdate(window); + SetPort(window); + EraseRect(&window->portRect); + + GetDC(hMainWnd); + WndProc(WM_PAINT, 0); + DrawControls(window); + ReleaseDC(hMainWnd); + + GetDC(hTestMonitor); + TestMonitorWndProc(); + ReleaseDC(hTestMonitor); + + EndUpdate(window); +} + +void DoMouseDown(EventRecord &event) { + WindowPtr thisWindow; + ControlHandle thisControl; + int part = FindWindow(event.where, &thisWindow); + switch(part) { + case inSysWindow: + SystemClick(&event, thisWindow); + break; + case inContent: + if(thisWindow != FrontWindow()) { + SelectWindow(thisWindow); + } + else if(thisWindow == tipWindow) { + long id = 0; + Point mouse = event.where; + GetDC(hMainWnd); + GlobalToLocal(&mouse); + if(FindControl(mouse, thisWindow, &thisControl) == inButton) { + if(TrackControl(thisControl, mouse, 0) == inButton) { + id = GetControlReference(thisControl); + } + } + ReleaseDC(hMainWnd); + if(id) { + WndProc(WM_COMMAND, id); + } + } + break; + case inDrag: + DragWindow(thisWindow, event.where, &(*GetGrayRgn())->rgnBBox); + break; + case inGrow: + //DoGrowWindow(thisWindow, event); + break; + case inGoAway: + if (TrackGoAway(thisWindow, event.where)) { + gDone = true; + } + break; + } +} + +void DoMouseMove(EventRecord &event, RgnHandle *cursorRgn) { + WindowPtr thisWindow; + int part = FindWindow(event.where, &thisWindow); + if (thisWindow == tipWindow) { + InitCursor(); + // Set the cursorRegion to everything inside our window + if(cursorRgn) { + CopyRgn(((WindowPeek)tipWindow)->contRgn, *cursorRgn); + } + } +} + +void StrToPascal(Str255 pStr, const char *str) { + size_t len = strlen(str); + pStr[0] = (len > 255) ? 255 : len; + strncpy((char*)pStr + 1, str, 255); +} + +/******************************************************************************* + * SET COLOR + *******************************************************************************/ + +void SetColor(long color) { + if (AllowColor) { + if(color == BACK_COLOR) color = LTGRAY_COLOR; + // Use colors when available + RGBColor rgbColor; + rgbColor.red = (color & 0xFF0000) >> 8; + rgbColor.green = (color & 0x00FF00) >> 0; + rgbColor.blue = (color & 0x0000FF) << 8; + RGBForeColor(&rgbColor); + } else { + // Use patterns for B&W Macs + TextMode(srcCopy); + switch(color) { + case BACK_COLOR: + case WHITE_COLOR: + PenPat(&qd.white); + TextMode(srcBic); + break; + case BLACK_COLOR: + case GRAY_COLOR: + PenPat(&qd.black); + break; + case RED_COLOR: + case BLUE_COLOR: + PenPat(&qd.ltGray); + break; + case LTGRAY_COLOR: + case GREEN_COLOR: + PenPat(&qd.gray); + break; + } + } +} + +void SetColor(long color, long monoColor) { + if (AllowColor) { + SetColor(color); + } else { + SetColor(monoColor); + } +} + +/******************************************************************************* + * DRAW LED + *******************************************************************************/ + +void DrawLed(int x, int y, long color) { + Rect ledRect; + SetRect(&ledRect, x, y, x + 12, y + 12); + // Draw the LED + SetColor(color); + PaintOval(&ledRect); + if (AllowColor) { + // Draw a recessed outline + SetColor(BLACK_COLOR); + FrameOval(&ledRect); + SetColor(WHITE_COLOR); + FrameArc(&ledRect,45,180); + } else { + // Draw a non-recessed outline + SetColor(BLACK_COLOR); + FrameOval(&ledRect); + SetColor(WHITE_COLOR); + } + // Draw the reflection + if(color != BACK_COLOR) { + ledRect.left += 3; + ledRect.top += 3; + ledRect.right -= 7; + ledRect.bottom -= 7; + OffsetRect(&ledRect,0,1); + PaintRect(&ledRect); + OffsetRect(&ledRect,1,-1); + PaintRect(&ledRect); + } +} + +/******************************************************************************* + * DRAW EDGE + *******************************************************************************/ + +void DrawEdge(Rect *qrc, int edge, int grfFlags) { + if(edge == BDR_SUNKENOUTER && AllowColor) { + // Draw a sunken rectangle + SetColor(GRAY_COLOR); + MoveTo(qrc->left,qrc->bottom-1); + LineTo(qrc->left,qrc->top); + LineTo(qrc->right-1,qrc->top); + SetColor(WHITE_COLOR); + MoveTo(qrc->left,qrc->bottom-1); + LineTo(qrc->right-1,qrc->bottom-1); + LineTo(qrc->right-1,qrc->top); + } else { + // Draw a non-recessed rectangle + SetColor(LTGRAY_COLOR); + FrameRect(qrc); + } +} + +/******************************************************************************* + * RECTANGLE + *******************************************************************************/ + +void Rectangle(int left, int top, int right, int bottom) { + Rect rect; + SetRect(&rect, left, top, right, bottom); + PaintRect(&rect); +} + +/******************************************************************************* + * TEXT OUT + *******************************************************************************/ + +void TextOut(int x, int y, Str255 pStr) { + FontInfo info; + GetFontInfo(&info); + MoveTo(x, y + info.ascent + info.descent); + DrawString(pStr); +} + +void TextOut(int x, int y, const char *str) { + Str255 pStr; + StrToPascal(pStr, str); + TextOut(x, y, pStr); +} + +void TextOutCentered(int x, int y, int w, int h, const char *str) { + // convert to Pascal string + Str255 pStr; + StrToPascal(pStr, str); + // now place the floating string in the middle + const int width = TextWidth(pStr, 0, strlen(str)); + if(width < w) { + TextOut(x + w/2 - width/2, y, pStr); + } +} + +/******************************************************************************* + * GET TEXT EXTENT + *******************************************************************************/ +long GetTextExtent(const char *str, unsigned long len) { + // convert to Pascal string + Str255 pStr; + StrToPascal(pStr, str); + // now place the floating string in the middle + return TextWidth(pStr, 0, len); +} + +/******************************************************************************* + * GET SYSTEM TIME + *******************************************************************************/ +unsigned long GetSystemTime() { + unsigned long time; + GetDateTime (&time); + return time; +} + +/******************************************************************************* + * SET BUTTON TEXT + *******************************************************************************/ +void SetButtonText(const char *str) { + Str255 pStr; + StrToPascal(pStr, str); + if(actionBtn) { + GetDC(hMainWnd); + SetCTitle(actionBtn, pStr); + ReleaseDC(hMainWnd); + } +} + +/******************************************************************************* + * POST QUIT MESSAGE + *******************************************************************************/ +void PostQuitMessage() { + gDone = true; +} + +/******************************************************************************* + * PROCESS PENDING MESSAGES + *******************************************************************************/ +void ProcessPendingMessages() { + EventRecord event; + if (GetNextEvent(everyEvent, &event)) { + DoEvent(event,NULL); + } +} \ No newline at end of file diff --git a/mac-cpp-source/tip/tip_text.cpp b/mac-cpp-source/tip/tip_text.cpp new file mode 100644 index 0000000..a65a1d4 --- /dev/null +++ b/mac-cpp-source/tip/tip_text.cpp @@ -0,0 +1,145 @@ +#include "tip.h" + +const char *szWindowTitle = "TIP 2.1b -- Zip & Jaz Drive and Cartridge Testing System"; +const char *szCopyright_1 = "Copyright (c) 2006 by"; +const char *szCopyright_2 = "Gibson Research Corp."; + +const char *szSide0 = "Side 0"; +const char *szSide1 = "Side 1"; +const char *szSpaceDashSpace = " - "; + +const char *szBarChartPercent = " %ld%% "; +const char *szCenteredDecimal = "%ld"; +const char *szCenteredHex = "ErrorCode: %06lX"; +const char *szHoursMinsSecs = "%ld:%02ld:%02ld"; + +const char *szCartStatus = "Cartridge Status:"; +const char *szEstimating = "Estimating ..."; +const char *szOneMoment = "--- --- ---"; +const char *szPressToStart = "Press to Begin"; +const char *szPressToStop = "Press to Stop"; +const char *szPressToSpin = "Press to Spin Up"; +const char *szPressToEject = "Press to Eject"; +const char *szPressToProceed = "Press to Proceed"; + +/************* Cartridge Status Text *************/ + +const char *szUnknownStat = "Ejecting Cartridge"; +const char *szAtSpeedStat = "Ready to Test"; +const char *szSpinningUp = "Spinning Up"; +const char *szNotPresent = "Awaiting Cartridge"; +const char *szSpunDown = "Not Spinning"; +const char *szStalledStat = "Stalled Error"; +const char *szZtrackFailure = "Z-Tracks Failure !!"; +const char *szDiskLocked = "Disk Protected"; +const char *szLowSpares = "Low Spares Count"; +const char *szTestUnderway = "Testing Drive "; +const char *DriveUnderTest = "X: ..."; +const char *szTestFailure = "Testing Failed"; +const char *szNoIomegaDrives = "No Iomega Drives"; + +const char *CartStatStrings[] = { + szUnknownStat, szAtSpeedStat, szSpinningUp, + szNotPresent, szSpunDown, szStalledStat, + szZtrackFailure, szDiskLocked, szLowSpares, + szTestUnderway, szTestFailure +}; + +ErrorTypeList errorTypeList[] = { + 0x00000000, "-- None So Far --", + + 0x001C0000, "Missing Defect List", + + 0x000C0101, "Wrote with Realloc", + 0x000C8001, "Wrote with Retries", + 0x000C8101, "Wrote with Off Track", + 0x000C8201, "Wrote w/o SectorMark", + 0x000C8301, "Wrote with ID skip", + 0x00170101, "Read with Retries", + 0x00170601, "Retried & Realloc", + 0x00180001, "Data Corrected", + 0x00180101, "ECC & Retries", + 0x00180201, "ECC & Realloc'd", + 0x001C8F01, "Defect List Recvr'd", + + 0x00040002, "Drive Not Ready", + 0x00040102, "Drive Going Ready", + 0x00040202, "Drive Not Ready #2", + 0x00040302, "Drive Needs Help", + 0x00040402, "Not Rdy - Formating", + 0x00300002, "Incompatible Media", + 0x003A0002, "Media Not Present", + + 0x00010003, "Missing Sector Mark", + 0x00030003, "Off Track Write", + 0x00100003, "Bad ID Checksum", + 0x00110003, "Unrecovered Read", + 0x00118003, "Unrecovered Read", + 0x00120003, "MissingID Sync", + 0x00130003, "Missing Addr Mark", + 0x00140003, "Sector Not Found", + 0x00160003, "Sync Mark Missing", + 0x001C0003, "Defect List Error", + 0x00310003, "Corrupt Media Format", + 0x00310103, "Format Command Fail", +// 0x0031xx03, " -- failures --" + 0x00320003, "No Spare Sectors", // abort on this + 0x00328F03, "No Spare Tracks", // abort on this + + 0x00018104, "Missing Sector Pulse", + 0x00090004, "Track Follow Error", + 0x00150004, "Head Seek Failure", + 0x00220004, "Cartridge Sense Fail", +// 0x0040xx04, "Self Test Failed", + 0x00470004, "Data Parity Error", +// 0x00xx0004, "Vendor Specific Error", + + 0x00290006, "I/O Bus Reset Error", + + 0x0088010B, "Reassigned Blk Err", + 0x0088020B, "Side Switch Error", + 0x00FFFFE6, "Buffer Too Big", + + 0xFFFFFFFF, "-- Unknown Error --", + + 0, 0 +}; + +/****************** Control Text *****************/ + +const char *szBack = "< Back"; +const char *szNext = "Next >"; +const char *szQuit = "Exit"; + +/******************************************************************************* + * PERFORM_TEST_PAGE + *******************************************************************************/ + +TextList TestBlackText[] = { + {250, 2, "Data Read"}, + {350, 2, "Patt Write"}, + {250, 18, "Data Write"}, + {350, 18, "Patt Read"}, + {11, 39, "0%"}, + {377, 39, "100%"}, + { 11, 77, "0"}, + {377, 77, "100%"}, + { 22, 154, "Sectors"}, + { 10, 171, "Last Error"}, + { 17, 188, "Elapsed"}, + { 13, 205, "Time Left"}, + {278, 154, "Soft Errors"}, + {275, 171, "Firm Errors"}, + {272, 188, "Hard Errors"}, + {271, 205, "Total Errors"}, + {0,0,0} +}; + +TextList TestGrayText[] = { + {155, 39, "Testing Progress"}, + {129, 77, "Spare Sectors Consumed"}, + {61, 135, "Testing Location"}, + /*{219, 135, "Sound"},*/ + {297, 135, "Error Summary"}, + {0,0,0} +};