From 0f41d8fed0c910cb65b80025c3cbc15ebf86cee0 Mon Sep 17 00:00:00 2001 From: Gwenhael Le Moine Date: Tue, 19 Mar 2024 22:36:03 +0100 Subject: [PATCH] 2000-07-16: Updated to version 1.20 --- CARDCOPY.EXE | Bin 0 -> 28672 bytes CARDCOPY.TXT | 57 ++ CP_48G3.KML | 66 +- CP_48S3.KML | 64 +- Convert.exe | Bin 22528 -> 32768 bytes DEBUGGER.TXT | 78 +- EMU48.EXE | Bin 221184 -> 233472 bytes EMU48.TXT | 162 +++-- PROBLEMS.TXT | 43 +- sources/Cardcopy/CARDCOPY.C | 139 ++++ sources/Cardcopy/CARDCOPY.DSP | 100 +++ sources/Cardcopy/CARDCOPY.DSW | 29 + sources/Convert/{Convert.mak => CONVERT.MAK} | 0 sources/Convert/{Convert.mdp => CONVERT.MDP} | Bin sources/Convert/{Main.c => MAIN.C} | 205 ++++-- sources/EMU48.TXT | 162 +++-- sources/Emu48/CHANGES.TXT | 366 +++++++++- sources/Emu48/DEBUGGER.C | 712 ++++++++++++++----- sources/Emu48/DEBUGGER.H | 25 +- sources/Emu48/DISASM.C | 28 +- sources/Emu48/DISPLAY.C | 35 +- sources/Emu48/EMU48.C | 301 ++++---- sources/Emu48/EMU48.DSP | 15 +- sources/Emu48/EMU48.H | 38 +- sources/Emu48/EMU48.RC | 262 ++++--- sources/Emu48/ENGINE.C | 279 +++++--- sources/Emu48/EXTERNAL.C | 21 +- sources/Emu48/FILES.C | 639 ++++++++++------- sources/Emu48/I28F160.C | 696 ++++++++++++++++++ sources/Emu48/I28F160.H | 40 ++ sources/Emu48/IO.H | 32 + sources/Emu48/KEYBOARD.C | 16 +- sources/Emu48/KML.C | 9 +- sources/Emu48/MOPS.C | 422 ++++++++--- sources/Emu48/OPCODES.C | 57 +- sources/Emu48/OPCODES.H | 9 +- sources/Emu48/OPS.H | 76 +- sources/Emu48/RESOURCE.H | 218 +++--- sources/Emu48/RPL.C | 48 +- sources/Emu48/SERIAL.C | 40 +- sources/Emu48/SETTINGS.C | 14 +- sources/Emu48/TIMER.C | 48 +- sources/Emu48/TYPES.H | 33 +- 43 files changed, 4176 insertions(+), 1408 deletions(-) create mode 100644 CARDCOPY.EXE create mode 100644 CARDCOPY.TXT create mode 100644 sources/Cardcopy/CARDCOPY.C create mode 100644 sources/Cardcopy/CARDCOPY.DSP create mode 100644 sources/Cardcopy/CARDCOPY.DSW rename sources/Convert/{Convert.mak => CONVERT.MAK} (100%) rename sources/Convert/{Convert.mdp => CONVERT.MDP} (100%) rename sources/Convert/{Main.c => MAIN.C} (51%) create mode 100644 sources/Emu48/I28F160.C create mode 100644 sources/Emu48/I28F160.H diff --git a/CARDCOPY.EXE b/CARDCOPY.EXE new file mode 100644 index 0000000000000000000000000000000000000000..d8f6abadf7dcf7a39d090f213769502fc8ba8423 GIT binary patch literal 28672 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 UvIZ_|;IalTYv8g5{!tD5Z~H9| + +Some examples: + +- SX.E48 , an Emu48 state file for a HP48SX +- GX.E48 , an Emu48 state file for a HP48GX +- RPLTOOLS.BIN , a 128KB port2 file + +If you want to +- export the port1 content of SX.E48 to RPLTOOLS.BIN then type + CARDCOPY SX.E48 RPLTOOLS.BIN +- import the RPLTOOLS.BIN as port1 into GX.E48 then type + CARDCOPY RPLTOOLS.BIN GX.E48 +- export the port1 content of SX.E48 to GX.48 then type + CARDCOPY SX.E48 GX.E48 + + + *************** + * LEGAL STUFF * + *************** + +CARDCOPY - A HP48 Port1 Import/Export Tool for Emu48 +Copyright (C) 2000 Christoph Gießelink + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the license with this program in the file +"COPYING". If not, write to the Free Software Foundation, Inc., +59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. diff --git a/CP_48G3.KML b/CP_48G3.KML index 01d26b8..0363bd5 100644 --- a/CP_48G3.KML +++ b/CP_48G3.KML @@ -34,7 +34,7 @@ End Lcd Zoom 2 Offset 20 52 - Color 0 255 255 255 + Color 0 255 255 255 # character color table Color 1 255 255 255 Color 2 255 255 255 Color 3 255 255 255 @@ -42,22 +42,22 @@ Lcd Color 5 255 255 255 Color 6 255 255 255 Color 7 255 255 255 - Color 8 240 240 240 - Color 9 225 225 225 - Color 10 210 210 210 - Color 11 195 195 195 - Color 12 180 180 180 - Color 13 165 165 165 - Color 14 150 150 150 - Color 15 135 135 135 - Color 16 120 120 120 - Color 17 105 105 105 - Color 18 90 90 90 - Color 19 75 75 75 - Color 20 60 60 60 - Color 21 45 45 45 - Color 22 30 30 30 - Color 23 15 15 15 + Color 8 255 255 255 + Color 9 223 223 223 + Color 10 179 171 171 + Color 11 135 135 135 + Color 12 91 91 91 + Color 13 47 47 47 + Color 14 0 0 0 + Color 15 0 0 0 + Color 16 0 0 0 + Color 17 0 0 0 + Color 18 0 0 0 + Color 19 0 0 0 + Color 20 0 0 0 + Color 21 0 0 0 + Color 22 0 0 0 + Color 23 0 0 0 Color 24 0 0 0 Color 25 0 0 0 Color 26 0 0 0 @@ -66,6 +66,38 @@ Lcd Color 29 0 0 0 Color 30 0 0 0 Color 31 0 0 0 + Color 32 255 255 255 # background color table + Color 33 255 255 255 + Color 34 255 255 255 + Color 35 255 255 255 + Color 36 255 255 255 + Color 37 255 255 255 + Color 38 255 255 255 + Color 39 255 255 255 + Color 40 255 255 255 + Color 41 255 255 255 + Color 42 255 255 255 + Color 43 255 255 255 + Color 44 255 255 255 + Color 45 255 255 255 + Color 46 255 255 255 + Color 47 232 232 232 + Color 48 209 209 209 + Color 49 186 186 186 + Color 50 163 163 163 + Color 51 140 140 140 + Color 52 117 117 117 + Color 53 94 94 94 + Color 54 71 71 71 + Color 55 48 48 48 + Color 56 25 25 25 + Color 57 2 2 2 + Color 58 0 0 0 + Color 59 0 0 0 + Color 60 0 0 0 + Color 61 0 0 0 + Color 62 0 0 0 + Color 63 0 0 0 End Annunciator 1 diff --git a/CP_48S3.KML b/CP_48S3.KML index d99597d..43bbb9a 100644 --- a/CP_48S3.KML +++ b/CP_48S3.KML @@ -26,25 +26,25 @@ End Lcd Zoom 2 Offset 20 52 - Color 0 255 255 255 + Color 0 255 255 255 # character color table Color 1 255 255 255 Color 2 255 255 255 Color 3 240 240 240 - Color 4 225 225 225 - Color 5 210 210 210 - Color 6 195 195 195 - Color 7 180 180 180 - Color 8 165 165 165 - Color 9 150 150 150 - Color 10 135 135 135 - Color 11 120 120 120 - Color 12 105 105 105 - Color 13 90 90 90 - Color 14 75 75 75 - Color 15 60 60 60 - Color 16 45 45 45 - Color 17 30 30 30 - Color 18 15 15 15 + Color 4 216 216 216 + Color 5 192 192 192 + Color 6 168 168 168 + Color 7 144 144 144 + Color 8 120 120 120 + Color 9 96 96 96 + Color 10 72 72 72 + Color 11 48 48 48 + Color 12 24 24 24 + Color 13 0 0 0 + Color 14 0 0 0 + Color 15 0 0 0 + Color 16 0 0 0 + Color 17 0 0 0 + Color 18 0 0 0 Color 19 0 0 0 Color 20 0 0 0 Color 21 0 0 0 @@ -58,6 +58,38 @@ Lcd Color 29 0 0 0 Color 30 0 0 0 Color 31 0 0 0 + Color 32 255 255 255 # background color table + Color 33 255 255 255 + Color 34 255 255 255 + Color 35 255 255 255 + Color 36 255 255 255 + Color 37 255 255 255 + Color 38 255 255 255 + Color 39 255 255 255 + Color 40 255 255 255 + Color 41 255 255 255 + Color 42 255 255 255 + Color 43 255 255 255 + Color 44 255 255 255 + Color 45 255 255 255 + Color 46 230 230 230 + Color 47 205 205 205 + Color 48 180 180 180 + Color 49 155 155 155 + Color 50 130 130 130 + Color 51 105 105 105 + Color 52 80 80 80 + Color 53 55 55 55 + Color 54 30 30 30 + Color 55 5 5 5 + Color 56 0 0 0 + Color 57 0 0 0 + Color 58 0 0 0 + Color 59 0 0 0 + Color 60 0 0 0 + Color 61 0 0 0 + Color 62 0 0 0 + Color 63 0 0 0 End Annunciator 1 diff --git a/Convert.exe b/Convert.exe index ab927ea004669736ef7339a3c15ed35e1242bded..01117ec87fb60fdcf5b76a4e2fcb177958deddd7 100644 GIT binary patch literal 32768 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 Y73)fHCAu;eWiRqjU%$ToM`_^y0^R`+=Kufz diff --git a/DEBUGGER.TXT b/DEBUGGER.TXT index b554875..5bf0e56 100644 --- a/DEBUGGER.TXT +++ b/DEBUGGER.TXT @@ -1,7 +1,7 @@ Debugger in Emu48/Tools/Debugger... ----------------------------------- -This is a short description of the internal debugger of Emu48. The debugger is not perfect and/or complete. I provide it as it is. Please don't send me any suggestions or changes at the moment. If you have a better idea make it for yourself. Of course you're free to publish them or send me your changes. +This is a short description of the internal assembly debugger of Emu48. The debugger is not perfect and/or complete. I provide it as it is. Please don't send me any suggestions or changes at the moment. If you have a better idea make it for yourself. Of course you're free to publish them or send me your changes. The debugger was designed to help costumers inspecting assembler code objects, a part that cannot be handled satisfactorily by the JAZZ package. Thanks to Mika Heiskanen and all the others supporting this great program. @@ -32,19 +32,19 @@ The program counter will never reach the address behind the GOSUB instruction. T - Step Out F9 -Continue the program until a RTI, RTN, RTNC, RTNCC, RTNNC, RTNSC, RTNSXN, RTNYES instruction is found above the current stack level. +Continue the program until a RTI, RTN, RTNC, RTNCC, RTNNC, RTNSC, RTNSXN, RTNYES instruction is found above the current stack level. At some code constructions (mostly used to save space on the hardware stack) like C=RSTK PC=C - + and C=RSTK RSTK=C RTN - + the stop address will be wrong. The problem in both code fragments is the C=RSTK opcode. In the first example there is no RTN instruction to stop. In the second one the C=RSTK instruction purge the original return address and then the RSTK=C instruction is interpreted as a GOSUB instruction. In opposite the following code will work fine: @@ -73,15 +73,20 @@ Toggle a code breakpoint at the cursor position in the code window. - Edit breakpoints... -You get a sorted list of the current code breakpoints. With "Add" you can add a new, with "Delete" you can delete an existing breakpoint. Addresses greater than #FFFFF are cutted after the fifths nibble. +You get a sorted list of the current code and memory breakpoints. With "Add" you can add a new, with "Delete" you can delete an existing breakpoint. Addresses greater than #FFFFF are cutted after the fifths nibble. When adding a new breakpoint, you must select if this is a "Code", "Memory Access", "Memory Read" or "Memory Write" breakpoint. + + - "Code" stop before opcode execution on this address + - "Memory Access" stop before reading or writing to the selected address + - "Memory Read" stop before reading the selected address + - "Memory Write" stop before writing to the selected address - Clear all breakpoints -Clear all code breakpoints except the NOP3 ones. +Clear all breakpoints except the NOP3 ones. - NOP3 code breakpoints -What are NOP3 code breakpoints? As you now user programs are loaded somewhere in memory and can be moved after a garbage collection. So it's very difficult to break a user program at a hard set breakpoint with F2. To solve this problem the debugger will stop emulation at a NOP3 opcode. So you can easily add a NOP3 command into your sources to force a break condition. To enable this you have to check this item. +What are NOP3 code breakpoints? As you know user programs are loaded somewhere in memory and can be moved after a garbage collection. So it's very difficult to break a user program at a hard set breakpoint with F2. To solve this problem the debugger will stop emulation at a NOP3 opcode. So you can easily add a NOP3 command into your sources to force a break condition. To enable this you have to check this item. NOP3 and NOP3, what's the difference? The Saturn CPU has no NOP command, so NOP3 is an opcode that is three nibbles long and doesn't change a register. In the HP SASM.DOC document two different opcodes are defined for NOP3: @@ -90,7 +95,7 @@ NOP3 and NOP3, what's the difference? The Saturn CPU has no NOP command, so NOP3 and Opcode 420 for GOC + (next line) - + In the assembler of the HPTOOLS 3.x package NOP3 is defined as opcode 820. The advantage of the opcode is that the execution time is always the same, independent from the carry flag. This code is used in the HP48 ROM as well. So I decided to use the GOC opcode for a code breakpoint condition. A short example: @@ -100,12 +105,12 @@ ASSEMBLE BREAK MACRO CON(3) #024 NOP3 - ENDM + ENDM RPL CODE BREAK code breakpoint - + GOSBVL =SAVPTR save register GOSUB + problem for step over @@ -115,8 +120,28 @@ CODE GOVLNG =GETPTRLOOP ENDCODE +- RPL breakpoints -3.) Code window +If this item is checked, the debugger stops program execution on every A=DAT0 A, D0=D0+ 5, PC=(A) sequence. This is the typical RPL exit sequence, so you're able to jump easily to the end of any RPL command. + + +3.) Menu Interrupts + +- Step Over Interrupts + +If this item is checked, interrupt handler code will be skipped. This option is useful when you don't want to debug the interrupt handler. But be careful, when you disable the interrupts all code until interrupt enable belong to the interrupt handler code and could't executed in single step any more. Code and memory breakpoints are still active. + +You can also use this option if you want to quit the interrupt handler. Just check this option, press F7 for "Step Into", and uncheck the option again. The debugger stopped behind the RTI instruction. + + +4.) Menu Info + +- Last Instructions... + +This is a short viewer for the last 255 executed CPU addresses. The disassembled opcode maybe wrong, because only the CPU address of each command was saved and memory mapping may have changed meanwhile. + + +5.) Code window This windows shows you the disassembled code. The line with the current PC is marked with a "->" between the address and the disassembly. @@ -141,16 +166,16 @@ Toggle a code breakpoint at the cursor position in the code window. Set the PC to the cursor position. Be careful with this command, you change the execution order of the commands! -4.) Register window +6.) Register window Here you can see the actual contents of the CPU registers. The values are only updated after a single step execution. With the left mouse button you change the content of the register. On bit registers like CY and Mode the state changes immediately without any request. -5.) Memory window +7.) Memory window -This windows shows you the memory content. +This windows shows the memory content. You can use the arrow, PAGE UP and PAGE DOWN keys to move the cursor to a memory position. With a double click on the left mouse button you change the content of the two addresses. When the memory position is read only (ROM) the content wouldn't change. @@ -177,13 +202,30 @@ Sets the cursor to the actual position of the D0 register. Sets the cursor to the return address placed in the top level of the stack. -6.) Stack window +8.) Stack window The content of the hardware stack is viewed here. -7.) Problems +9.) MMU window -The timers aren't updated in the single step mode at the moment. Please skip these parts with setting a code breakpoint behind the critical section and continue emulation with F5. +The configuration of the memory controllers is viewed here. The viewed addresses are the first configured addresses of the modules and may differ from the given address in the CONFIG command. -12/14/99 (c) by Christoph Gießelink, cgiess@swol.de +This example + + LC(5) #C0000 128KB size + CONFIG + LC(5) #98765 start address of modul + CONFIG + +will config a 128KB modul at address #80000 and not at the given address. So the MMU viewer will show you address #80000. + + +10.) Miscellaneous window + +The Miscellaneous window show you the internal state of the interrupt flag, the 1ms keyboard handler and the contents of the Bank Switcher latch. The Bank Switcher item is only enabled on a HP48GX and a HP49G calculator. Only these versions have a latch inside. You see the loaded value of the address lines A6-A0. You have to ignore the last bit (A0), because it isn't wired to the six bit latch. + +You can change the values by pressing the left mouse button over the old content. + + +06/13/00 (c) by Christoph Gießelink, cgiess@swol.de diff --git a/EMU48.EXE b/EMU48.EXE index 5e3fdd499b9360a3649f05b03d439908c3c91fa0..7442e9515662f7feae4918f2e14fdb7dce7b1589 100644 GIT binary patch literal 233472 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%0AwIY|0k44aTZ2DrTBztJ>`$Y36 z#%x?XZjLlF-JRj^wi@!=*L-XbEp6j=613y`_4W55H&;`VMMBNiEJ#Iqp#Ig{V8}vX zo3kABbSj=QQx4LcRo{kc%E7%q)IgPZvY9mt4|oZeS<<1yf5UOI=4z#?mZOl3Zmm?t zS#_cmh9B@F8YUt`{X*E0{!l&12X{6z71PwIYwh6>^mYC&hX)Kp69>>bNGCm~U4 z>B(RjwQKH~Bk4PtH3Q|Mtp%(hbVX2CkhTf*kvt;;(+QC3nMT?F6C_kWrW>s#KBkjp z|5~JqOudh3fCZ2UTYCvBq>if6xfY|_Dxlx9smGk*JxKbhND?<+i@kAaH_Q|TkCqE- zXNA4pX$=G;v-r&bJz#wWH=Kbkq9PcCxGRo!xQSvLB`I5CbFsp=0UT_?LgEG4Hy_k3 zB>PhSw?Cro!kg&whtCrj^Zw=?xK>^VI62q51=laf2?iStN0^06SYJJj$x>I&EQeH4 zwb}_m`Y=qdaM;?7EZ%{z=SpUx(aEedp!x7DaV!+TeF@#IS$ z{k&eiw>{1Kej2mEyg&Qk|~W+qbL z!k~1B51KkKg+zoIDT|PTEE%}Otft)dFzAyDND2b8%>h#;kP2IiRl=ezn$mM&QUoR~ zw=I{O`&txpsoXpewi3&7esve9yt>w?@#-VEB+@gJ8FgYVTP0OiRMG5ZhNzu;Z+O+< zw0aBAgxL&b0(^;?3TFTqdEIE#D*LvkNzxQECHR(N)>JI}jno>zr#_j3Q_W zI?Fg0%f9z8h=k=)`-A7d9u9j4%<{UOs0*_AS&Sc&;9M31$l+!2YV;t6nz=M8iy@h- zbRhY~vRIQ&e>xWqCkTtEOn?KyMCu6bUnD%V8djqQC@RPw_oi zJ|uI9a7ERsArS|^X(jT&X|h$`5TaqyK=-E(zys86bh^F?Wdsr$fe^|&#Q2UY5Bxa} z7%*D@M3vLf=)@}}SI>&mV9zC_;E80eJYWHB!8>X{+s^uw1s{gPiQR;*RMkZcA$TP; zk4jRB;S*ItGp!_*G=Feukkpf@K{yrjN?>P{u>x*m#3A|&0D z(V=Q&$@QP$OV~&dy((|-PsnfIDkNtkY3G?{_b6}3FsrZ_H*1c2Pa7eV!9oxkE=|p= zmDXmB4DV$oa*M%^-4E;4)pFS2tkmyv1*lQ|TX@WEf59LCy~HIs98zi3s$uxN50eSi zT0yl-XzV*($B6hu>)1_ry>_Rvv7sN0a<1+Ui<a&+Qv}pBzl}xQ(Y#E9oPdRnG!}PCdi2iG#y6~?)vXYrFj*}Ie#ds#oQ)l?S$En zt|2X!pzXL=IQc7p7qKkRZOT{1@)dz1kaD>TrXF>WfdC#GwoW5paWBz*D#Q%V(c*{PNb6@VsqvKKN) zFwaiq%^uwBR8|3ys*wQn7_i1kl{ay6kn{-Yhzgi3Q5@!CF5QIVuQ?9s0*)?R&%WLv zNvW}ay4aie>lA&ep>~1v5z3voQXIEui}SrW|0j+aYWhK(Z$%rwy2>HtRZ~_F3c=wwsQ*f?0(-qe94| zC&p~z{*2LXy9568{MI!euUK2iHJWbK>?Bj1hoj1IX`3Uz^>IsWhM%g)>tedVtaIKL z!|k4btO%Q^j-_oY!dyKDGM=V9@Q`?qIm#9ZAy-jJs{t51qu7bE@Ln3K+~<$$X42|> zUL3eNzpt?yP8xS=;~O|tI3cStAs#d*zQN3PR!Pd(OxTl{&9go%_;7zX>hX3M)`qF& zHGe?$x~fq~6MW-P3Buv2sB}#;a2*`ATiE~sR5hwn2oVk$uGti7coPj;u64+S&1;9o zVHsTElvKDn0cI)M-fZT+1{E5Vg$v*K$W{ReZsP$_{X=Vy6{BwQ)x??7eDY@xvjYLF zB%s>GaAA=A2R=aB0w@-jxQx} zTI3=GG7{bQW_rWvPmE>LUj5iAoQLV^6G02KMi-aZOF419l*xPPfpNT-GKcLYn2csD z+;MfPcG}v_M4AXANBn8z<@KUBuvlzm45Z-jOctA^8#972FxxP?Tz20k}&R)uK{Qxai)D+pS$8nvYbxEabEu}*5#=uCo)5(HUJtk%F ze-#Rk$$r&if+TnvaYN!!yCLx?ZU*s$2#HHwBoqP+Fu^%RZNe6VmooYXQd9shLnS+| z`W1y4?|nU7wYmfypCzot{ElS)MY0vqYq`K<<{BH!l3$|PvPBV-n~x4bPB(QAI{>26 zn7Tx+0IOy?plM(VjZf=E$>ZxcsYL=U~RhBpx zg{`k47wbcXG-+o8VqiO}Oh_Rkuy)-?nDlrDtnxaVf?z_t_!dlv}sOF2x=s#Rz)GE+<94JVjXZXE_DXBRX>=*A0iP>eJLMLEeei;e;U(%|x{3HU}B*SU8 zgWoDsE^6;H3k;Wpb~qJ#nxK0Uc-ZpE^@odAxgq8=UC+{FLTR}+Lam4Xnn<454@c~fqGhZx9_HityZ=p zV5M7Seqt^}Z&EhZ!MLM=?v$$&bJcA|@-$=b72?9sYZezNy=me?_e;uwG4lSw1TfY= zb)!SN`X-0;XB@ZN9n#-$xTiTJ<8+7g3mi9n#UZuhu;1*E>TsB6IHdb=?8K2j6LWGL z{W!8<7fr%(1CE<Eaauh>;8;n*jq2>V{S~|C~xS#Nfhu@(ZEB=;{8+)zRCSmy$=bGT7|czPJ~AV zxQ8vltL0F0&#JA|dC!bkbC=qHx%U?cdUa@Z^bJwRxdT|via5r&t;pBgmMy)f4K^zl zo7-6UOMv|xXz^>DBgZ$@>qR(6t$T{6D+a~bJR_|^rgJq66S%mIRanYs`U`WFPYhwr zkzvaCJRH#re5vt?39Q1%3ZABEpon9*#eFl6V`!l`hTsz06~PL#eQWjEDF_hkZ`Czp z9YAvDAsFy-^3YAH2C-A6>T0RCkT>1?T1>Kp%;LQD~HS#HZJVWUb}MP^x5 zink>N!Iy}cKuxFuet9z{7kg1BCT5mWScc>R>z`*f-|HTQ3}vt;JHcej?un8O@r+0% z)=Ska_&Xs{X-Vrgc$H{s-25nMoHQG?C^CT21gV*h9By zc_p5=I4M}Z`130Id$Re`L_z-`b(^3dM049hf2+e8u%T(Y+!$q-43=1PeqMRVzvw%J3= zmbSsacvqoAdI-mQ9KXl08^=i;mRlXtbQ~2pzJsF{M+1%~9IxYe564*?S>VW`iO;XDR!HSWuFhpm=MBAkI0+)kKrn3 zrUCpqOI%+972!Dv(LC2mX&8#^j6G!U=krhh)+cWF7E(;B?uqg8Cd*B(YSdM#OuZ^4 zk{=PRm&uz9H!%}MwFV8dCQ%j$fl2HPpUlqVHRT^)^XElqX)_JU$a){ZslrNv>-Q)T z0foHn8n77JIVwZ}4qhozGI>_2wKQNNY_Nu(P-}!nL?7=lBc~6Kh~h3IK_VMSltm;O z3ljYt5QtV0q6@HS%3p=Wy4)yzAgIh7p(t(UM`&Y}H!YlI=ENyc#$HJl4kt_bG;_Tm zi!(x&JG6QY4$};V|JYl|IxlO^qSg0y%&ff(sxRQfCx? z1^MK43U1wBg5C1ud${rrq{!>u#)T6#h#u!c7>gJ=6#T_Q;6UQ3PVTO%gs%#Y$s%Zr zn2?(!E`S(^6~X8YhuzB)beVJh^DI~Md^ADHX&rc=8LjrM4V5mQ>UI>*r3-` z*b|6OiFmTPw|m*rSnvyP2FmVH%a*EJYJiX$ho-q45rqceJh6Gus~L!X;LCXAWUdt6 zMns%|4gfI&t8wt?mMC=-Ft)fN#|%c(<#VvuGgWBuo803fxWxnJ$Zvsz-Xa|Ioh&6V z8&R2t1)76CJ~zvzwV_3DvW+1}cVv}oI`4P#@gbR3xu}CHxzkTgAoODss&u%ElAvmo zSR@mUV_e(AD`xUVhDUCDmAv!pPL;)}PoU8weL@-?6s!h=p_CxOd|}FXw;1wIuW1KR z@ap5?z453-hC8h{)IW-T#8^iJyqTT&OX!!Vhhsf-#UgL)Hdh5L1Cw8jm& z1*54>cTv4ta9S2NTL3*&uW!M6LL;cb+Jj?e`F7WglYQ^!U^BuP2i=)H{mYitm;p@& ztphr+>p}oP*ogLrYa1=+aLRBpP`RN?_;P~>IGkPu>xF4F=G`|2O~#K1M&H(&(HlqN zQua9r%5=wUBpErB##kt;&l(#I$hRuBQNk%`bojQi3pJzN=|SUsT-V5+erDq2fS_+V zWB@Wmj&00PTm8OTphk|%E_NZk3cstw6$`c{9ZJjVfn*l9Ti7oBzB{3RvT8lL#vJ^? zAAE-O(3YcLW1rC>y?)=x7raN(+A+*du&iG!K7xkyb9|wi6xsLSRlr9-kM2eQHrfl+ z&IO%OB|K2VfGwD?6*YJdr83JeW}v*cL#kc zB!K^>2L3L#UBB-R*wtCJt7E`_Q-Hr+1)mms06*^`!9w6AaM20y5ht8(mcZvft^_{1 zd32W?A7TaNAB6RRnkBY!h}Ck|q4K;euUm@p*c`Gx6PzBlJuTtHOSJJ4)ko5*&l*rJ z=aIDPwsUk5WtzR{AuvrH@k|qO3&9}!GKPbwZs0>O*-py6@iZ<$zLZ(BajQdz>{J5O z@dFevmhH3bsP3f>d+B$E04%&wD?*iB7lyb5;UEMKFheXq*PS*iy8XJ^UC4?5K zX~I%8`*GGV&2qZmLDV}iB;EffIo&r5SASg>AXMqT3zrFWAA^>OoUBiv1p1E;mD()X}38)4m-=83y zhiCK*>f0E>_D*tIxrVE(zb&OX1ZX^|&5bw(<@sW`gJ!Y=*CY&qLUU0M3BTr-wLD6V z(LE}dHg`Tcey-&C2N=4MsIsT}h@pCW8|&-pl{d9vuLAJS1=sv02IX?jF*W02NxEtL zFlYzcY=#VPmja00xd3UkEN=>VkLb#ruIEs9?=4_n++tAVmX*4mMUt~OkMdd2rFc+M zh7O+U+JFb8Wav{_XPN6M+ylfxx%t}R;Nsl;IMF$#JMI^HCQ)R(dBreD*7*PqyFi1f z$&X8PgKj>_yfkzJB$~l9KWM6^JRPKTHm((UG^q@Hn?zSNInZNV2eW}1v+N6iL85i_ zKF+~z2XZI^om#&7CT8XO3)hI$;u1jLg*Xh7!fZd+cu-(-gT~pU@6tW}y)(h(> z=t@qkpQ7_3#G0!q`|`n*{b;P}Up{oTXrUZc=W=2@oUTVfzKB?$y~3L_;>XC(sJ>x! z;Bg?stt9GBZuR6Q=f*pRs}9(*ovX9z9tK<+zl|T_OZT-=E!I1}&PC6@6?qn~;r<(t zFq(4Za104mV0j-hUxXzdk>F7oC5E3zxMu3-w)sOLuMz;#qaY(WeXOX$NFeomT8)fJ~vEvvIEtRZOsYeO~2x^7G!91L5Mm^d9 z;;aQAU=e*40UJY&b5x+#jY5@9mxb3QjA5a)e7j)`k3(Z5&$LMmdh9PC2SLO-D;1eT zB+P9K4DU;v>R=+1FnQa+CvTCiaDPAf)Wwe|%BW$E30B#g&X;h$5zSgB%&@|eJ>0-g{m!c+4e<*bO4=m@fZXn*gt?vCDR@zrcl~|Egu@}^*!B) z-5RBM!^So5@-YVwij6DKlR^Y0aT|=pO3UEH@}5uez}avBgixIgZ{kw+?Ljou;HCT@ zG`)dr?$o9(oP-Srp%W``&|_KErwvW7iF^gIwjT0nHlzOo)`4xGXzr3(O7`sowmpBu zj|%=~G@^pP1(&kVG9>uu=&UAk(-s(jLI@@NA2c>{KpS7esRW(maK?}6XaTr--$A5SFc#z|`J__xRIJE1rTXQJNa2#u*r2@Rv*M=-rS55f^j%#uGiEz~P+aNp` z#gTYW-}9aG*ggQ>BF^%*>cL{47km9?Q0a9CwyzFO#JWeLNIr!nf;oSd(KEUr9d>z+EI*CF*IrYBQT}&a*wQp#xF&9f5={bfAMN@jfrb?V zl+PknIA|%bru|?db;9KvRFSqF8newXYQTHexaLhE($=UNlit{s+*;ia3vqCs^M@N( zQkSPTa{qARp0$HK+!3wf^R-MQ)-?|XVS z`L~?}=v-+oCerFJnu1HGTesT2(8aF+CbIhwVry1-GHH`SKlT(Kg$4tqc^8zbU2r3Ikdv@*_B@JL7+-2}K{dqI)mub% z9T!73U8F4tkg>ga2@j|se{dP)rlrYpq|l4f#(pZ)xP_KY&a7aUJ>>04$9B#v3cHeh zchbyE_K!v265F2FDf|A6h8DPQ;C@$Ck&ETM1orriIIE0O1nfgZ0;Z!V(r@9|oU zxOLF&N`9Mx+XA}1m&(tet1?`vK-dKkhOup~cw*y5YZ|^LXB4%Why>k&z z(M;~N-s?m?AEOvYx7UIU&Bt)NI<@&-N~PFUA`lbElz<%3>AUco3#M2IGDJmX;!HP~ zhP4g$Av~V3>l|FofhBXaHE(tv>y@!`QxN1n8LnRy_U7#*Wk{{q}1aPJX@T^jb zT2!MXdJ7R;<1v$hXq{C`!m+5bwD&3kii0F?s#yt;M8w>{Q{G}t~euwlm91ZI63eI#eU9FQ| z{D=tRz!(NSoxxwcgg&8tv_V5k>}ESenJ`@i3LxwB^6WX%UDuol+=v%B;c{uvphMp1 za|Q2+YI%Q@n&o5MsadpG6}pJ#KNgpNV9@+<&ZD{NGm8Zvo-5e&zPO_2B1PGBH*&VT zf7#mha9A%1SeHj(rS`qYEL_n++ej1;!zbUkF{n{7Mv8F3eFlZm;B4;08IyC#yV*jd zK|J@HmFz4Ji=(Y@YDmUe3duOG`7rT*P5qv`XsFH;LzMzD5+WLwEv0yjRIe8uBn)B@An^kVi|zW zQA2%fDpI*BQj=ArrV>(|3~Wcl`;a4+^`MYvV7$)2g5FCA&k>_`UNFO$f3jxbG9 z!^qA+)6+zoi5}fdvi!EPjv5*HOc+m^>8dqO1Y|8`s3W5S!DAVJfpC&4Ycpyo%8s4g z^LPIRck11z_K!NnZqZ)|+@dZklI!+mrb3|r}wI$DY~wckV)^hWzOWbipS6{*&G{TLx~rYvbWgl*SzEX$grnZ zz~^W=W57$%s5kt(gBhc0io>3_C_3u@FR;{@5$zAUaQ-cUTtD5mQv^l>q3$MPDF>WcI-)?ev$w6VQ>?>%1yF9%IC5W`b5 zdeck_<=G0B8||3KCsqBiUAKw4j>pL}-l6QmHD?fHMg6tsqm~N>hquSz?+<2ORkJwk zd0Qw~wf@Pdm|~_jP5FFUsD2z_o5tWg-8@Vx2ZE;DNPE})6WfC_3z}@a!W!eoNw|m` zoLJQ&VshrdkM~#sml23pWGA@nsboDoK5>RBD+*Lu;ZS8okt!>SRar5E$8E(%Dk5sD z{5n1>TJw|0tcdL(B`1*xzr>0+;2rUN@<3r>^m1W1X2CI7w#T~&3u+q?a)4Rfq@XNZ zsMs?j`~H@j$T;531;ExD8sM9EUd3}v)?WE1fCULjA?{gt`Na_q*I|Qx5B9pucsB`L z^9O;ewk+&DlP<5D#Nm4SZNQZvu`iKWz8Sud?0@G=m^P}>1MaUDvkUI)6C+vvf-KQx z2HMC@s)A@#cdid%a_8|w(E_sXM}y}aKqfWVvWI28_aPr`t!4@8TsqaXm7*7DL|7u&e?KS}nD8x$q1;9U)UN-yBB3B8Qy9VJ>F z8Si6Zaob$oNYt!uTEw$yRyQFU4QLe8oWa+BHHYv}h=S5=ub7c+cVN=vRNDHX;|HSb z#XIpJSL;bS(KczYPJCY1cmSy2&E#D;i2&Yx3E@J+p`iJehd`l9$M7;rfUb-Lk;GH?FO)__mHloH-*QpO?JJ43L^atgNmXq5i`SM zbN~5F7e&PJkN7V5^kb1mA)AYhbTmDur8Ml>oc%FsM>wYF_1CckyQh0p7Zzb*VC1ZM zCnASrAI;;7la1UjUo#jXgCtmCH&>rcZz8wy_}vJ^tZw}}e-ZvzxbC=<`HES-gCOIT zVCO3krjAe@Pd{~>uBlOUTo;N4d9DG3@x&~zs-t`G816UO4w~Cz;VWitA2D+fqB54u z9L(=&EP`hc94bbmll--lT`$FSI4o(3@YRa}&dx$2QKxH5y-*@K{)4jb>WNrEA%8G? z9RfIwdxc{MYE^1g3WY-2dhWUkbOW9Uge*ue8^0Sh=5qRoAl2v4c+|JRxh)d>LJezv z&QB-eBCE6N%3r)N0x1&b2_#~9L3%9&A>NOWC(AXD}xFw3?PQf0VmFCMz}>cb5#>aP?Wa z(MLF@Hm<=b`KE;7WLgiFWP})T=plBV*c3K8v09W>W6T9Yf}D3if}D3j^-WdPM@{{B9eZ`J$$Qx3ZJpD9 zaKgb-?~#+x=>l?-|&|1!xBwpMPL`2J>v2 z--uI`l@1TaN+Sk^u5LN?DPQ?+{s#4OdN6Gkg?7_ch2b2SnvfgXfmyc%6@w*C*65q= z6>Vs~GXXptn;AdAj+sW-zG`eP0Ex!sUWm0b$*Tz3Gt1MIz0_5XDkEITV+bks_yXUQ z00tbz_d?89L#boyxdgiH9&V&AV`L~VCZI=-9;E0 z8hu6w{IWFD8-kg$xWwd>M7$be*fKtK0;ieqw689y34iUNz!n%AX+Z+pw~9%ZCtX&K zguiD+!r$3xEdV~cplrr=uiZ75Ys2ER8N3iFf{qaqf_#@aRpWAjB0FucaD>bJqTp&S zqQ*i_+8`v`REt4V+>l`w;T?n$#>>GneguCMAA6Ay{Jng?LbBcuc47P#7$=0Vu1iI1 z7rdb<&XN+efgQJCsi@_E-9eTiWps>}h;M<5^%ALz>_hJ%1ODwP>TZIV$^D7A&3Alt zR}1myu^69bI!RhPk?2$7@a06Om`YeGkWi$R1`%OV(SuJt!H1P(o?V6z`s9kUj@A^L zX5okC*=R&luR|Fm$%kdk5Ob1MG}xYoWE&ViYww}w>T*Fqwyh?`7ut9oE`dyRUMYs< zX$(sCgIB<5>QIz@Gx+WbZTDr2O;JzzsFM&49@90NRuEwZ)G^U!hO?6{W$yP`N3)YN z1WaKkJF6`(CM+(Fl6|)!ygX)oF$VFWXJ*O!UdWMHk(?-ll9%(%C`ciZEU$YJq3=Vt zj2Z`V`)z>R&z4qTkbn3s)Dj&Ky9b->%|^B;tH&yGSezEVnp}|LPUVZqVg(nyjh>FG zTZ;%y09`TS4pl~H3?G*b&fJr1JzYy__$avh>p79CJ|d` zd&uxn5L{KDFKH5?stS#>)pbA23lMH*;AY9N2#eSRo0=UP831?0bkX#MHjCJTxX$Fb ze|q^Rpw^7rcR}~0#_fVZ+pg=@=)Uogp!;XkYl+SohzwGvVs8|*T&3%c7&>8FElq_f z@Pwqmb}qJJb?bfv8r}o&WBs@VvS)JLYXrD(!tcPP*r|}5zy(;A=>%;G@NR<;oei?D zkdgqGi^~33+6iWBriHUhoi|{bRgA!0;Y)xA_8=vY7Z$2{$O#8t-W_KvQzfda zR_vEKK>KCX4GnNX@(m4xx@vv2#~`Wj@E_zJ+K5kQchnKuc2dN7QiGq@TJ`r%;p+cfU0WX=$M2@nqqRDq_vcZ ze}gZQOg_XH&?b~0<3*>8M$C1fRcx=VuapDInQ9WlG1cP zE@j^%#NkP~A*zRVVQeS$i_RRhFy7?B7kS`Pq$MBW7yW$Kdb07(%An~t=#`DX#g7=k zdLSJ`_?Sg3vRe%D(=FuX&viYA5>aD?WT|f`V`XqlG%`oQ6aim-?8(8;XBQPQf+7cf zG3?5FGJyf!9%OxIPqe3}4QBG+jezOoY(i^0&9tzCVV&B{7ju%oz+wd7N(bRWD>~X5 z9C$U#eV=Lqn5Zb@mQfV|EkA}sJ5rg4&$-~0f^c2{I zh6PUltHZ+gDTW1o*YDDXg&Tq0)xAd2ue5OFJu=?gc1@XUO-!R={pO#!ex5isE|vxK z{)UL4*Vd2zIb-APeeq*sA>Q9R%-9&v56VAt@uHK_WF30aydeW!*u#iRne1-1HYMx&eaaN|niVVdAx^kTN*MZ0Pm=G5MBy$`~>cD z0q&B|3@*Nhi~!i7LGCF=mblKNZ^C}^&T}S)XIRjA&NX{jD{)6uiGMU$7t)J;W3+9I z=L4(Djf(|M$~BrKH`fOD(`dWEhFmFt#mYfN6fy0kU}rod+EZ+`-_>Cz86WJ~b_6!9r<7+Ar>{*i*>(nCn7Yg``J~jN>cEbOC_}5Q-&>pPe7omQI#EaUDXz4v)ZL3tLMOp;&eT&qT!C9`2L2AT@FrU9D zgt-@TLx>-e7m?~${qkO|!8++UzTdlR0XyBK!(nGRG*QBj$27UIg&&r9xL?Nl_D`k=pvTPmw$r)y{B zur5diqqpE`2QUhIQtZ1~wi%b$*Wj7EsKVagR0kj%VdhF5=nKAG%iL>#Xfw{4#jch3?ALXkL-X)%eSEXoysCZay{v<6Yw0$&95Rj?7`4lLHpMd) z-~a74v(8Z+4bP!+)-ehh@NHvi38~vgbu0^*p2tnYZ}0;Iyk`yAbkfRRN4Azj=25Mq zUT>hx0TY#97LOt}9v-?aco=GoVj$Jmdjfz_tpP(VVcBZbVxfsL^@J|7=HmN~rL`HM zQwOl5*3vy9V9G*zLk@oMxvH0NX{0Ye(kH$eSJQI%A57 zkL5MIi}ao#HjhU8pt30kWWO=Xn5M6H2YsZtmr^d;y>)su1E zkxK2*Uq>os5m!`{{@UsZ6THD6)ezWhXF~=OlyBTmZ^&*?kZmcx%4VWOxKF7Ce%x^=15G>v-+LSrcpL>9Bqvzu2O04Va!qdVXueD>$tl zlh=~BtDzB>`0hJ?dq)JUbXOn358H=jN&UX1yAJ2IrqZmfVKY(*6J~k|S9oK$Jw)FP zq?YhH>O$(|{7&{a`U-;?0B>>ty8KSr-;H6wd8Q+k(rHl0FNVN5_E5{AbWN;qL!{@A zc+5&Vz-KSo95BUc zT%Y_^?H{Z0u7xHN%k-ccJ96>m>*+*nfsFn`y!V*^m!F&ktHoPL7^QgDz+f?>HYtkF zf3zgvgEyK*kg@L$+@S%O;!r^YkbyhSt$K|>sr_R&#!gc)hcF(5wQU5R2FN2%2_STy z+wA?(=C9(-V+pIxASjVQgZG$~u&SpX(jT3d-}guxEDnpS4!w`>ThLgr5N<^_zFOBu2X^X{4^dQo#$2p zX7!9_2cXHj5GC;MHW3i^t;BGgk_l9K4>jNIc|DH7xQOGZ;scKn_Ntw&LoegykUGj^^TMm!V@2aTHC}CD&HGOLdGm*S^))UW>lupiT_bk0PVh4DSWz-YT)3*uO znV&L+Ws}UaAI&?8*K>4_$ZOLNu#TUeI`35+KgF93JM1U&PT-b4--$hxR<15vF}KW= zUu?(8Tx$r`8rBJkDZkiC*O+*+zuoPprHdTw6#+rvjy8MG+T>aV$av2{_WcMK!7(JP zEJC!I1M5iy1=f?i39Jv1(_{>nbCDcQLtuRo&*I}*HuEg1$Wv=^8UyQ#d7c`cr&G;C z0#K_bBe0%!ln2&7#q+#|JOVNA8F~#Epu%f2G^mIn@IvY_Xeg3Okk|+*LOkG6mC7cXczzxP(2x)PH-5?L+prL-JL$@^=b@|eN?L9c1; zdCK)f>Z^3BHRKiY+caJ_@2|B_=-4*SmAiWOytn7bp7;0sT@`>Pstq4n0fWgeayI2+ zjtfEkjtX!f@m}5qRpM3Z+spJjhLd|4+Kx16UT8(BOlTG`rT6r0?2PRDKDvfwlTd6t z-q+WcclOlZTaKo(j#_uR4qtJa2rSq;r;g)`AK@G#puFx*ln`0~>_v7sGYOjZb6V0* z3#R;V7cB7aMw?l(?>(Z$!+EoY^JY31 zk_g1(lC#(lsNuX>!+EoY^Jcm#FC%$S4d=}o&YLxyH!l;)Iii@0GXgc7H)}X=)^Oe= zpaI5gd^&a_CQW9~l@tbU53k)K!Q(z}?Rg2%xhK>Uy811>dT+=aMbdvY1Zs@H$$!T^ zliQO*hP1p-e8P6}{>ZI~WG z5L+HeZKg}XD7>;Vw$oXfx1H@rg?U}5j6@c=gxE#DUEjqmRsIh9q(V!zDrCQtx|00Y z83$_(+ajGB-<9s~^KKjg0llu)AdAPV;m=W*2NR}<`@F+dVklEc=Dzsl6M3!B=VLD^6Fc< z^}CMjdVg)09;n2n%jCpO`P9VK$^Oh(;s#aXPF@>_oqI{GfyNLKcN`Jd4njt#cqfgR zDph%<@1#$BCFMZXMAJkSpTw2aje$%G zTFv+kbsN%KpRdLW1GcN-wK@2T2+T*r+V{f%p$U}5yaRei>3oE)n!rqw_i&2RByRQm z)}Xu~ZXvz!)TsPi+`@&9TeI?%xP^+sZI1FdZkI-MIRR8}!*Ie!bjWh-LZ_uEs^#UW+RVIGSUL2s1l97^@WmCHIUYc*^x5T{xCv){78-_~%#OR$Fvs2Sd#ow; zyXVCDh1lu&^a2typ0U^2fS8p1ifyAw*>ji$Qx(hHS2#KCkntt7w6v`wmD1^tnH(p` z^d}sNJH&I|{es?TAK$)Oj2=d5`Y1Q+tFP|otD8@F2lVd882>ak=rQM6`m_vvl)Fg1 zl3aIcX?JN?^R%mC?P`H`b-Q*|rd^e5SBu0|&jx^ANAKzgT66vt7M}fJ!7KrYyaC?` z^X-;5gq#{g(vT2It_WZtqg5nq4Dx1u;# z*!wHcwFiiUxP={MhGOKlZ&lowJ68mzll2{#PK*+^K1cQ73r$*0%*VlWHi+l%;&}rV z;4M_?)CS^<2+R<~l~DrFo<|Ykj)6+Ln^`}`u;%g%(yI^GIMH~AV50$Ff)7l=StG<* zgdtPGkRFR-`6wJ#es~-|w_tLwcpil1Q-n`Ip^@KQ1b63J{%$I42ZK~vAp7pe$=hR= z{VzZ)v2Ac6Lc*o{zb-M`eSGPphm ztOMcU__ISQty&2Nk9@TKN_;u+$2sBlkUk4!-&^N`=SJN-*(3&Os`5y@X1g z;eEvTCKP{^7x#4ZTI+~2eE58+V!$f_dsCHraf)#H9CX``iHT(SIb3Wpogv`*FJx2J z&WV;yL)jl)RJM6;Vr`Gd*QR_hCa!j62jY}tT2p@UEihe-gyVXgS=A15Zgxl|RaKyV zq=T|!DNOz2bUif)g;h54j1E3!TLK=j?^f>qP6E7EW#-NmUz%`za5o4KbG7Jl{$>N3uUmzgcGeB+$ zzKk+hRqzB+o~O7@jIEEaDnxmTn=p$CX2HT*Mzns`wwz8=-i9*fmSVUJAO9<~kZ$hd z-c$Id%ouprx}<7i8!;rx7UBz^oghefznYFAaVrv)E3V?B*N$(4c2gxW!M4lB z`<^?J;5`qC^`$16CRk%!WcApInc50Q3(o~wUk&%yT?`F`xLKp=eE^J(f~ zOhsPBS<2rc`&Z+lp59PL{&-fn0enYX7Q*A|LXzH#g^AasM~d>_*fh)zaKeOu6Kz?`jr*Th{l{3^U3>^)l;%qDt-q6#k>a#W;u*6=i* zDO;_%j&f1WqT=+HUs6K-0Zv!!k0Ps?gJo=$Kiu@5i8e0eB*av*>91R_gecSrfPQzd2#+ zsW(eFUx4m0JwCatA735EmzEJcHL-=RPaVM45>6bTLL@BJ2ppxY_?E4=kOn?{C{LkH zKt*g<5mrH@I(omEELCJt3wy4rr$(HEHli}SXCg^H*BwMN(RWt*dTJak)G9f-1XHN6 zoX`M+M?(l(Omo!n=slK2O98F}u$tJJDv9;D=Ae~I@di1#2_^$OOm3(-a%PhQ0X73) zDug)8a=N;NeMfH#1%DJEDjK3MD`*aGyJegw3-4{S1f4qEROmdTy`dCudkS7z-E9x` zrh@bb!uG8g*c)2daY(XJtc?eG!cCCLMZKPW;aZpq$AT0BPks!ogfG*(M?ymx&|r!5a4RWYiyWs2Gsh}zOcZTE=UibV1C0<5p#E>crPJq>waIXt19 z5e#lpHWUa#6^b_ZeueAgX`;=)|1@IyqswW2LU@!;1J`k%Of-??)jnv<|GJ#x3Mwl3 zxP;KiiS0FE+LKyWO?w{mpy_Q4fc&mX_$a57Q33V1FG0x)q-U)MR0IC7#|q}FCaAV5 zQ+UI~K5guEMgA?^5tFZn5jGCBMDmP`=J9r05wRh=Cs#3z%cfr?8 zq3@`W1X~ogDwJ@*f6)C1mDzWffA=F}=ArD{C|jfVcBIU?%hN}n&k0+PAuFG404yKC z0`|pWYYo3z3r=&2y_StvaxAGiHGag0E5G$v%uu0q}dq;>O${H*0KHD(e z2fttj!dA2{RIQ`LjuXZ!I$h93_Wcg*KA#J7+;- za4R^b8)hIe(hXz{?$fM6dx#TOlSWbhvc_BZ%ob)G5&n{W|9}|f@eyUL3-2>NJYtc) ztD+Ji5pYTwVKdYkoU}0Q1Wsc8g7sni^4O#SOrR;?s1gq21KjcN5}_5CLWsC5QKP;K z)uEme?kUCx-pO5GhEh;KnI%=3PS7zYY+XoA6xptqU&Q{36^I@DP8!#Cy;&;?4Rk}? z+AFWXR*Dt*U8MKTw2;$DT`Y`rL?gcFwq<$SuWI=)l`eCz#uM1*FB0?wx_#x zr61ogkBnIiQQ3D86~lZd7U__3af6P`3KSb)N#z8b#uXio44t%sE2OR!f#T8jt$F)O z0>$RQ!+DOWTw@w1;7F^^ns%0_fto z-M+X%=lP(%`~;V-H^#OqECR#FTB}|q#eJh!N%f?6q*4L;Q=aDf%`*z4`3%e_YELM> zw{Ir!_=7|01LWC<%ecn*+%HkO#fjaoLy2EB((gjzpd+1kL4=Y?^yRczO13G7;q?Qg zMe%S=pfQ|WYc5sp=2*K`U+f{EO8BE5Fo!B*z>`Kk8FXJ?>=})_3^-8T>EtR)wTED* z(jt)&yh*bWJ_3;@7HDX(%?M$hi6{6Jw6NM;i~oo?tJC6U885IjYVAUvsjlhtCS;M6 z2LKG$oy?^Au%>QtfcYN=qia5_Sk@sQR;g<_NCTM2`xmK@JZ@LTp~f*>d&~?0xuX6V zlpsTU1<9(W+t9W>RJ+DD@#L>p{F?b}^yODh2HCe4VhU5@pFzsv!DKvu3&cjJDP)8f zrr^1}?srHKm%jOQ83sD?x^C1f`(DFuvD=197EvYJ@l_dZHpeLfXA40Ex>SW>Hvb;^ zT_aH~xhNpAT)+`XiWDo&Gy!Q2S9$h+uJim`pIGQEbdE#-(sP~3U zXpbI-0xLRFAI0(pzJ!sI3NlH6)O{nc`wpHlE4}xASv7M4TB~02wYsn3K8a;5-J_N^P)T(zeTpFN3Sw?hxyBY6}L;U_Dne6TMx3S<4|HB*06kr=DK24~aIx z0XQy84hOS7Ot^BYBqat1lmgJ{FQI`5i5lp6ls5I$ANd@+RWi!+g&CEN3jgy-oz(P3 z_iQY{H?60^fgZI{FfgAMny4tcw6@%kQ-$^LHP{Vtp*= zf9YXpILQTW&>mJ`tzKmI6t>IN71uP7q zH@T^YFW1m8bXK~4q!qIFhxhV_<|ig21}l?nI40ACl6KBSu&8y9NWmLYA-gQJd*9-D zdtQZRGoVo(vgF%=7NK0pZr~ol1;`Ii<`Xwl#fZ&^o@QwkPFEnNW__Qi*W5nBG_QR` z`aG)N?AcH8V3m0Nx-b_K3}5%Mh{0Cr&WoQAz!=DcK}LH1V(P`t4~EPLENHqgWFD4n zL>Bu8i>k(i_pm||W=^c+l7y?8>M~$d!#l$tI&n_|E%u&9eeAt1;hw}|>~np>eOkhO zYQp`9$i2>W9NiLaU%gMvZ;a#zG6XM4%h>kQ6YfXF-&ds|GX_bHQ!QyFHAWs4$U$s} zEGb=T@GAO4Mlyh4+4Oa&WG z!+r>0;gY_Op^W<(VQV?=@sz)A@Ef2jfkz&~yP= zREb02LUHkpHmE400bd^Ii$XDZ3|mpMmeF`*-8YpfYoV4mXU#-S(dk} zEN@Gx6Xm7q$j>&(b>9hKK#;-mUV`%ZZG8RSbFBbRU1hY03>O2&HrO6U`!pXshB4*p z2Fb$K*TDI%>IdO^B|mN0+J$?i528%@^&qF6n>+r@(ZYH3p{BY5_bcXcP@}Q3FNw)DD0oFIp19zl+vX!bf}yKjp(nO?zf&>F%o*lRi5eXb@LzQV$Kpr+im5+iCkSz0gD69>bRVUHT@Au>Pm zPP0>pYZjlsX4~%O({%wO#S7)PoqSFUG{WdyjyQ}$7%a_&2`HFRt5F-v7D>f(x#&cuIe+;a$k@!VS6@CvLTSrk1J{VH8 z)Q50Apj=0IGb_;mm@h{(+R7O0p+j66_RUmUgZvYr4!WXaKQGh;g57wFC5%M>$JDb# zrTjLH_KnX2KR{5R`d+x{sF8jXJ^d`SSIULo={py-OCc&~cXsl2>FHqYTH^xDdaoY5 z=dti_ARo{4w@FO!-6#88)vGaP_qpy9CxIO+-D8V2EgYi;&u05U z9s;SH21he12O*`bqEyiX01jV;Q#t-r1iZ=;$xRdZ*rrGAM8jTrF4C_hIP|)G?McIJng9BngNJ$bhu~_krn@c`3fZVdPU_ zG=<%Iu>}nY1r3pc1R30OqMQcr+h`6QGD}^ftoK3SyHo3HaKcmq4-q1U-#l!xG~{LpPe}_JT3VzElytpWbMxhF^)T$N4RzKf4Bwt)iIYX}w*s=_JYT9C|pr*n2 z1WZCTf1&}!eI!XL`)y2RgE6CRLza30a>F;yGkU|zRqSdgRv56RoK2m~poR!qd} z0wvIg7CU&ESiEK5q*!?37dk#vvZL_C)`lX3)b=(Y)UZpaZP`7%cSS`4vywnIp_LRc zjPOCLQhaYjsZKHoBpM5PlT9M%9muBSyJX)fzfczKl|}N23hm@Wer4)r_wwYpUP~MBSMhl5uT$K2*%8S;aIWZm|Ai z#>{{WZwj#{v)29rIffzO%!^!-gM7)#&G6bZ%mBtP$LQrD7LIu{2rA`wcy3nK!E?hY z6qr(uTvcIbfG?(ptszv%GsDJ{eXn57l;063Tz(H|x|QNWzK0a-I3lWBUFh$bQ)tM_ zzBW`HH1Xi)YzoS#K3x_NkDd+{iv@lpVV!xF{e$4H^sfHh6W?N1`Zi(y-Wp!QW1KmK zzGUd0DB=gwcQKQTp6{sP;Ud^xzbz6Kf^b_vpg0jktK89s=+!^d&yD!%q4M_Yh+|b% zfH^^un}sA-avz7?Z~s1Nx+o@`I`&ZA7S!DE6p(T>bm6z27>6)_24isMl!d^}on2py z6Fa{4{SZDQncs(yjpKQJYui(i+B=T5;B9$I2EI}SZzP-Mr|(MYPtKgO9OROHe+Qwk zuL|~qJ(Ry65#iYL#s)lces%^L_NXf+DfSS)7Jf$Wxv`#-^AC7(S>Y>GXm|eW@`hG+ zCjY$03c{u}P*r#lftkRj9Ln|B6Fh35ekBdkE$+*zZ#8&K)lcYBJZZssDTsA6;hAKz&Bux z(XzD8Zz3(riHXIA-$4x4tpowkZsAN`%yXG?z=vRw+uS4jQ;A|m&o%w2lm{MX<%1S{ z3y9wtaJOu8INZ+fKzh!E?s{U_0KY-_&2{~$R;t21V?qnY_o`dC~Eq_95?> zcymb|E3vK3QBLafl*6waZ6DHMej$l!d4hi ze1;#Ig5X0Kg2C)}(Pv!bz~39>q5wwG?cT#@y{+jJ-^$-2#DnbfbN8)Y_J0`{TzYLH zB}>AK&$3k<`CQxmx1N#61q-tyl>`*&Pxwo{o6V7RkBC4mA%m!LBWx_G_TQ@UtGxA< za9G^m^)Sry?YL!%8t8=adG}~p>YY%q*nOFZUYhiTPA8@A9+8Id;YL|D!mejsus_Et zKk~v!^CZn5N$~a;*<_<D{*64nlgrwld=JGW3YF@3^4$MR-rK-OSzY<#lguO;V8R4VFjb;a;+6_1GNc72 z(L{MuDJDb`S`^&=`cG3zErl6yyKD`eL}%vdSi03N?Yg_f*!|hpAKUE<&=xX@BmpW0 zC~sOVV6D$MXrt8dVkQ6Y_uS{1mt+D#!Gg`_GkMN9X_B3gMAl+Kh3Fs44<8#%Z%3o)DN**fH(l2}~JZmTaO6iW~L8T&T8rX8%V3EIYDzP)6Y_x9l3Y3nPd zc6z7QKa@VpJ8AMvtM{h*hisF)A5(|f;zX928Q9zL)O_>^zVKuOQaXb(GvPZTm6nq6 z9-h(;9~WwETjT31uF{SLZB9fk1-t5xI9qg^zxuR)MsKYPj5HkTlG+FM~(g(ZlU`2d=Uyrg?S{787gJI z3TuO^Yus(xNl(@y2DpBzO)FkXg}d%c9CTJyEw)y{)eidWAq5P&YMzb0P&`-&7vMdj zu)Nue0tLKRAb^;IF8?J=&bgK;_aa!L5FvrMH@rV8eV{<5VpO)O3V~O(*j|Oqk0CRB zR29hLEkx`iuILQtI7*QfQ_x6W`b*&H#rn)q)N)1mXN29aLhk{P^wFuwA1#k?D?wfw z@TpTpapdcr(8MoVF$+SZzFF|TKmp&YqCqUtR#-1uC}vjj<};8=VQ-DA*WKZn%q2t~ z6fzL(QWhO3l@dxxX`oaJSd^e7Dw}AeRLyg}jtbkVopd(x2#SQ->d2s#La_~uUy3_a z&k|hb&2w*EY@O$+)e2aBsxn(@diONni?3!y@}3p>WvTeUc!KKPH*X$t#;QMcj$0Q6 z#xu7#1V7aV(+>f9rmUnYVt8@}O0g*{TKAMCh_V@?P*Sb|*@AsYVO~_0Go@>9@RyE!CZpO+SJW={ZWe9vEX>9TI zdms8FLe?;XlV~S~!}U*ru>h8E&Z_~)2T_0lx!rb?C79*vq5JY3U>mgEQc~z~lnnLQ zOR_z-k|Ew9!D+Y1S-G#)m)zp^ULlY8o(=PDsPW)EhSRWb3E6IG*rtw+h9u=KhAioi zz^ul87MK7)aNRSGpndG|8TBWpIvZi`u`mGnm)NAWY?Eh9TL11V#-z2Hua-|i+ilm^ zBOf?0i-=BGRai3RA=|hK(JM>JSLK$>!2Ktp!%O~gmA#}E_chVv zLr*D!I4>o7bO(q*{Qg!%-a`~}&9z>|w<}lmuRy%4kDF4k3e51_(d0q@<1er*CZGKU zWlPNJco6T6>S$UOzc<-;KVAg{#<3sj|Ayh@I@+Zr<(}(c>Rs(C04d@2dVg!9YRS(o&o3ibS z*_2DlpJa*NN+^;vOBHtK!c!s03lwl2MxcOU3G5m99+Mo0hiSX8=T#&7o7p~A9bsK}I2 zdj|ahTm@@#&u&b#*!BN9(c4>JO0s7<8y``4*)VfCo-_t7G)@R|N`cd`7*6xC-d5pc zH|vA_yuvFmevtKXZX6@f7<>7UT+^)77<=QEhvKmlvj?U3p*^e}V#Ji(2X$a7HbW!$ zj*fx<2t%F#qAT?WkWw@RbaRGYh(SSua&WzX_R?3~jaTZfJ%j4b(w;$mCkk(Gg2LOI zO5yFjAPP^9ZoxwEI<|KNe512#L=a=5Dau8#bL*y^jG3Z5n7m;g{8$NAb_=>l>8;Tf z_j*UCn2-JIiP(H><#6Qu(jf10he;otEeZugzI^ar*|fUN+q z{@*I(+o$mj2N`|T<;~hsf`X!#0mU&}X5+SGGPZ(Wf*VX96(J(_RPY{0QD~Z@Xlibj zcNliC9KP)J2sh5{KVw`3*p-pf7**4Ztm5wcqZZrdf&pX*-I36Fb(9di;*39D<+#Jn z#{Wf@_IeQ=IPOb@6sr+^qobvHMkY=99|f+Fudz_!dL5J0QPZ*z z6o7*Axkca{Byn!ZQ>r*aRIx0Rr7E6VQ3V$`6=tHIRLXf1@oL=dy*!vrm84h7lq4oY z0MG1qeuHHbkg(B4k{gXAoKiEBd{>fqUThLYT}s`Kb~Ht`>irm7v2@I5iXvBBfHJQn z4^%K6=*U6jn7h^4=mT6)Ab>D04uM+MJF0)Zbb&*aky>QZvZiM>nvjZDd?if9>p zn>mFgQM!bs&%^PJqH$sAt&nWR<%SdGZAE#sWY%w#8Bn+J>5_M*W{N;3rnb$X#NL zv}aH`0lD@JS|f(CjGH5S6U>plDa{f4uMDok9JvvEzrn1ZTro%V`Pf#%^fZiVi}kMM zc&&z4zGuc@dlg!X;0}~Fdt|5KL?2c|?ZMHnsy@WmNdv4A{-!AK}ol<-hRe)~eJ6{9@v|}71h|ZulZxs}ytRq4I6*B7EHp168ZBuWrX>P+8 zGsf(eq`#5F9Ht*VIwsl0< zpsAXoIO>YHok#c`<@~j#z%{DDx`FrQj8{jeF~+&1ic32nUx$v zjhw3D1WXP>pDF1Tm0;O^gI&2Xc8wBt9TRrtF6)l9n%aQkTeaDEJ}CoP;FOD)rTtnr z+D#AVY|w5>rQJBL2)5N9cB0MHI)AY(b-UTe9ID-Hp&FAQ6E=T|)2LvTY1!nVsd2$O zX!F>@_igFJ>#Ez&m8wh!iANPO{7+J_U7EN5ulw#j%eX zkb+tngi_6z_Z;;Y_o8Mtv+Zo*zz3U7gDqGlC4CZmBgPjPLycngS2Em?(5hw@f~0CU zrq2 z>o;OJ4@t_CmueT3tMH1Q0Tx7x42f)rlpvUiJ&}^xeUp`|Vtk?%Y=|>0VR$8*Da&4m za&{=shB8pFTQ22=JthP1)QGg0a)BBX^9GxKg^h)GYT2EbBlyZ)7?f=S16pM;!nc}b z=xEB7hzW#aVoW$Bqmm5@LxOC`pvcS^y%wEhf!RSCnvIvE%4NBV4I7rTZ-f%I&Bja2@7oPl;nifD#m-MTzk{Cg zj43JiUWTW#Ex^?I==a>@dk#iCCVk`Wu3+wE8f&`y3F)2!YHtzl)`rO@i>9J za`kf-tUX4SF-(Op`VxIF49>=uMcxt6R-@2ek{wiSv)kA&pi@B5qp}KMR*i!GWCEd- zY2vC;?k+xh(+m618~y(-IK4&{fXxo0nx&SE`putxN@}!-HL4uJgQ%t(&kNeA>*qY+ z+mFo+pFJOJuB@uANp#MOpS~VKD8u~|zTRH1IRys`$58>#unb?iA-fLrmc#MFbjxG1 zDdhNg-1&~Rr{2Eb(P7hAvbm}Wiar7Z0Hz*y=sU66Q|rdr3~63iJ)_W))kwk^T0D(< zof|E{_jGTHotr+3&xZMdaCOzpu-na|g$N@tC&D-%%gI=^^m8~4n3QSIf9OA0w7e68 zCjA9xDKT&Pg=uLP=}xp0Cg0M>?xUe(J;gbWvZ`HwHn?}d_fD@j1>~b~JY$fRT|R$9 z3DzjV7@vR(x)H+~&jrgC+u>8(t99HBrjMQpM3!M#0kO`7Wr~e{DluhPh1lwk_U^?z z@ZH{*zya2avii{78I!DPZj|;FSNmxvy+h3*5G<|4IaV?K;?p&#CFKB;>6*Y2ZpkT0 z#6H9j?=e&*Hw3`KhFtFmW6Wocx^mi#v^Jo{c}*^Cf^y9Fj#TqiaF=3i8Bgx&sa<{R zZJ%^DVwMAJ6&M>6B&tT-;^;31#XE9#l80BLJWnqk5~_4GW#D~Ewx(t@L8UG zpKkRye7eoMcry*5sADsyr=l5~snSEXpWAV%h^;}ERaQU4tHz$%z{>R6jx39HMNw#G zM(D1zU}YM{b^eOpVC7%^Il;OSQg#8lLZcppGkV zVxYOa`M!y}C%#bIF(ciQCJUV>ZiC{I>9Wv_4F9ZNe?DN=hYJbTYp-uB^jhm%%djFO z2fHO4v*@gLzR6Q!?6d<81gQxEjpneNQ;eW5)+?bE1BsD@{Z$G5Y%8_YBl5s`92*RcCY>ZY%FVfl z27|1Hp`B6VuBYp|PQSc^lRO{$pmk(0mWzLjC*s%@pwVjM!YpMDB3kMG1z?vfO0HXz zR*uH>03?h{l4o)6h%(b|0UCC-o0ezNEbmw@iLN_lb z#KqKDgN}7nO)RGd^6jWtoJJ)COu!e}fhqfGOg4d~gjUG*UZ#~JGTt+6N`=ki z)M)y@t=-{(>Npb3!sZ2d&e$MJB8s`1t)lJ?V8Tf9IHcj{04c|UI;wkJ!DzY{h@yb3 zIy%pyX%%&UHi2`KsY;<51gu5D++7a02XJdFW{oWYukvTt!`{_Bz(aT)Tln3TNiKiU z;4(I(Xh1p;dox;Osi=Y+ZK;DHx%_~2IDU$qNPTa>~IU6Q{>Z*CzFC_^8HkLDc zOZF+rRz^YyfU{u_G&H)j!nqIwHTw_oC}eZN3-)M{|6z*hX|biSvgb|g>$mt73$@WD zrkdcFe!nAQ9cTxgh-mpb?p!bEli-%r^7s%;$7FgACet1Imk=n{`~==bT_x9f9r`!; zyoS%CmHn^~xqK(Gy}4Sa{u%TsvLjAb$L$gmp|_wT5V3IexZ4B8^C2MEuTb2e@Tmog zDKTJ_yr}8j>wXD~>w#1-j}p+Z+jEDugF=0|5^9I0(4ZUbyei3Zn+6aP|g1)(@AwzPMy*OYJCey!LKv36}v{#bWH#-cDYMbnGc~IqFa| zwOr~<`(i_L8oJ0ku(SBL(^4$b2oqFrGO%))P2@u|axuijU*FLIG3 z+ZK9*{~`vEMMkYkbA$i25v#$La0-ZRDKNv1SXJK4amb6ZKrtVi`4O2%L9EA6tVag->_DP^Zb)ZvTb|E={P0-E*Bb$HwLJw;S)P?QnteNOvY$;Xr-7 zB)UL$I_upd&M4nA70`v~2%L?NffQ7BU!+hYL`AT`Jv#)g)WD(}rE$9ntX;;2F?AK{ zR@F));&HCRiz(%H&tMp&F565t_L*g~Z1y@tp(Hs%_j>z+~DSd*zv#CKf)dGol6$!x9PuF~dGR5V-uhaZuY3 z?r!i&4RU0UGT$>ipx#ke1~n+Bq0niYK!lRMHEUcg&D52m1x58WQ%A;y6jG^)4%H@w zMzI0z4rXGq+!?%P>H!bprADUR*3jZT7R((9g_c`?YN$7}SG4a;rFx?s^|y}pjxe6; zZyoCWi|?e>`#D}Y*Xl4)Ny=-2fRNjc%qAB0TC`3BYqd2LFpBCfnx{2wGcU+6N zXUM3M&kIdU?=?VZ*#r=}=jQZ)fD(+<%X#h7(%#KhS>$#5wDi+M5%7o%>TUb9p}lBn zRHuR$&byO77<2za>rYNtm0y2y%0t+zWn0Kre_KF4g{GBvMGwaWQ25T1@yNAm1h$sR z?l3hXrkfd(O^2#f&VgWB$#tIcl6$>b<%;vcA=v(Lncv}_=|1hvTis5R0IZmihM~4_ z07L~o-=Eezh=(J)nD!>H$}?2Mm3m|2ZeT4PwNfJ(T1sCCOqwATU6rd1;`40vCB5X~ZZy1uiM0?23)(3o)OHK8j>r4zdu; z;t32?kayxhg$y$a@dI8R(KOr3ol3GHBoKJ+p&;$WK+UdlMdztRk}-5o8kd)_u{B9U znLQyrAWa4_0iZ)(wol7JJ4txkK5fM5p$RR?e{WhZbbF3rr0DxVvyvY~#>0@)vT3Di zC$OiRPOpf&EilvW&Bho*ER}cQl*v&?4S%X_OI>QVpPd_A+giumCO7U%sXh8|qgo}B zjDyn~XH^^idx!RtY~Vrq^bQzOCOeW^6ZeC(W43D^7~tR*8`U+!iVGs_c@Xvyx*O6b z!oCrM1xxf}kkJu(HD7Yzu#qE!Xs!~}Lc~UDo$ejZhFfXG<#qR>J?`;*WV76CNBnrT z%?Bm3$NN^))(dmfcXF7s@cQrS{;e4Robe(WQz0AjQ|deo%&Nay#ZSfX3J>j z+LEEp2FChrgym<^>ktCyJK=CP{*b6h5)r~Tp>&4ocV=Q(HQf>&rkC-K(y7B*WJS57 z&W4YZ)ajykyxebHmk00h$mj6nJ2?i^B%qY-cOnE?HYdV54LE@GYmt+Hhv--EP5=|) zACWHdG33^_fT3!`DgJZM zqzufgW0|!aSq-H?0OwCB=)VQYkt3*wDbg2sE_X*g*1&xn66COdq1$)M69dqFR{bX} zp1Dnu&#A>TBxdV48XiJP|9%jjIT;C_ zM29&_dxTjq9&7MHx?Qg?E=0!)N2uyn150h%H`q}^-q45Ip<_85hQHNlgUV;13{hwB zN@evLb|`vG?k}CtV;V+GbZ7n4(O!q~R6i9~uecrZp+a)?6Fr6)Z>9=~a4iDbWUiib z+|VMMsYN0$^Zih4{vaa0c{Co&Z!_dwu!@B7%Jl-*Gq9x^*78W4K0mcJ!CF=(S1OzN z%Cy^IGk0K@U&ChZjoHkL0x6+>qFwwg9`)!cb{5W_i`y-4{P1=;v+*vwbPZD#dZ zEFKID#b$o@FHi!{I9$C@*16dEhJ1!3bF99I-oY`x(^=k!RCgC}MsHH)k#$G-)eL2m zX7XqwWZ-f8C|9D=`2&i(x61r~In=>ZGzlGwESP>o63d zOp}g*5t=)=!m;$i9chr_kvK9|cQ48$33%+;z#;g>(R(wtM>gZ0)pC!TI?FDO=M58sQ2O6}44xF8svB7{X(HFF~Y z$v_y)ehf%xk20y^?7#i&SXYjE*{iu^kQs{4#~ftF9egSd63$B@hGCr&k$+(EVI|uOwG@WGZjn& ze`F$1Gm}=pEO2ZgE|c_C&oi=_r#@BJ%*Ed1{4~9q{x5>OFY+k+_G}WReLGIL5DhdH zC@hMosf;XSN?|wT;G>DDRjRrnmU)Qr72@Utw#40Q-zFaw|E>G!ab+N~vGmPiZL6Fs zHV8I`gr>!Ykem{XoMJT?B{>`0q*4ToU})vhO>D3j<1p3@bNPEJDq~=B8L6n zz>Mg}1cxSJw30C44C-2{1i^)@bvC!&yh-?P9J~8h+)D5YqEmYRJgVH{Dk!(KLhu(} zU2f^b&x!lQAwQOG&8hL7&cqqrzO`UL!tuS0*hF`Q$BomA1sBc#GiViU&3gO?-NbCJ zw&vIJ_0&E*&3C+3%v6#aP&B`MUZXx&YwMX;t7q0~Gz29T5W)e7QhKLS z)B|dhYv{LCXV13p?VwIJRfVoU>cK0_<$X+kTHAc_i3wGWW7`2_YJ?y39$dY58riw?i1zSj0SQfH zr1tImaaVz`uFw4;4pw`(PJPeYJO)TiS!#oOvQy8+J!a3l&C<;}#q`2SZKloF@~-bh zk@JyXTG2yn3;H{+FXxG#>u}L(?Al5Pwqyo`p07==#>B2#V5S&@8NuDohaW<|4UbkaJXuNJOLu3is@YsG?Ca&Yz@{C|30MMPDxw4mun76fgSU`K?pmj_?hM#Hk0j^Bn=Noe z#d(8m8lg5#-tZ~T8|=0x|kp4K$ajcBQ#i!7_#;pHJnBFyFpK3$@M&L%Gc28l^bT_*hZQ zQjt&;w04`J01#1B`+^}IYt|Q)TbANC7Qeap{djb_WfWSm6W6z{Ew}tD-hCI(mb4-1 zL#(zT#$SecGbGcv%NjDo4kdx9pD_0x0U2|&0y<7%?q3C-X~(vR8+9k#s4Hz4U}C!Y zt!UbGvU9uFhIzm|dK)uvbNMGFR7j%S+B9GeL*AU7X`-T&?M>WUYdjv&*4RB|>xzoa z)N{WQ9rnNh&m`@+zyk;L@f@XKCef3vRsXdDlrMR{w>NqPtVO)bnKEA~yN^OoVt20Y zB-0RkbFi$yc+tw^sg;2U6tOCk+si%t6R@2_YO`NBwel+V3&&O>2qMS&XZK$83lHX6 zPrLuH>@w_oC+4weyPeBdM4xLSWY_D!#6Cr|zV{y!wO`;T(g!Sl+iX-wcx}86pFG&x zn}{6Q1ng9cL()9O3xmXI(3m6p(Ou|v`YUHvWa;- z7E2+~>9R|7Z(n(xww}f@`ow%~18o=UN%#Kc4y~S6oVDGpFI%A)_X4V`&8V*5NOdh# z)wS?|{#A_9QC)4Z>iW$Av%1<)U3IFu9)#L+xAm>Ag{l)YI>!Rmm#Qw(O9HM1MVgGa zLbkU-jAd*)30TXXxTh`6vrv<2be~%JG0iWuJiGE5ZLQGq2lt-kmuqW;mR;`GmS>5U z#jtFfwuZHYuD&4*kfPQf3kUpsiJ;&gV-#*wOivqjD5M80R?Ky=V)7G;am0&R_GLKU zb_O;OnEn}nN=zhpVsZ?+&8DPppJ+-jA!;_~NZt!se)HU{* z>rRm)CKE~(u0C2%#BCuC_RzGiL70698$bjHEIorrx?ZS(ou03~$=M=(3|ta{ zg0-~k>NZ%xnwmop8;Ng&14Ox`_G7Pc@knuRbF3+6d<<+?(;u}T{wQZ7b^4aIfH3S? zhF&H3``+}r=vl&s=p4Noo+V=<&2;!Zh+wli<_6lU06Y50l#Zl6xo%@%n@lZA-a+|| z2In@4yovw?a5EPR+YK)oW@>V=M4Dvw^`RM)yWXh-(up_)nc6kN`~rZUY=A&~gTuB=kNv5&u$=o^dtnrX542VwkuJz`>_(=8S#kcleg z?5f2KM>8ED`l{bcjF?R0mhue`1X%)j(Btnq8)&DBn$w)2FT;g>z4H-Tpoqfx_UxN! zB0oL>7utU>l+p#V>?0eOP=dFDKax+%kGxCMvBnm1Y(>>s#&(9Wv;5nEZ_;26e4pAN z@O^q8oDE&nX>)Au7d=k&p3cT^;rXrzZ7rRGhSr%O5VaXF*?W8^@|};|fsZuVpAdXP z9$Xa0bL^p9RbE%Xv6t{8SX*IythOg26ZobKgsL5ZZ&Fc4Mh%!wMz&1Hfo_CWkt(b& zJZf2u>F#B^-wv3LnoiPD`9+>G)5VYrOnR0Bfk-g+B}UVvz0`uy<$^#ggO>X&JAjZw zF1{o0`nIP9Mnpe(=2WMB+tZQiqZkTl&I+A}fuI^HbRM=^Q8$ZK+6o&>fcT!t(LU~e z+u2wJTN^cXjdr)KQgiCNn6|2J3==<|khn~0teK-oVHmd7#4SQzc!5*|N*<>gj>yFG zbNchxtuoR9a^c_l$q?Lgk0D4HN^JN!Z|B&2;cnl_{FOBj!*Bjps{Qc|xM*|i*yZvn z#n3z(ZlX1K=iU*>&Uq6n^6vDVO!rLpowRun*}^x`$=ZxuZB7m<0Ef=avFn=`qkFy@ zv}Ax5VE{M5uBfwsro=95d*|B`yWIZ_X4ESr!;TSWl)6#3iz5{U-r}x zl(t1Tm0K>KP;R*$*Dv6gH@@7m64z09XGzP)m)Gd@{)Q8C<`G z-&Fj*hv&<1eGS)Dxc&h@ifa1Q08;^t;g>ZDEp7NT5RTD zn%txtkJgyAumg4Tx@j$qlsXCYb z=~b$EgX%>WE$r8i-q$ymQ zVQLMh0PL>zcT<6s)_X(QB+MZX^iNgPZgp8CbX%mtF53gIdY z(dIOBwWi18V4@&ao9PkJ6Wz$}{12p9V0BmcdXO7QX)Fq_WOBAAZ~JM4MlCCFefFd^iV|edogu zK?IxmL67bdtES(4`0b}4R0s5XVFhT}Oc|dKm!|>d!z-&W#fw$C*tWpgaDZl{grUL< zqy4ijH1jRri7T9sV50;v*(cm0UUmmAT9tinT|)*C`m`WqkNBDSc6&wLgM2&9c)N^m zzhFnJ)!tRhi(I{TxcB#oO;-zoy0D#yoG4oL+&qYdN6q zpNS{m#rowD=ONlwI_1dIG%>5}!SpBRgS*bnEm#*K^n>Ft8zX*xzstViUcB@jP77q` zqvyo>!ufcvsGH5CHyKIqL((Q1hIGCuAFc4Sgo%bC(H0wl@XT&NPH+rt1>YvqjG32U z#efBSV|*J=>n4(p0_q6E;9Z2i_Za&ULX}hGZAVhc8~G)>q`U0;gHMTbWhCx#ZmkVC z*5cF%()<{~QW6gFZH4jnt9TnHuaPcj5scq5(O4wf@&xYQ2YI83B4L>1jU%Sg?<7ZJ zry%cDc5KY6#xEdm%Krs1h$U0>eVs@?yi?KlGr}c(OZaxF@%BIQR%R|$^}S-!H=l{_ zLZU5&xEp}J;z3j&*ZR~EhAH}Y50W^>dP_zlwmgR?Q?T)Y3gm%3#T*M0sN9ZaKqDr z$%!q*8?V)=g?O)fyEoA)l~gyW?+>29s3yKpFPe{~daJ_{Ww#FN~U`n}VehGfKKIksDG~oKc)N;!l{Jw+V>e6zIaqRp{ zw_9x)_Ds3wzbv!I#r#49y74~fOsAZO3N`8Lh2{F+Am7(}wX=aEe_!tyXTvYZkT~@t zwRxD$0x!4*aV!@g`X*bvZ^a3}B{kt6jnS$l5^hrb!VaN0IyH$8TaC9xXn4(xcfktD zKsLi&q9agAfxK?MyWP?J7hZ)=E)0&9)q!v+t&PD;91m!_7;pD!o3_k;SB>wTO!z@_ zmW`~cTSHy7P>Qj~j1?KUVqs-KZalAQ57OnY>>R%(b^FAhz*>f+;8}nkU!}1H6Hp0#)cUacmT5j2m-_P;;GH5=EYkUmL z!E2g1gteJtScEEz!vS*ke09luE>Dm}tJg~!!@>g}~!ZgcNh zu^+)#IZbug006Fp5kWY*gNP>4N3b8|m;2Qh@49)ofe$CI5?4QhGkdzUQ!-gPy2IVM z(uVCu3SBLJ1h1_76jHbkEaR(DFm($tnjD1!f%fWG_#e|W*qlQE9`DljyY@A9d2?L5 z1GzsZYP^7q8Sb}NyxjCzReBt+2pA!F-78-Zd>iG+nPE?@5OXpF{@U1GIpm_t#q#;N6zY2RinbT(R*jOkFAJ^ z{D48ZB~{MGYcWcc3RDb21+Q?mkbCQlN*tw~2(}xXidEw%sm8*@YAi%GjzTpS_Nm6v zSew_5I(*Kxv#}F(xGmsNb%={uheXD#$85@%em$#yGb9^^Q&Ig~iLu|6Se;baRN#a(9NKFR?|947i z9Bn3?m8+macO2gZ&6AJzvI{#Rcg0VTlcGT|_G9&^B0Oan@CM8p*# z?)H_}8ld+>rgwRV2gWH`#syVa=5Aks&;rV^qYd@3t_G}vn}}nEU_ligR1Fx_F>iER zwY!t^Ww}&dsEZt_3+ygn)Wrf-7r91VxDY~A7NR7~aGcGjIZS4q?eE^LdU^+q2r!z{hg7I7K{cz`;~O_XM> z-Mw|?c(5W{!0yua2s=bCB{psXdRZZDY)a~Ecn$0i%4tll-EwkU>un9sWA_TY*7A7z znsxxM!H`@}fmZ5a$JM$^4}f6Y{EIs6jmP^!mwqXM7p91f2sx|+N*8whQ1yw-6J5JQN(y8AM7_CQYJA>nXK(@V z`iKY=1bei|<{!-5^cMyTI3Ai;UH{Jf8V!3^{{U6fEEH#Rh_g%g9l8~A*X8Q*9dT}9 zg@kom_{e3T-MY&f4&-^D@tfH}VMl1rNe#N|Qb#O(b-o;~^5^ zvrthxMc=i<0PXGBrF9!RV<&<|P~GgUR-dGnhonMY(Nv5EBB~#XpABRu+TKg91C0m! zH;|4Mui(HnB9Wtzm=2>;Y1}E}*-bec#B`DF3AQ}m^y#DwgtjU)1WyAOvsX&QDAqT< z&t3`Uit3Y=^{)OC`y`wxme?u1;d(t*DN=PXDx_9JB_oWkX9HUrHl!N8uk^1Nb$;~~ zfN(Z`KDpcZ+f+ge;xx=kpoBOZz67*X1#wgm`E;zCG0l79yhwGx*~SXATm96w(}2Q* z47CH{s3W)o#zTJN?w(!F#<7sr>v^VOa8GbYM+Vd2jiq@OZuGC&G3{TZ`##F{D~VB-tfsdayOQGusL=#OCaxIP=jNqeD}jai?@&RN?uMQd4~?F+IMLopCEV z<7WrzjE~0+sv$&Lf8qpMi|T|u8$u!9d zFkvmwMya)VGPl&U1bo57MLyI=M+P6rEq^vGHu>|#AUDb^hNH2rcQ6l3?6IJ=zAznT7QDu7_CbUTJ?m+(AaXwG;IBr}d}gN*`K@e_vWpA?_?j>sBCY(AtXIKC~K| zg@ltJe>U~3(k!YVqptbdpdWP|qd#8Par%MK!1SlUb~dr?OFwv$f_`$UFa1EfAN^RZ z7^k1QOtqF)+{WlvZP=in4~l*^U`4-b*Iw7)+O+P)vbq>>)o2Xhd{DsIVia&iuXn7B z49?n!^!xb#kpsEkHVb>OFdyFgWJzdJ$ml43|t;#wwhZQu@)R003^J-KfDKWx} zjVfd)VH!`yP$I>U-lay}?_oeMOU;45tEEbM@?Zhws#@!o(J4q!V=U=DMz_~sXo~SC zaCZpqj@WQWge_rc3d9}#Ezr@fZ7}jV{xfaRB!>eX4H%@MK9X0!8_p!UlDzlA#URTf z)SyRu$@lWD;g=omx15hOLzuMf$vy^*L6Y1@J65+PI2*vWUMpNQZ>`u*LBVFR_rTN% zJurZCR?Rr?f9`8P!#Q*EUfYpif zC2=1ZPb&{S$uNC6FfcW#+rVvDaCXtK;2!;#DA&Nwd}WJMIm1Y`8fqgt#h}wn$MwH` zpnbkSv|qf=REpYqbl(QVhcF0G^add#vARD1y{?LJHV6Z#>4JImbeIef2SE}8_%&ES zBf(??xO5AEki;|zER z-L}Diufnd6GeBWr(9EtQm`B^sq?vD$D_tzZ0vdrP&Fp4yrYQak&0hs31JT@yvis2N z6fBcy{yNyukLH!anNoW@>bk=9z?% zEml(jb+$<}0pKiK{1=*oz+@nrccSb*G+!lHCegeWZ0JX`Pk56=bA5kk&m#KJ6!d?i zdQ%PT+Za5U4VzVTKI{0qz+xbJUqZQk=p8Eb*bPkZ;|T7^Q3N}z~c3)*J7^3$P;}4Sj?n!fqrES7CSNNpIy6Usz;m*9T~Hb)`FfEbNbAcCn{9b zuU{0eKaXlQkSF-%iQK6f8F4H41^+;vw8T7ViPDPwc`};IV`~lM2|jtEn?$eq#gpn6 ze7$9^9>Wo5`g{g6kz{(~xX>$GY=JKuPNn3DjyL+iKAS%Y@M66^_VZ!a4j%$f*}~o8 z4BuNHQ9b{%4X1nCOc%p-%I~u{Lzx|t0+T7L-?GBVrO%Y4$+8IT zyON9*7EeauiYGfkXv-xTr6Hb-+AE$cjnHOGGKvm{{Ea7(Q;|Wn7THJg&ICfp8a_}XPXOaI7{QnI!D)yR?HtWyh*?J}O1LF_K_b&b) zFB`aDEbVwk%62?@>lx>Rjb{8iEYzEJ6_*?ut+^`nsovEu_n)_n_ASJinl$bAp!kPd zZKIE^J|zy~KQ4%ami_p!Jd1Ieod{FG2q*hNnESAmaVnwXc<~_!OwWO_Vay?@kqzS2 zV5%`Z?WFcv0DgKyAvhblQ8&uK*S5w?{1^B#Pewm}uZ#lLX~K~^Ga~lyRhG|*0r&e_fP#V#xYwrmH~gz zuI(Ay={~qD9s6a2WQ>^BXCP7Z2BhGS?0UGME!x{-JGDLGHylR7Niq!!q%YI9Tf39b zmm2##HXa8g>nW=+F@pA>f@H9QGeO(3ehRrHQ#+*{1h+Y(H_G6+XQ{#M4ujj?McR_0 zN!oD)C2SzxW4Y)uUDM~uQ7-?Br6D25jlPp*&c+?IK<^pb+r)~g#erD3U~-73Tz1ul zBeq&Cciz*8FR69y!trhV8d-}h3Ooj{pmqc4)}F%>%80|Q!)1_<`SLL=&Q#X2ce>$V zaW-_K>AG56yNy1cn$ZO9myO$N+H(uS`wsnS)Rs|1{w zz|skrX%-?)Fpqx!`cpWjnIU9qToBBr+78)n4DONnF^o&c(Uqak$7s#Ox-sUuR<;Qp zvtI)C&69ESu@00`zYDzGOMujyYW%h`|KQei!*D@?jnLztx|bADVVkuXlXk`An?~;yTbYrkY^# zfxwtqY*KGbQFeeRlX&A*?jP_<^;@3l3c@$Rj_4HgrIBtGd8E2;bHekV`a^4fDaJ`` z=S#naoX++Qj3FYM@YwkJiaW8rf7{p{+S|ch>0Ma)?|i(~*P89m(MnA&z3O!B$H^#s ztPTG;p;7KHn`1OfZ>!YFw4zC4w_+&Z?^ePX#2+Yaui)~!Pkm%{Bva7Mw7Y7m>NcXPGdQ+)NJs3W+< zsE3~&+Xhl5Q896I$in4z?Kp#Za25>XAG^(_-jIUPdFQ2gjAZ%gKeSEaH)w3aP2?7t8tgJfeV66(JEwP=lnL66e9#^ez`iK^{`s} z8%SsdUO-L#3XiUBT!xP2#hksDH8wbx>uH?QGfU(@YUiH#To|J4do{!nmj?#E`Rf?T z6#Bg3|ATUy{IrT|Lc9vFoorw$Do1ODFLaDJ0VybO1nyt?an8B*31E}40Uc+3V%J8$ zeH-fe5JF+bnC-&}wUiIFwBSWZ1>YNu8fq;UFFd*WFgbIg5a&F7d{WLl$$w z&)u~=6X_;I8ND_E%4Zxpo(YM8A_$t`Oe^Z;jA2h^Jy4eswFxi8TBYbWzWb{VbRjJ$ zqKHiacob^3$LSBHe!GR-hQ!2@d(k^p1c9o3GmJoL{*4$+a~LVriC6*ab|ZMePh-!F95`fFrJ?G+ zNp>Zxx<|n+ldDFm7Y@dlibJ_$s_v;*q3V8!M{q8Oorm=$>l^%zJF4pv)xr|BdwN`u#``VNFAIt371Ghit>b z$$5|-TT(5~07$#O1t7J3xV53QBk&t7Oiqj~;9xa>zO~?9YsR7hi+fKf)WF#q1v*3Gz9Q z&n>UnB45E%d!}U_JC`CSl7B&x^RXpjQ#t126}46bFYAvuBQx+U7Gqdd*}??TbXRXe zRjW}-uQ^JQFKT=ul^c{1YtQcRYgXS8V~lbtag4&8ea9#&XQN=38QF?LgW1;zs*w$N z=*i$0yJvPijy9Z$Jq-%6WWgNCu|r@NRDakJ8_a13*0If}ALx0Z{^eUSQ_u!+C~^+n z{^WQ<&1RmU6z6;QWByEU^^-U8P3Dsmcp2``zM0pN{=A8}MknjPMvbm>;ohGay+MEU z&%|#MABRV;YR|MrM?%V?BlO3S3@Plv$AyxSKx_8&8Qe{LwzDA}yo`Jd^Amhqq2B=@ zS{F7?Dg5u#hWRoX%>rt8cBMYf_!xf8rdyJ~-%~VuuI`9^x9f+#my}P|7x=%8eb3R?C4U$AKZ1D~^VVpowWatNv zm=nt+8*%KlUnsZy3w|N|{vE&n#P3J=ZNcw3{L&u4eogq9SKc4OdtSe=1nbA~YryXt z_%Y4DmTs$^#Sslc>M0f zZ!vyB{CH5WSA^e~Wp^yL z>+c?Ab*mx$uIAIdp)HiZP4q88p30K@bCoQJWZ2Q>_SabpzGo((4yxvf=P7u#ulEx) ztelT5!IWKZ(QnqQ1jg9}IdMl%*p3apaH?@IZwXqM4Djl=XJK1wEL*33_RD+EbhOeE zqm>3#d&^_nL)Mb|!`7C=w)!KtN_j)KyLQJKZhB|W!TQ6u`o%vJ%~_njX$**f<{haY zzzo1TL89&HEYHYb8@A_eW?!(5h&ZfI^5QRGipWOHzD+I2#I3X8$DE0iF2XNw>JR4( z-QkxP(XmqtvLq3g6`G0orvBwLbPp{r+lC(W7qC0Ql<5ACVuX{;QnDiRk=|cm$A`!# zajTp^wVZ74hW97fHmu8ZDcqRw8Ijeik2sMaeAqT@yD<`sIG92Pg#Zw2LlgqkNcfO_ zSh%*CT~&mKVksvEI;uV5hJmLsUIZN-{(|8|hS611|BF1pR#vzd-uUH(!!8_(NYGL? zJGABWE-;qNT-eEW* z#e*mNKHY}Q_2Q$lM?Q{we*ya^eKPT@lJIvJY*OZYeTU(z4Tv^rE+| zzn&@Hi5$bd!d*HvN7|E2qJN3t-~)>6!FSb9C`vE^f-*=C`;N%Zn7!ay-g3)ARH?t< zIzDmBLVv+%-ma0`BHlLgRyf6BVq`_)-xg5yi)xjj!QW$+xr( zXTw3fD4FjsU>D;pclGo$PtRqRBRW&ry&==&?P=p^X8@b`4B@hUjNT`(`qMOezo;gx zjG;?#m!{*Evr`BU&hPEnt*n#LdrKj=7VRw+EQ&!vzA;uY0~4=@@FLC`qm{j~&}MjY zSojS0@fEMYl-M>Uo31U7Lpck+wi8RM@s17|wZ=vQ0kmTAkVy=)DyL7Ni;7@|RvZU3 zNA&_L1WuwEA~GMiskJp~Nlo|&R@mH$6*i-{BgDE?8_&_Miu*af7n@N7gsZ#pZOru) z%NmPaC>=(3_EWPFkUSFr*|0i6oib>jps1v$HW0p+y5{1l~F>@AZE99^hS{?`P z@hlL0D7vq)Wr61=6^e2_;fMc&p(sDY?e(YWS_peaH1jD@DeLBUQORLyCS1w{RZ2J5ez2 zRi-RaHYj)uG8y<5Gt&sk)PmbMzI;;IGR$n_kS#<1-9bZz!9U=D5liK(|G5Yq0q>r+ zjB5y2{?_BS z(|C8qa`e~u-HzWJ{Ft7Xd}uqT z{3>hW@Ndm~$lsCiN0iJ)14uY^2oHj|uXPA<$68Um5ei(p4H~e@jWnq819{AEU?Jsq zWc&_q_~&b}Do&6_UrTy3uZajJq$dVvO|NAOegZCR2gHE95V)>w8_;2M%($ik#Yk7K zbCc{Gua zfccrw$WLaevi$-!RyguEk<$uDE!>lM01m!zMnci51{6lHt%wZ*hXf|m`}2sZiCF?X z0b~_G>$W0EiBmyZKKgVEyq29$@}(A5;z?m@?!=r>S+gS({F&H43FRj-iJ0wYD6awA zRd=HZ&*;;gXDx5NvtN>w<+)m_gXQU3ZlfJoig1VS$+6*tKxsvWV71zmC?RF|P`c)4 zTCJYch1Qn=qtKcOLi!z0wO52}L&v^VRUXXE4CY_={2l)MS&iFPzPZ4Nig@4Uv5G=# zSgIEOAj%_UA!#8WOnHjF`#)g%J~)0Vc_Eb_BtKb;A53|yxeroa6h8-`Z|Dcy{w_dy zq-;QbUV!o_nV~hT#T4>#0m@VKu@*m=@|3)g$`7VI#ZOZC!Ia0EI|q4@F3+|5j23W` z^{8bZLJ6F<#3t6wjHSYE0OH9Bw)0S$A?&bRPe#9k9+Z8*2g`(A5?v$geUdUAKqZCAqutU(5FJUfNPX+DdZ?UZiXv zmde@O~9;Qwu!;PTbl@PO(MJ}65z281_zHkj^W38h~dX3VB*I%F*tbSaSY!2 zM0meWfX6x*9K4kEQGx6!>SMl?a1Z`B--~~PuMdIOzdi;7Z!HRn(QDR+!0TThgMpW_ zJ_KI>`WOtn4T<=z=L6(f;PtPMv%{+fsWn>Z1i;Zm9jSg~se-E&ufk2Bl#g2JB;L#D z(r7QG9(g|*|D{C*N*|OLB}`Z?9~k2$^fyW6vuUaFjdrCKOC6T-F}Zwc zU#0wy4YHVx6a-2MK`T||OV)ELe=Px7@ULkN{(-Mr@e{ZSl=3mT{Kw=y|0k7C2xj?` z^_VOjFj>*xqNBArTm{%J|URpOV)ELe?zSN^|%xIe~p_!DIb%||Czk! z|D^H>!7N|04zm0T<~y7G-NQ0d$lrXtmlj~k-@Qy|$RF<^f08$kDO1XyD*r6%TJWQgz}aA zsq&TlolE%}fU%-KrTit9|1%O-%Ku696M{+qubCtz{Ym~q_*<1tVMcwZ%f>3uy@ z`RpSBpCz3MKEJH1BYa`2l07Yt;-w^TamL08c+d;6=e22f%j5(84V<4?CFehcx&OiEPb!}z z>FdrZe^U99|NgV)AD){2bc6nL%AZiak^j(hXQO|>@)iB(ls~C_MgLjzUzVDFqyD?F zFYKA?FEM{Y`9}UV=aj#H{a2h*{-pAS{+zSscc!M_sK2glXQMwce?s|2{x#>6KY{*q zwW_n?obo4?ujo&aA9K3p);`mD*vZmV0<=JV@##D(r}IQJs7bv_&CglA)!_+jpO$C) z^XI+5C+4Z(ulw!!flto%fiL_iJ5TUa;4knG?HC;XBi(q{gworfIY{c!_F)gB=)OzY?v6}kWQ(9+e(v@SkODYY!9Hbxnp0-_m z5fNQv?Ob@D8qKJ4Bh2wn(;opB8-QS!m2TXr3YCF;U#V_`Itf-{O_eRZ)#FqNamo(c ze)M`R&w<>b+j<9%zQYo5DMFNwuXU)ns1!#N6pn$9=VHMJk_q1vtDoXLGEJ5YD$mv% zJb1>$8F_L6&j3hrpJXPxPvAcj#-)h&g!_U(T-zcG1dJ(;2E27F%NNdi8pkwIEWKI2 z48F4JKe~|ziv84HJ0+tNFE)MzHG@+CD)eR;M_M-DVHpSl?$Q0JV9yD774ybmvlPM` z82;>m;NLzFe6p-Be3=#Ug&oQKi2V5{=CKKV=rz(0>yzI3y>m9)0f?6dzlw$B)p%33^VQlztNm=xctLI6^aj5tWo99Y>=Pxam?;~41k}#%2 zlJKV1S4!4fy!lw^oZFGng$Kg#$Y3}K?+mT4nuoF~!Zu$^hALY{nTQe7d;)o;LvGyG z{CnJchXOj|Oe}z>LVRo89`N~n$?^vU}Wdh1b)ZExDv`yQN^Ngte;~$^BFG=I`L-H~;=8MT!Li_f$ zWTNtg$FZ)f+=kM30buyG^w8G`>!)eEj|A=5#e2J-YpLH;etS*~0g-GdIn=b`uV^4& zcnDAsHU(z@g-zrru-J1XtfvR-iAO`1=OdCiZkHk;d7)&-uK3+Z61QN3Gctp7GT1eR z;eSTpuzdG?L@Bm~&dr@z(hjxYG(;T$*k3I?V~3>#Cq{{4>HDFle2{2Q91!g@Fyy_` zLq@9d&MqGnNc(=|!_5DF<%4iOO!8sSbJ5C&ujLBYHc=e01soGl1&Q-H`v2LClWwVd zlN*fE^vUB>TS7YHdq4PONfLZ(YWPX^2i9NM66Qbko3QS*BZGYi{{*q5^zr#pY> z!eC)y6&v=P@i8xEKLxXI1uUu04KQ9U!8JFrFqwv&<$cqiJpqqoP*VLQ=D)j7e!{~6 zdH65+d2`16i~8gzJhk#j@{`mv=KoTk{DkM1IBm&)f6Dw4f|Sq#){)GOWWeT|aVJ*9 zv8NV2Nn(^imP~)Y{3KK5FK2!}GCzqKRDP1F@>eoHADN%T3@Sg#RQdmr`T5BFWRA)& z>uao=M}S1lCX0G-RH!E%n?Q*PPz9%GhHH0XYswUGG>mg zIZybqB8V@q1LMoDp#cB?t?^~W6JK5j#+P410sjA6<9ok>FRugR%depT|NpJ=od6te zI2ZZdZpv@#-%NgqZ`m37%ddv~{*CdaOcGyS2bKqZHRShij4x%9`0_e1zWi#)?|a2p zdS1gaXu)thSG21+0f@O-%(zzlG%axrWd?<{A;*)YHoQo`!;>4#?~3~KgY(lGp2sE` zc6-?!-lIYLg06^+LF3xfTrVQJ9Ye~&l}9o9Aj_i|e30d_8sBSqsy{;5v;{yC=h1x~ znOGT{Y1U*w5~%t#Dsv6{M=c!sDeE(|X(|3I_XRpjxTMv-;O7s1{RM`<<^scCcY)!r zzhLky0Qo}JuY!N!>Q}*EbAkDD;p$i6zy5;Zzf|gX!UcoRnhkBbaPZe$VEC-r3yJ^w z3k-j$z^}mn3!|S^{a!fuYc4SUs(vpV{_D?(zwc-cx>cMfpRgxIbxmU5Yq)1yGeg)C z-k5V1!%ABd-s{3vJ_Gkz4*iy1!(`o)Z2ynR#v(~G%%T%7o=iPi7LiC?^ZT%7o= zKmYtQ{QEg)o1ZZ!GY3j93`8mUZ#myD|Fn+30+Oe!FB zMs?A?mfWAF38D?#H%tJKn8Tw=Jlccvc_hIv1C-S8`Q`n?CuynS^UM2(fB%5+`Q<&r z??zT5U`@?FDV8x>&w@{kT2qJrbY0i!mphplGn`7wb*1KyT4jDtd`lulvi)DMGQFY8 zTNd~32g_}kJdiLr!|#<4aWFv@-ap_#N>D&BRe>0*bSc6JL>PP=f=HSwBq2_VUK)eg z#CVPt(0``+`dFOTS@NfxueYkEEo2~6iwc2N5uB0UFP?*Voa21GEjUMb{K3U@oI5=! z=Ic|U{XxcaoKc=Ru?l&f+UmJzD5= z!Dd8dOjaYG5)p4p3|TT1$O1&xT3aPlR_TfU`eT8iKxB{11)=gYgx;E+Ip@1Yy&3v$ z0Q}1+CNkA=Y2we$N=@lrn)u&;iSvJg;7=(6|3?|}FCwS5qDALt3a?1xSsTR_s>AEo zC{$?lpk<$3{yEG4C5V5L{9l6jC&~ZCj=!P$MHyEc;c=6t3-Hd=x@R@^c;D2DX&9xJ z^7gE$tpWM&xlu_uqE(^#7xdNr3(;y+^O2$u8Z3%`VG8L_+P^Q+*i9w>sqtsyy_E51 z6S$P|XA`)T@n;v{EimL?RDXZ@9bBu(AE;Gac=^AC@fZ2Ogz*>oza;P%ZF~m%-#W|t zdh(E9#jO{~Sl~U`Ly8{u59SczlB0)>BQ`!z^d!EzV$lA{(M^9p7G2|`r)g?zyVq;0sel9tB&eDFCeaPC+B}Y$UV@c6|E;)M6(ta*E zdd|{*R({CZ&m~9CS=!GfN6%T>&qYO#+ONWjDNNZ91Lq^frpmD&Mt$Ob80nUz{V=fc zS~m^|mFn6}o+fWW)R#A_KTtGflvkRYf$14RRFx@tCEH;GKoiGu~&6 zbI)i;OArrj;PSzZfzK2D6ywkj$c4X8`dRa#ACL=wpY*HxXJh~Sq<^sOfBD~e`~R@g zKiKyFVWoet?f=6{|6tqyvcL28|6!$nu`OtLE4?*1GQD+b*q^z~ena@DY5vS;`o!Hc?)JXyYNk|xeZun5HE+%|fVHXoVl$-O2594>vr~Znyzl&5q5Uoo>Kai0Zq5h)y zolpKzf2G{-)e66POmmp~y-NJpdhwn6*>%PmLa5|s>Mr;3<=1ID`|ggueQFmsM^EpX z;XaO=mlnlmu@<%&58MW2VVmp^dtvaQ;F-HeUkvzAubI0;UkvzAu*}_`2aOMf2Qw;z z?Qkm75ZxF)lCA~C?%E`Rv}<(-+Zy*@`=L6e?gxv^7|`i0tPhg4jc`X%SjP`}ua6ms2X!&DU!#U2b%QK}KB zcqp9CS>rpL#jR7rhtsvyqK5+1L!oa9qrO8zuxr*^T11g!o~Wsv(spUb)H~xW%}(x? zs=bp;cG7;SY;@-hKUQJrJH+zh#E+Z{eTP_HocJmJQJl^jex|+hJ_djrMC`=f5VNL? zVc@k@V=)R04!^StJ+Znt@KOAs7@a?S zQk=JN9_{Z|WzXGkvDn`i1wK-Z#_2*A1Aiz`=IKG_jL-YgKdOCQJnfyBgf6=FPA;9l z_WpkAO9bV-@#lQ|pICe1pCH3x{*xQvKfl57pQm4h{_~3mKeDaAf9K-C@Be4-eZZqC z&i(PT+1+FlHrYiJi3+l4P!ZHfKod8?4P-;G1Q!AcsS-d*8d3^rat>%q=;BFqcaK|X zul;wg*PBS&i*4^M*IJ6L#SNhe2-N@W2St`|?fc#pZ*L-@Ojq++5#E(khaZLi0^VAk|c) z^t78U_=m}Vw0!E#pJ(|b&Cj!ZYNWrv{Es?26ZV5)e>1^eAn|7peXRLj`AYm-MXgE0 zze$+4<8PnZ$*hM5x9D8DJ^l`QOuhUQ>9OwqPozf;_V>xN1g-Mn1B8zqLj2T|nQ_^a zw!WRl5uHC}?P6OGWAzAs?j4e1&3C_6_qVKl?$;dO+1|8|e{fKCg+|{I>bzzDz|KJG z9l)q}hzK66J2~{1<7n^Cgg1+OeKfk5H=Y`4pdJkQ*s}#30urA8#2|Y%Qg9{noC8fnK zWjsc{Iy<|V!TjuuTph61?YroV{8P~*;S)Vh7neX72QhjS``Oat^$LBL(IntxP@u73|&ygOr-iOZ3KQX;-ij0 z_2j=}JvE#@Q4T#r)s4}om^wSBPyP>2ke?-eq8xgLq!6P|F){k&|9t5$Vf2Ykj6TK0 z=#&4G(jV5JhWDh`Q3P$*FEf7+ouhr?`9?y?f8Tn+gB)W1{-@9*v5NWopF)qC>qF;m zpH!ZoZv2u+FYLb*6YD?Z|D^rr)6pjpU#LGQCPtt9pD+DPMxW@!#t+5B=#&5RrB9SY z&rrk1=u=FLKKVa;`WM+RaG=BN7oSvpi~Aj)yuF9fqY4o{>R6oK<-6NILN8O$<8+7Y z7kqlSw0Qp!da?Sk2BkyB$H*6JPV#@aIS1)=BbjMoEL)(0*_wwh3i#i*1#SmIAdrb? zk#e$~9e4~a$Un6H{c!$!SbmZ?%TJ!aEB}2gKh=@tC(qxNe=*BXQegSXGn9YW|3`}d zD;0Z@t#P=IZSHK`#lJWz$J?wskhA#?JVMEn>6w;==d&c3W?DT3k|`~aMrqGdQoEI+ zhiK7y`2u<*irC)gqv+)WCzL#$o@q1ie3mjb^JOEY1=1+(SxRcRQuN2rBhg)s-dx~> zlIPJgtpv|!DO2-&JOas-7D%JCXDO-OO3@!fk3@YrdP{*5N`8=@X%FG~EM;n@1!5qX z(gJCe_ADi}TPgY@>4B2EJ&tcAUk4(sA9&-@kq$m3`z>m!#(heBzIxVL*PXQwfF0}L z?@Ep#NxnVAEUAJNFd3pNObeEw|C^7ov zkI`rGL-f6$ias^vW#|)1j6V5e^jZ86{RN+j{*uenCzKd{^2g}2_#yh0pOAhD>_6<2 zn-<5577Xa21`e?1EhLF?~Hk{3# zt+8rvglA{i^%K__#;>ht350C=oc$jddEdQ7k{pk}XTr1bSv>O=OrVH4EaGm8_}PsV z@j7xj8aLx}`s8@}%)zP{54u+1@?c3#nK$n`}><70@;`}bQB$duD= zeGg^Hx2gK|@`OQt;K4oTZvA19bayMK@!f4J>j!yzkWHCDjsMb(vBr-H^VY9`fILyv z$II!r{s485hia&GaMrht7|v2nT)522dUwj`;nl9~WYtcfYM<$kRa-Oc)2B;|@~2Br z!cB+!4tlL*`6NHnCqc5reIhLLhOdK0 zgV?ZW2Fc+CIFiYgF_~Oerk8KQ%eUU;Tkf;KpsHNH95XFfH<0pX`Z}veZ~pzDw%!Nz z;b{COofEY6`hLnECg~6>X$cp5ed+|l8 zn>gfJJ4?Q4xYn*hB?fDB`vo;(8k^NkXlt6;n6_q-Oj9%8a4mfma7bXbdzrwxAuy(; zc^1;p+(+7(`S{d&pG`lro9c`&87FN^9!!Kpppd88^aDr@%Y}BT{51nK}Y%(SX>)Vk+0&~crt0@TZh_oE6v5`KG1qi{}EI& z=8ltSL|zWl$hQdgt@>2Pek_%%e-Ti$;K)qFsL0(R?J zps015Pv6gKx?%gw=~4n*E?jrvbm?Y@_VOc`M$Y9&@h2N!gk|0^u4uY65pF8n0Ni0X z#>-_@A|d>LR|k_EW}r<-CO?YFghckSzstAaldXzLg_CeUF!>5 zZgY36PQa&Evm*Dm@$$l{Sr=ts1ogoJ!>PAjgup0v0D-Nwa~H~~lS+CHUK<%t3|m*m zC*akysP)BvOR-54KiK;FJ$v>{9O(T^>$`FKw~37pCY~>=k&HCw|4f%OUE5!#{a%0Z z3MBl#ES#IAy`is2H;e^F+EbK{8R5KsKeB~qn7}vt@(iQ}rDw^CRERoU#2>412bO7J z`*Y zQms>e0XaDKMR<&%JCUk&Yj3FUS+!U7z#y)6;8NJtNUsoYh*!1TyexK>M)S3LBDHyJ z-3+ND>u}j7OGiP5&A>1CK!zR836~0&uANijw6iUvk=5NMC-{ss3j|ua?ciyMwIk#V zy>j%RK6xzTQLmRl#Ip+(>&G$U3bXI;_jEXM{(p z&Oi!^%e-$ofRXk(P&T>{xA=Z_M`~Me3>D1%8|Jb~l>CPBm7pF!1a1ozgyyMSpxr7_ zy|k&#h}P05AFb1Cq`C8q1C0=hJYN4-%!)00uyaL@Q7xU^lhU#iOb5p_;^{N~AWBqT zqQC7S?&ybcQoh{+zAuWPW*Gc2_E55~M)L6*QdYqyWkgyU7)%A9==cX(c${*&g5?w~ z(_tmn=~vZZCAke+!FZ+A6S+`OPPa56~ zKM(+KUXsY*BnkT~gfk;oFgQiJj=^j{_JfGem2$EAn%(Hq3ebtLYag#Zis3Um!(unj zod*Q_Xb=n*WTdtiWL#&%W2}US3zggisV`P{crCk~dl8(LL5tNHZx{ZlxxDKF;@II-vgg)rNtOV+@9OP#98%eW7_Y6`6J|E0{7z zx-3cCds;1lf()}fUQ6>oL`Qy;Ahz{y8-^t12U#G{n4}^_{{hO6HB&m(tJ((Cx#+PY zYE1Lc0*$CfD2+-U#=ZaTfla0Wi@iiOoUv*ouxdo(vLe3c<9JmP!FrFC-f@`^tOck*X#1U=Ehz=`NfPQV&Qaz2FK4ObW62fyd{S8U6YRuBo2pe~XXS2#w zQ;5c?RNaUwqfQSaGZtVYSy>w?*+}b09JD624Z0ag4JER2B96xIQh?(8yG;0;w*>Pn zA%^@^V5l?05qZ~AWw2akuM?GN$EQVk>1eKe6if~sy9>o&v@p+XcXE^ z@lWyi*)bk!=2ZuV#W(iEnD~12+M#%Gjtm7%(`W&guxPbk(HYt7Sr7L505& zOWZt|SUr@8{S@^<^@J1J=>>?XjMt4xb4ZW}xFGYop}%~jTdOl6$GOHE>JB^Ue3oh6 zaN)L){D5imT^eqf`kXl?^C58`Xd$tFOaa4MpoIkYF{r~DrGPPm$k3A7hj2B3%=$1 zeu-c%~%%2Ry9zrGFo_$SvNxa~EQq_u(DFG$*Z>K|ajEg=&wCcDw<+wto5dD^+q z?$G=Ur~jx!ecKUg4b09+&u~;ZwnVbFYn`ocTg`YRm^-V$mb0o=w`6r{Z9!zv($1iz z1NOVwv4nO&FR=^mQ)d=wr)WA=eh9fZYb;zc=tQISX)j?{pG-N`4hxD*XPf(AK~6sc zqj^fG)Bm<3keb5_{)2uFb)GqgSk~TE4syEfmb)FaVpezDJk%A~l+#i`^G3wc^jCeD4LLii!g%D;bAkch;sasA9C9h?X^9LhsN>YcFg7m}8eif5;hjpRAcn;Bi z!l4NImDZSyH2W{6x%z|X>{{|MWSgrccM(8`PsSP#2A5ZZtUP@kxL|}a^`~(0y}?8moqr+O z8Kk@CVVV?Fq1Z9@of9EXM)fsDb*@i80RkVQ`aK(?+RhtfajZouK5>(zQIj)av9{q$ z6E0BJONSoK)G13!Lx(oSZA0a72GG-HlP3hHQWwnte+Y{`PEm)p9(WC9bskCMbPNnr)(=K@o;;+s) zADl{?1(t%$%R@^hGD_cGe z*5cI^)Pe>j&sw3_GJ2n-aGPM5{1-5s4u&te$OPl#!1$Pq^Bq=VoP@`U8qPejb%Xtfxl#Q%?>NY(XWO2I*ptF0;R({JC!onr;B)>PNTlh`!#yCjYir~ zuf{S+^y?6|DfuY^{nts$}Nb`l-hWdOn zp_GIU-Q6b7WqoI`Qn5}myujT7R-}I8g#})y{5f-!9b}C0((|xOkMGA&GsZiae=U2S zV9##$Jj0%S>{-p8OW4x|@fzbt)w(RXG zC^g~lpi}&n15%*4Kj8hKCN7%julzs?c>8Ni3&X`ps3tW0ToCH_)tDG*b;oF^q_|ye zj$e6`Zt&U}QT2qqM6K+@e4;hjLSw^gOhKhz&GRK<6t8nQwzSpQeEMR{f#u~18B7mbHAT?G8|8? zfu{y(RGcrHQD)G4MlkQ#a(bP;#!{+3i!%u@Kca=?7qZl*Q2Oy@5TfQyHH!PSMfOcL#o2UNjz^ORz5Ubx(JySW zYy-+SlxsJclxb>LoSeA6p$`)9uSrC^P0CUFR^O!G12%fRsiqF;dL+r9)@MYWC^sXH zOryOA&Fw*NABwhuaL^3YIN7Fo`+5%|8ZbIS^V(6VxL|e`KC^xQt~Tf|e0VM9bR9YX zJra}I<809oXLMGlc3}p!;h~=Z(P|3?B?|s8f`m+c4JFuhx-o#=8bwgCcAYNDP(+#j zAMn+f7U$$WjnSrzM5@h5dx^GsP=WyJ+6=o?qLEcG@B2UrjCqO>b6e(fMy}VAeejir zzuNEauDeo;r&<`D>U$RVR6?o$Yelt-I7=taXc8$eufCZgGALp)i)cE0znq|tfq!uu zaS&wO7->7ez~Z)=WPJfOGkN}tc}lT^vAI!^`s6`^?Uv|YE81XfE-Nj`GN7?jM?@M@ zXVL<@_h9dv>Op3}VheLDlw)b>LusRtmOdh)q+<_~PWjP)PqN*H6L5btAAU-ipOK1G z=icbvLwn!ddvL^ILuKj;{+tQ9-|bfb0%!(Hpmik$lI-b3cy_&&B`KmFrSp^>%g?wQ6K zi)@)St9P%~itM|ns_f@K>#4uWf7VqW@A$9I{pZ@W?XBm=`j3v?-`56@wtWzB7=9YZ z|IzmSU2R$?UPA@?+c3Um?Q7eED%em_=ZKwn+Lc6vExkvcCDk>z;MfrDZK>O3Ak@IG&(-Vqr;XS+G(!(td^p#YyAq!L$|VMcPhm z^?(peu12W!sLlLJFnJkyb?3$nw6Pgw=;<(e>R&{^(O<@#6I43G-XEfKM}VqFu{{iWpYHK? zn&fyW;Mb$(nxut3LwUNi6nkZ$`1H|L14hu>?9=}X*){J8&88&=i@)!it51blwY
;HjdHE$D0niQ-4>#Maf=L_Ze>iJo6l6ro&a@T&DBBv@HsC!OO+nrP z@A+u_K=J8;;`9E7laizHKInOW!zn4K*cEW%40>NuBbK1osd@T?-q%@88_r8gDrUP9 zIVpFB9PckaZ$vHa-t#8R9*!+p>Z>kwU}aF*A>XVHJS3;817DXNhGL`nUjHS;hZzq+ zOg;K)3I)9$%jIi9(9u+eDB@jf=yxl@B2 zLg!qU(BWmCSQBGvFVQ=CE|}UE@~M3swQcbkwDKs!dn|BWAuGLhsPx}tkC~4FqErnc zKEP0l8A>yy1>%X1!4Ac!mH!>o;)YRE8;(K5-^9RF&sX9~-g&R1$&b(=_0{e1m7K}l zw9G$P4q<`1VhzL>axq;O!&R)~&{rLd-Dfy5FfS0w^MAXPP*{g&LP4J(p+tW}E^deY_ypjfM=42$ z_e_Vk3$29A=cRS%rSFuIjap#*NgKbhD#T2VPMT4gq2jZM5Rh&2olg zi`nfx0s96D9w?4-PTJ5BHSbFp9LMP$f*}f8gMDVO!S27O!qN1fB+icT!=~dufR8%X z_2>aumr);zZ}*-wMMsL!P>sTQFH_HFI-0!TLZr;$DgN^hL!|QU{__jvThxJvJG{rJ zYS`$lsyKd?Kriku(X)YHUS5-V|8cqsChX0+7P!dS3@5DV%?~5g}qPX z08ig`6*%1i7I%DIN%7yaNJ)Ht(ad)5f4ZXP(1P8egxp2);+eU(D*283e|C|4 z=ghnJ)>}fYQA=ohXz$&fYsNXY>~n1Co_MMdpNQIyPx1c31Ov&wKZOgdPutR`&r$Tr z`=T#6w(JabYONP1NB_-#4?3m^Qe{k-Ax(@sO?jPqhM}Cm45pVLwY;3kF(uXs9gR;z zAgm8wLz(*wQZMp4ZD)-MM84?Qwn*RqwzjkNtl6<`cJk9%yPNjPOQ<=OdH#lXB{`pO zBm&-@m_>RD@=bm@nH9I&3~B-IE@LWfK*HXg*uQM|=?`Iw#bDhIaa9H53k#!WjEtc2 zI$Kji5g&g53YD1l=^Lqp@*Jhp(ReewybpbYTFtu$wS6~|Ok`^ig<^+nL^ zN}75;yP-ssXD-)Qp}eRA>+&Q+IbtXW^>v8gLG`nF%>tD>#d&0Rd@BK?OJC-vh%^lsBl%{-FN$X!MdI6e)3l4Rp znuXVn?UXO5-NHc>-it)-?ngAQogCNuM|Gf3v8a{Dn5J|zlJSal=LxJ=bxJaJV6SV% z2ZJ`P_(-tG5_S3;PO~0n31&ylRl)25Z`APGvAT**XFdHWN$g;`V@rIYyA9fcmI4a~ z_t$((q%+K40TXyBh3=h%G!4R68%`LC6DuX97`r+D!={x=fz}<0Z-lMAl-fCyHxkBh z6qO}4uTd%G(NTPqG$6#|>*2!=j+Ho4^X|cVH#yrIRczSOVjA}DVS7X&_0n}t+7HpL zdiMKF?hXoHu(O60;GI9J?NyCGA4V?_4w zpmGvg81gm!+87vEXYN$HjjZiztK~V#WLhulT0Yjc^5n*2a>jFzgCt)GfHibn^!NCy zXexDLYg;&fV9@u$ps!!}_%@{ZqqZUGd>hj8Gpy0G4!hfwX<_d%*kLM4tG|ytSn)Sw z?Triuv7S|`uZ9;=wPAN<+v;CG9oUzNKr+)xNKNW4@}p?r$sI zHt8|gsnKZPM>TTB+>DTxw+VhZrPO$%=1Sc~%A&?}KXqIkH(sQs!{s<6bIM6bTtCRX+C>R zT#5es=*5aZ4kOp1wMDgc!M&JWVJS?4ZO|52l*lcGiBez@EZ49U z!d{3;!AzDyoT5um>c&)JO&`OW+=Wj19D0=!@7Ut)k0#^!fn!T?e`}X*L@TUEuuC3+ z$=R4XX#`zdFs6#7IcEvSKfraQZrH? zNAm&TD;WRkG^xA{E6g+^5}X!|*Z)A;0IAdq$`>1V^1CY)iv$6Km&;A~EOIWouM6$3 zbsMYE>uvgt)F99&e74pW+L{gr7um27LcDt3idEwh8w>&qkDsN6I1jVs+tdOZF%8X z_)!X9m-l2W{1}B(^1e;s1e6~_*jT-=#%Am<53XBSz8FXGsOVe-$y8W4pEQ0Vsg$&* z(YMJo$c|EyCxL6Gm~$qVY1`$I;oMo_++V8^ySt;_l9Q?YEo`3^wujuU6p}H@p`_Kk z*|3e^o0s~PFqe?=8bR!4uUE#0u>a4!UQUAw9*O0jPk#d)4b36HQyBe|f7AIWoE1%7gI?{c%DsGw#$M1mSYE-BND;AbYH`Ymvd2&~t!Y9vnl(1+QCN(P1qD zr|4BUT%a*O_d@Q^V$R#S6pcSv ztOY|gtSmX?^f

*`p3G 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 ROM.38G - + Where is the path to your ROM image. This will create a file named ROM.38G. This tool will also check its validity. - HP48: If you have already used another HP48 emulator, you can convert the ROM using the Convert utility. - + To do that, start a Command Prompt while running Windows, and type: Convert ROM.48G or Convert ROM.48S @@ -136,9 +136,9 @@ In some cases you have to fix Color 0 in your KML script file, because the colors red and blue has been swapped in the "Lcd" section (bugfix in a previous version). Don't use TRUELCD.KMI for emulating display contrast in your scripts. It's not fully correct. The hardware contrast values are in the area from 0 to -31. But the HP48 ROMs bounds them to useful values. The S(X) ROM use only -display contrast values between 3 and 19 and the G(X) ROM values between 9 and -24. +31. But the ROMs bounds them to useful values. The HP48 S(X) ROM use only +display contrast values between 3 and 19 and the HP48 G(X) ROM values between 9 +and 24. **************** @@ -147,8 +147,8 @@ display contrast values between 3 and 19 and the G(X) ROM values between 9 and The command line syntax is "Emu48 [E48file [Port2file]]". The first parameter sets the filename for the emulation data, the second parameter the Port2 file. -You're not able to set a Port2 file without setting the emulation data file. The -arguments are optional. +You're not able to set a Port 2 file without setting the emulation data file. +The arguments are optional. ******************* @@ -156,23 +156,23 @@ arguments are optional. ******************* There are two ways to transfer files from or to the emulator. The one way is to -use the serial port to transfer the data directly from your HP48 to the -emulator. The second way is to load data, saved on your PC, into the stack of -the emulator. You can do this by using the Edit/Load Object... command or with -the file Drag and Drop feature. But there's one important restriction, the data -must a HP binary file (begin with HPHP48- or HPHP49-, this depends on your -emulated calculator)! If not, the data is load as string. The Edit/Save -Object... command will save the data in stack level 1 on the PC (always binary -mode). Be sure, when you use the second way for data transfer, that no program -is running on the emulator. The second way doesn't work on a HP38, because he -has no stack. So you can load aplets only from the serial port. +use the serial port to transfer the data directly from your HP to the emulator. +The second way is to load data, saved on your PC, into the stack of the +emulator. You can do this by using the Edit/Load Object... command or with the +file Drag and Drop feature. But there's one important restriction, the data must +a HP binary file (begin with HPHP48- or HPHP49-, this depends on your emulated +calculator)! If not, the data is load as string. The Edit/Save Object... command +will save the data in stack level 1 on the PC (always binary mode). Be sure, +when you use the second way for data transfer, that no program is running on the +emulator. The second way doesn't work on a HP38, because he has no stack. So you +can load aplets only from the serial port. ***************** * DRAG AND DROP * ***************** -Dropping HP48 objects over the emulator window will load program files (like the +Dropping HP objects over the emulator window will load program files (like the command "Load object...") on the stack. Be sure that the emulator isn't busy before doing this. @@ -187,12 +187,12 @@ utility will allow you to create it. The syntax is: MkShared - + For example, you can create a 4MB RAM card name SHARED.BIN (in Emu48's directory) with the following command: MkShared SHARED.BIN 4096 - + Valid sizes are 32, 128, 256, 512, 1024, 2048 and 4096 KB. If you use RAM cards greater than 128 KB in a HP48SX, you can only see the first 128 KB of the card. @@ -209,12 +209,28 @@ Port 2 will be write-protected. Thus you can transfer files very easily between two calculators. This RAM card is used by both S/SX and G/GX types. + *********************** + * FLASH ROM EMULATION * + *********************** + +The HP49G save the operation system a reprogramable memory, a so called flash +memory. The flash memory is divided into two parts, into the Operating System +and into a User Data area. The User Data area is viewed as Port 2 in the HP49G. +Emu48 saves the Port 2 data in the ROM file (normally ROM.E49). As default +setting the ROM file is writeable in the first instance of Emu48. When you open +another instance of a HP49G emulation the Port 2 area is READ ONLY, that mean +all changes in Port 2 are lost when you exit this instance. If you don't want to +save data in Port 2 and want to protect the operating systems from overwriting, +you're able protect the ROM file. To do this, close all Emu48 instances and set +the variable 'Writeable' defined in the Emu48.ini file, section [ROM] to zero. + + *********************** * COPY / PASTE STRING * *********************** With the menu items "Copy String" and "Paste String" in the "Edit" menu you're -able to copy HP48 string objects from the stack to the PC clipboard and vice +able to copy HP string objects from the stack to the PC clipboard and vice versa. @@ -233,7 +249,7 @@ something goes wrong. ************ * KEYBOARD * ************ - + To enter a character to the emulator use the PC keyboard (key translation depends on the used KML script) or the mouse. If you press the left mouse button, the emulator key is pressed as long as you press the mouse button or @@ -249,9 +265,9 @@ button. The emulator time is synchronized with the PC time at startup of the emulator. This may cause problems with other non original operating systems running on the -HP48. On S(X) calculators the address area #00052-#00070, on G(X) calculators -the address area #00058-#00076 in Port0 are rewritten with the actual time -information. +HP. On HP48 S(X) calculators the address area #00052-#00070, on all other +emulated calculators the address area #00058-#00076 in Port 0 are rewritten with +the actual time information. ************* @@ -268,28 +284,28 @@ again. * REAL SPEED EMULATION * ************************ -As you recognized the speed of the emulated HP48 is much faster than an original -HP48. The reason is, the assembler commands are emulated faster than the -original CPU can execute them. On one side this is a big advantage (faster -execution of programs) on the other side this cause many trouble. In Emu48 only -the timers work with the original speed. In result all commands like User-RPL -WAIT wait more or less the correct time. But many programs like shells or -editors use an own key handler to realize an autorepeat implementation. Normally -these programs use the execution time of each assembler command for waiting. On -Emu48 this time is much shorter, so the time between each key read is shorter as -well and you get a very fast key repetition. The editor ED from the JAZZ package -hasn't this problem, because the key input is synchronized with one of the -timers. To solve this problem Emu48 generally slow down emulation if a key is -pressed. To solve some other speed depending problems you are able to slow down -the whole emulation speed. There are two variables 'SXCycles=82' and -'GXCycles=123' defined in the Emu48.ini file, section [Emulator] which control -the "real" speed and key repetition slow down for each calculator type. Each -numeric value is representing the allowed CPU cycles in a 16384Hz time frame. -Because the used cycle statements (from SASM.DOC) in Emu48 doesn't correspond to -the real values of the CPU, the saved values are estimated by comparing the -execution time of a program to the real calculator. Increasing the value fitting -to your ROM will make the "real speed" HP faster and vice versa. No warranty to -the functionality of Emu48 when you go below the default values. +As you recognized the speed of the emulated HP is much faster than an original +one. The reason is, the assembler commands are emulated faster than the original +CPU can execute them. On one side this is a big advantage (faster execution of +programs) on the other side this cause many trouble. In Emu48 only the timers +work with the original speed. In result all commands like User-RPL WAIT wait +more or less the correct time. But many programs like shells or editors use an +own key handler to realize an autorepeat implementation. Normally these programs +use the execution time of each assembler command for waiting. On Emu48 this time +is much shorter, so the time between each key read is shorter as well and you +get a very fast key repetition. The editor ED from the JAZZ package hasn't this +problem, because the key input is synchronized with one of the timers. To solve +this problem Emu48 generally slow down emulation if a key is pressed. To solve +some other speed depending problems you are able to slow down the whole +emulation speed. There are two variables 'SXCycles=82' and 'GXCycles=123' +defined in the Emu48.ini file, section [Emulator] which control the "real" speed +and key repetition slow down for each calculator type. Each numeric value is +representing the allowed CPU cycles in a 16384Hz time frame. Because the used +cycle statements (from SASM.DOC) in Emu48 doesn't correspond to the real values +of the CPU, the saved values are estimated by comparing the execution time of a +program to the real calculator. Increasing the value fitting to your ROM will +make the "real speed" HP faster and vice versa. No warranty to the functionality +of Emu48 when you go below the default values. ************************* @@ -299,33 +315,33 @@ the functionality of Emu48 when you go below the default values. The serial ports are emulated as well now. You may choose the same serial port for wire and IR. Remember that the IR port only work with 2400 Baud. If you want to change the serial port settings, but they are disabled, close the serial port -with the command CLOSEIO or power cycle the HP48 first. +with the command CLOSEIO or power cycle the HP first. -Now it's possible to make transfers between the HP48 and Emu48. If you have -problems with the connection please try the following. There's a simple way to -check if your serial port is used by another program. First disable the serial -settings in both combo boxes and very important close the settings dialog. -Reopen the settings dialog and choose the COM port in the wire combo box to the -port the HP48 is connected with. When you open this combo box you only see valid -(unused) serial ports. Don't use the IR combo box, it only works with 2400 Baud. -The next important thing are the serial settings of the HP48 and Emu48, they -must be equal. If this doesn't work then mostly there's a hardware or a resource -problem of the serial port. Check this with connecting the HP48 with a transfer -program you like on the same serial port. +Now it's possible to make transfers between the real calculator and Emu48. If +you have problems with the connection please try the following. There's a simple +way to check if your serial port is used by another program. First disable the +serial settings in both combo boxes and very important close the settings +dialog. Reopen the settings dialog and choose the COM port in the wire combo box +to the port the HP is connected with. When you open this combo box you only see +valid (unused) serial ports. Don't use the IR combo box, it only works with 2400 +Baud. The next important thing are the serial settings of the real calculator +and Emu48, they must be equal. If this doesn't work then mostly there's a +hardware or a resource problem of the serial port. Check this with connecting +the HP with a transfer program you like on the same serial port. **************** * DISASSEMBLER * **************** -With the internal disassembler you're able to disassemble the HP48 address area. -With the default Map setting the disassembler always see the mapped memory -address. If for example you configured the RAM at #00000 you will see the RAM -and not the ROM at this address. With the other Module settings you specify a -special module for disassembly. Each module will begin at address #00000 and -will not overlapped by other modules. For Port2 I use a linear address mode, -that means that the second port of a RAM card greater than 128KB is at address -#40000 (128 * 1024 * 2) and so on. The "Copy Data" button copies the selected +With the internal disassembler you're able to disassemble the Saturn chip +address area. With the default Map setting the disassembler always see the +mapped memory address. If for example you configured the RAM at #00000 you will +see the RAM and not the ROM at this address. With the other module settings you +specify a special module for disassembly. Each module use a linear address mode, +beginning at address #00000 and will not overlapped by other modules. So, for +example, you can access the second port of a HP48 RAM card greater than 128KB at +address #40000 (128 * 1024 * 2). The "Copy Data" button copies the selected disassembler lines to the PC clipboard. @@ -387,9 +403,9 @@ Other graphics and scripts are available at Casey's Emu48 Graphics Page: *************** * LEGAL STUFF * *************** - + Emu48 - An HP38/48/49 Emulator -Copyright (C) 1999 Sebastien Carlier & Christoph Gießelink +Copyright (C) 2000 Sebastien Carlier & Christoph Gießelink This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software diff --git a/PROBLEMS.TXT b/PROBLEMS.TXT index 50930f1..c871be1 100644 --- a/PROBLEMS.TXT +++ b/PROBLEMS.TXT @@ -1,18 +1,18 @@ -Known bugs and restrictions of Emu48 V1.15 +Known bugs and restrictions of Emu48 V1.20 ------------------------------------------ - the following I/O bits aren't emulated (incomplete) - DTEST (0x102) [VDIG LID TRIM] - DSPCTL (0x103) [LRT LRTD LRTC BIN] - LPE (0x109) [ELBI EVLBI GRST RST] - IOC (0x110) [ERBZ] - RCS (0x111) [RX RER RBZ] - TCS (0x112) [BRK TBZ TBF] - SRQ1 (0x118) [ISQR VSRQ] - SRQ2 (0x119) [LSRQ] - IRC (0x11A) [IRI EIRU EIRI IRE] - LCR (0x11C) [LED ELBE LBZ LBF] - LBR (0x11D) [LBO] + DTEST (0x102) [VDIG LID TRIM] + DSPCTL (0x103) [LRT LRTD LRTC BIN] + LPE (0x109) [ELBI EVLBI GRST RST] + IOC (0x110) [ERBZ] + RCS (0x111) [RX RER RBZ] + TCS (0x112) [BRK TBZ TBF] + SRQ1 (0x118) [ISQR VSRQ] + SRQ2 (0x119) [LSRQ] + IRC (0x11A) [IRI EIRU EIRI IRE] + LCR (0x11C) [LED ELBE LBZ LBF] + LBR (0x11D) [LBO] - the baudrates 1920, 3840, 7680 and 15360 aren't emulated on most operating systems Windows 95a 1920, 3840, 7680 work, 15360 fail @@ -30,15 +30,26 @@ Known bugs and restrictions of Emu48 V1.15 -> all programs that run on a real calculator will run as well, programs with incorrect DA19 / BEN handling may run on the emulator but will crash on a real calculator +- no MP interrupt on card control circuit or timer restart - no beeper support with OUT command -> all programs that aren't use the "=makebeep" subroutine, like alarm wake up, have no sound - beeper emulation, ATTN key doesn't work, Windows 9x: plays only default sound event or standard system beep - no infrared printer support +- problem with timer2 (8192Hz) emulation in Windows 2000, timer2 + values may skip, so an emulated delay loop may take very very long - memory window of debugger view some addresses in I/O register area - with invalid data + with invalid data - Shell OS: clock isn't synchronized with real time -- HP49G: flash ROM is treated as ROM (no writing to flash memory) - -> writing to port 2:FLASH will crash the emulation! +- HP49G: the flash memory is emulated now with some restrictions + - first implementation, at the moment the flash memory is more a + simulation than an emulation + - no flash programming times, the flash state machine returns + immediately the ready signal + - only one write buffer, second not needed because of prior reason + - not fully tested, especially the status byte may sometimes + return incorrect values (error bits) + - quitting the emulator while programming the flash isn't allowed, + because the content of flash state machine isn't saved so far -12/14/99 (c) by Christoph Gießelink, cgiess@swol.de +07/10/00 (c) by Christoph Gießelink, cgiess@swol.de diff --git a/sources/Cardcopy/CARDCOPY.C b/sources/Cardcopy/CARDCOPY.C new file mode 100644 index 0000000..2c1e16e --- /dev/null +++ b/sources/Cardcopy/CARDCOPY.C @@ -0,0 +1,139 @@ +/* + * cardcopy, (c) 2000 Christoph Giesselink (cgiess@swol.de) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#define WIN32_LEAN_AND_MEAN +#define WIN32_EXTRA_LEAN +#include +#include +#include + +#define VERSION "1.0" + +#define FT_ERR 0 // illegal format +#define FT_NEW 1 // empty file +#define FT_SXGX 2 // Emu48 HP48SX/GX state file +#define FT_PORT 3 // 128KB port file + +#define PORT1SIZE (128*1024*2) // file size of 128KB file +#define HP48SIG "Emu48 Document\xFE" // HP48 state file signature + +UINT CheckType(char *lpszFileName) +{ + BYTE pbyFileSignature[16]; + HANDLE hFile; + DWORD FileSizeHigh,FileSizeLow; + + UINT nType = FT_ERR; + + hFile = CreateFile(lpszFileName,GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL); + if (hFile == INVALID_HANDLE_VALUE) + return FT_NEW; + + // check filesize + FileSizeLow = GetFileSize(hFile,&FileSizeHigh); + if (FileSizeHigh == 0 && FileSizeLow == PORT1SIZE) + nType = FT_PORT; + + // Read and Compare signature + ReadFile(hFile,pbyFileSignature,sizeof(pbyFileSignature),&FileSizeLow,NULL); + if (FileSizeLow == sizeof(pbyFileSignature) && strcmp(pbyFileSignature,HP48SIG) == 0) + nType = FT_SXGX; + + CloseHandle(hFile); + return nType; +} + +BOOL CopyData(HANDLE hFileSource,HANDLE hFileDest) +{ + BYTE byBuffer[16]; + INT i; + DWORD lBytes; + + assert(PORT1SIZE % sizeof(byBuffer) == 0); + for (i = PORT1SIZE / sizeof(byBuffer); i > 0; --i) + { + ReadFile(hFileSource,byBuffer,sizeof(byBuffer),&lBytes,NULL); + if (lBytes != sizeof(byBuffer)) return TRUE; + + WriteFile(hFileDest,byBuffer,sizeof(byBuffer),&lBytes,NULL); + if (lBytes != sizeof(byBuffer)) return TRUE; + } + + return FALSE; +} + +UINT main(int argc, char *argv[]) +{ + HANDLE hFileSource,hFileDest; + UINT nSourceType,nDestType; + + printf("HP48 Port1 Import/Export Tool for Emu48 V" VERSION "\n"); + if (argc != 3) + { + printf("\nUsage:\n\t%s \n\n", argv[0]); + return 1; + } + + // check source file type + nSourceType = CheckType(argv[1]); + if (nSourceType == FT_ERR) + { + printf("Error: Illegal source file type\n"); + return 2; + } + + // check destination file type + nDestType = CheckType(argv[2]); + if (nDestType == FT_ERR) + { + printf("Error: Illegal destination file type\n"); + return 3; + } + + // open source file + hFileSource = CreateFile(argv[1],GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL); + if (hFileSource != INVALID_HANDLE_VALUE) + { + hFileDest = CreateFile(argv[2],GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_ALWAYS,0,NULL); + if (hFileDest != INVALID_HANDLE_VALUE) + { + if (nSourceType == FT_SXGX) SetFilePointer(hFileSource,-PORT1SIZE,NULL,FILE_END); + if (nDestType == FT_SXGX) SetFilePointer(hFileDest ,-PORT1SIZE,NULL,FILE_END); + + CopyData(hFileSource,hFileDest); + puts("Copy successful."); + + CloseHandle(hFileDest); + } + else + { + printf("Error: Can't open destination file %s\n",argv[2]); + return 4; + } + + CloseHandle(hFileSource); + } + else + { + printf("Error: Can't open source file %s\n",argv[1]); + return 5; + } + + return 0; +} diff --git a/sources/Cardcopy/CARDCOPY.DSP b/sources/Cardcopy/CARDCOPY.DSP new file mode 100644 index 0000000..0005fa6 --- /dev/null +++ b/sources/Cardcopy/CARDCOPY.DSP @@ -0,0 +1,100 @@ +# Microsoft Developer Studio Project File - Name="CARDCOPY" - Package Owner=<4> +# Microsoft Developer Studio Generated Build File, Format Version 6.00 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +CFG=CARDCOPY - Win32 Release +!MESSAGE This is not a valid makefile. To build this project using NMAKE, +!MESSAGE use the Export Makefile command and run +!MESSAGE +!MESSAGE NMAKE /f "CARDCOPY.mak". +!MESSAGE +!MESSAGE You can specify a configuration when running NMAKE +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "CARDCOPY.mak" CFG="CARDCOPY - Win32 Debug" +!MESSAGE +!MESSAGE Possible choices for configuration are: +!MESSAGE +!MESSAGE "CARDCOPY - Win32 Release" (based on "Win32 (x86) Console Application") +!MESSAGE "CARDCOPY - Win32 Debug" (based on "Win32 (x86) Console Application") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" +CPP=cl.exe +RSC=rc.exe + +!IF "$(CFG)" == "CARDCOPY - Win32 Release" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir "Release" +# PROP BASE Intermediate_Dir "Release" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir ".\Release" +# PROP Intermediate_Dir ".\Release" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD BASE RSC /l 0x407 /d "NDEBUG" +# ADD RSC /l 0x407 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386 + +!ELSEIF "$(CFG)" == "CARDCOPY - Win32 Debug" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 1 +# PROP BASE Output_Dir "Debug" +# PROP BASE Intermediate_Dir "Debug" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 1 +# PROP Output_Dir ".\Debug" +# PROP Intermediate_Dir ".\Debug" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c +# ADD BASE RSC /l 0x407 /d "_DEBUG" +# ADD RSC /l 0x407 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +LINK32=link.exe +# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept + +!ENDIF + +# Begin Target + +# Name "CARDCOPY - Win32 Release" +# Name "CARDCOPY - Win32 Debug" +# Begin Group "Source Files" + +# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat" +# Begin Source File + +SOURCE=.\CARDCOPY.C +# End Source File +# End Group +# Begin Group "Header Files" + +# PROP Default_Filter "h;hpp;hxx;hm;inl" +# End Group +# Begin Group "Resource Files" + +# PROP Default_Filter "ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe" +# End Group +# End Target +# End Project diff --git a/sources/Cardcopy/CARDCOPY.DSW b/sources/Cardcopy/CARDCOPY.DSW new file mode 100644 index 0000000..0163d4c --- /dev/null +++ b/sources/Cardcopy/CARDCOPY.DSW @@ -0,0 +1,29 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "CARDCOPY"=.\CARDCOPY.DSP - Package Owner=<4> + +Package=<5> +{{{ +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/sources/Convert/Convert.mak b/sources/Convert/CONVERT.MAK similarity index 100% rename from sources/Convert/Convert.mak rename to sources/Convert/CONVERT.MAK diff --git a/sources/Convert/Convert.mdp b/sources/Convert/CONVERT.MDP similarity index 100% rename from sources/Convert/Convert.mdp rename to sources/Convert/CONVERT.MDP diff --git a/sources/Convert/Main.c b/sources/Convert/MAIN.C similarity index 51% rename from sources/Convert/Main.c rename to sources/Convert/MAIN.C index c4e0034..ec3e2a6 100644 --- a/sources/Convert/Main.c +++ b/sources/Convert/MAIN.C @@ -4,20 +4,15 @@ #include #include #include +#include + +#define HP38G 0 +#define HP48S 1 +#define HP48G 2 -HANDLE hFile; -HANDLE hMap; -HANDLE hOut; -LPBYTE pIn; LPBYTE pRom; -DWORD dwSizeLo, dwSizeHi; -BYTE szVersion[16]; -BOOL bUnpack = FALSE; -BOOL bSwap = FALSE; -BOOL bText = FALSE; -DWORD dwWritten; WORD wCRC; -BOOL bIsGx; +WORD wType; static WORD crc_table[16] = { @@ -36,7 +31,7 @@ BOOL CheckCRC() UINT i; DWORD dwBase = 0x00000; UINT nPass = 0; - UINT nPasses = bIsGx?2:1; + UINT nPasses = (wType != HP48S)?2:1; again: @@ -94,32 +89,58 @@ static DWORD Asc2Nib5(LPSTR lpBuf) |((DWORD)Asc2Nib(lpBuf[4]))); } +static BOOL IsHP(DWORD dwAddress) +{ + char cH = (pRom[dwAddress + 1] << 4) | pRom[dwAddress]; + char cP = (pRom[dwAddress + 3] << 4) | pRom[dwAddress + 2]; + return cH == 'H' && cP == 'P'; +} + UINT main(int argc, char *argv[]) { + HANDLE hFile; + HANDLE hMap; + HANDLE hOut; + LPBYTE pIn; + DWORD dwSizeLo; + BYTE szVersion[16]; + DWORD dwWritten; + UINT i,uLen; + DWORD dwAddress; + + DWORD dwAddrOffset = 0x00000; + + BOOL bFormatDetected = FALSE; + + BOOL bUnpack = FALSE; + BOOL bSwap = FALSE; + BOOL bText = FALSE; + BOOL bDA19 = FALSE; if ((argc!=2)&&(argc!=3)) { printf("Usage:\n\t%s []\n", argv[0]); return 1; } - - pRom = LocalAlloc(0,1048576); + pRom = LocalAlloc(LMEM_FIXED,512*1024*2); if (pRom == NULL) { printf("Memory Allocation Failed !"); return 1; } - hFile = CreateFile(argv[1],GENERIC_READ,0,NULL,OPEN_EXISTING,0,NULL); + hFile = CreateFile(argv[1],GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL); if (hFile == INVALID_HANDLE_VALUE) { + LocalFree(pRom); printf("Cannot open file %s.\n", argv[1]); return 1; } - dwSizeLo = GetFileSize(hFile, &dwSizeHi); + dwSizeLo = GetFileSize(hFile, NULL); hMap = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); if (hMap == NULL) { + LocalFree(pRom); CloseHandle(hFile); puts("CreateFileMapping failed."); return 1; @@ -127,42 +148,65 @@ UINT main(int argc, char *argv[]) pIn = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); if (pIn == NULL) { + LocalFree(pRom); CloseHandle(hMap); CloseHandle(hFile); puts("MapViewOfFile failed.\n"); return 1; } - switch (pIn[0]) + for (i = 0; i < 2 && !bFormatDetected; ++i) { - case '0': - if (pIn[1]!='0') break; - if (pIn[2]!='0') break; - if (pIn[3]!='0') break; - if (pIn[4]!='0') break; - if (pIn[5]!=':') break; - bText = TRUE; - break; - case 0x23: - bUnpack = TRUE; - bSwap = TRUE; - break; - case 0x32: - bUnpack = TRUE; - break; - case 0x02: - break; - case 0x03: - if (pIn[1] == 0x02) + switch (pIn[0+dwAddrOffset]) { + case '0': + if (pIn[1+dwAddrOffset]!='0') break; + if (pIn[2+dwAddrOffset]!='0') break; + if (pIn[3+dwAddrOffset]!='0') break; + if (pIn[4+dwAddrOffset]!='0') break; + if (pIn[5+dwAddrOffset]!=':') break; + bText = TRUE; + bFormatDetected = TRUE; + break; + case 0x23: + bUnpack = TRUE; bSwap = TRUE; + bFormatDetected = TRUE; + break; + case 0x32: + bUnpack = TRUE; + bFormatDetected = TRUE; + break; + case 0x03: + bSwap = TRUE; + case 0x02: + if (pIn[1+dwAddrOffset] == (bSwap ? 0x02 : 0x03)) + { + bFormatDetected = TRUE; + break; + } + bSwap = FALSE; + default: + dwAddrOffset = dwSizeLo / 2; + bDA19 = TRUE; + break; } - break; + } + + if (!bFormatDetected) + { + LocalFree(pRom); + UnmapViewOfFile(pIn); + CloseHandle(hMap); + CloseHandle(hFile); + printf("Stopped, unknown format.\n"); + return 1; } if (bUnpack) printf("Unpacking nibbles.\n"); if (bSwap) printf("Swapping nibbles.\n"); if (bText) printf("Reading text file.\n"); + if (bDA19) printf("Swapping banks.\n"); if (bText) { @@ -200,8 +244,9 @@ UINT main(int argc, char *argv[]) DWORD i; for (i=0; i>4; - pRom[(i<<1)+1] = pIn[i]&0xF; + BYTE byC = pIn[(i+dwAddrOffset)&(dwSizeLo-1)]; + pRom[(i<<1) ] = byC>>4; + pRom[(i<<1)+1] = byC&0xF; } } else @@ -209,8 +254,9 @@ UINT main(int argc, char *argv[]) DWORD i; for (i=0; i>4; + BYTE byC = pIn[(i+dwAddrOffset)&(dwSizeLo-1)]; + pRom[(i<<1) ] = byC&0xF; + pRom[(i<<1)+1] = byC>>4; } } } @@ -222,15 +268,24 @@ UINT main(int argc, char *argv[]) for (i=0; i ROM.38G - + Where is the path to your ROM image. This will create a file named ROM.38G. This tool will also check its validity. - HP48: If you have already used another HP48 emulator, you can convert the ROM using the Convert utility. - + To do that, start a Command Prompt while running Windows, and type: Convert ROM.48G or Convert ROM.48S @@ -136,9 +136,9 @@ In some cases you have to fix Color 0 in your KML script file, because the colors red and blue has been swapped in the "Lcd" section (bugfix in a previous version). Don't use TRUELCD.KMI for emulating display contrast in your scripts. It's not fully correct. The hardware contrast values are in the area from 0 to -31. But the HP48 ROMs bounds them to useful values. The S(X) ROM use only -display contrast values between 3 and 19 and the G(X) ROM values between 9 and -24. +31. But the ROMs bounds them to useful values. The HP48 S(X) ROM use only +display contrast values between 3 and 19 and the HP48 G(X) ROM values between 9 +and 24. **************** @@ -147,8 +147,8 @@ display contrast values between 3 and 19 and the G(X) ROM values between 9 and The command line syntax is "Emu48 [E48file [Port2file]]". The first parameter sets the filename for the emulation data, the second parameter the Port2 file. -You're not able to set a Port2 file without setting the emulation data file. The -arguments are optional. +You're not able to set a Port 2 file without setting the emulation data file. +The arguments are optional. ******************* @@ -156,23 +156,23 @@ arguments are optional. ******************* There are two ways to transfer files from or to the emulator. The one way is to -use the serial port to transfer the data directly from your HP48 to the -emulator. The second way is to load data, saved on your PC, into the stack of -the emulator. You can do this by using the Edit/Load Object... command or with -the file Drag and Drop feature. But there's one important restriction, the data -must a HP binary file (begin with HPHP48- or HPHP49-, this depends on your -emulated calculator)! If not, the data is load as string. The Edit/Save -Object... command will save the data in stack level 1 on the PC (always binary -mode). Be sure, when you use the second way for data transfer, that no program -is running on the emulator. The second way doesn't work on a HP38, because he -has no stack. So you can load aplets only from the serial port. +use the serial port to transfer the data directly from your HP to the emulator. +The second way is to load data, saved on your PC, into the stack of the +emulator. You can do this by using the Edit/Load Object... command or with the +file Drag and Drop feature. But there's one important restriction, the data must +a HP binary file (begin with HPHP48- or HPHP49-, this depends on your emulated +calculator)! If not, the data is load as string. The Edit/Save Object... command +will save the data in stack level 1 on the PC (always binary mode). Be sure, +when you use the second way for data transfer, that no program is running on the +emulator. The second way doesn't work on a HP38, because he has no stack. So you +can load aplets only from the serial port. ***************** * DRAG AND DROP * ***************** -Dropping HP48 objects over the emulator window will load program files (like the +Dropping HP objects over the emulator window will load program files (like the command "Load object...") on the stack. Be sure that the emulator isn't busy before doing this. @@ -187,12 +187,12 @@ utility will allow you to create it. The syntax is: MkShared - + For example, you can create a 4MB RAM card name SHARED.BIN (in Emu48's directory) with the following command: MkShared SHARED.BIN 4096 - + Valid sizes are 32, 128, 256, 512, 1024, 2048 and 4096 KB. If you use RAM cards greater than 128 KB in a HP48SX, you can only see the first 128 KB of the card. @@ -209,12 +209,28 @@ Port 2 will be write-protected. Thus you can transfer files very easily between two calculators. This RAM card is used by both S/SX and G/GX types. + *********************** + * FLASH ROM EMULATION * + *********************** + +The HP49G save the operation system a reprogramable memory, a so called flash +memory. The flash memory is divided into two parts, into the Operating System +and into a User Data area. The User Data area is viewed as Port 2 in the HP49G. +Emu48 saves the Port 2 data in the ROM file (normally ROM.E49). As default +setting the ROM file is writeable in the first instance of Emu48. When you open +another instance of a HP49G emulation the Port 2 area is READ ONLY, that mean +all changes in Port 2 are lost when you exit this instance. If you don't want to +save data in Port 2 and want to protect the operating systems from overwriting, +you're able protect the ROM file. To do this, close all Emu48 instances and set +the variable 'Writeable' defined in the Emu48.ini file, section [ROM] to zero. + + *********************** * COPY / PASTE STRING * *********************** With the menu items "Copy String" and "Paste String" in the "Edit" menu you're -able to copy HP48 string objects from the stack to the PC clipboard and vice +able to copy HP string objects from the stack to the PC clipboard and vice versa. @@ -233,7 +249,7 @@ something goes wrong. ************ * KEYBOARD * ************ - + To enter a character to the emulator use the PC keyboard (key translation depends on the used KML script) or the mouse. If you press the left mouse button, the emulator key is pressed as long as you press the mouse button or @@ -249,9 +265,9 @@ button. The emulator time is synchronized with the PC time at startup of the emulator. This may cause problems with other non original operating systems running on the -HP48. On S(X) calculators the address area #00052-#00070, on G(X) calculators -the address area #00058-#00076 in Port0 are rewritten with the actual time -information. +HP. On HP48 S(X) calculators the address area #00052-#00070, on all other +emulated calculators the address area #00058-#00076 in Port 0 are rewritten with +the actual time information. ************* @@ -268,28 +284,28 @@ again. * REAL SPEED EMULATION * ************************ -As you recognized the speed of the emulated HP48 is much faster than an original -HP48. The reason is, the assembler commands are emulated faster than the -original CPU can execute them. On one side this is a big advantage (faster -execution of programs) on the other side this cause many trouble. In Emu48 only -the timers work with the original speed. In result all commands like User-RPL -WAIT wait more or less the correct time. But many programs like shells or -editors use an own key handler to realize an autorepeat implementation. Normally -these programs use the execution time of each assembler command for waiting. On -Emu48 this time is much shorter, so the time between each key read is shorter as -well and you get a very fast key repetition. The editor ED from the JAZZ package -hasn't this problem, because the key input is synchronized with one of the -timers. To solve this problem Emu48 generally slow down emulation if a key is -pressed. To solve some other speed depending problems you are able to slow down -the whole emulation speed. There are two variables 'SXCycles=82' and -'GXCycles=123' defined in the Emu48.ini file, section [Emulator] which control -the "real" speed and key repetition slow down for each calculator type. Each -numeric value is representing the allowed CPU cycles in a 16384Hz time frame. -Because the used cycle statements (from SASM.DOC) in Emu48 doesn't correspond to -the real values of the CPU, the saved values are estimated by comparing the -execution time of a program to the real calculator. Increasing the value fitting -to your ROM will make the "real speed" HP faster and vice versa. No warranty to -the functionality of Emu48 when you go below the default values. +As you recognized the speed of the emulated HP is much faster than an original +one. The reason is, the assembler commands are emulated faster than the original +CPU can execute them. On one side this is a big advantage (faster execution of +programs) on the other side this cause many trouble. In Emu48 only the timers +work with the original speed. In result all commands like User-RPL WAIT wait +more or less the correct time. But many programs like shells or editors use an +own key handler to realize an autorepeat implementation. Normally these programs +use the execution time of each assembler command for waiting. On Emu48 this time +is much shorter, so the time between each key read is shorter as well and you +get a very fast key repetition. The editor ED from the JAZZ package hasn't this +problem, because the key input is synchronized with one of the timers. To solve +this problem Emu48 generally slow down emulation if a key is pressed. To solve +some other speed depending problems you are able to slow down the whole +emulation speed. There are two variables 'SXCycles=82' and 'GXCycles=123' +defined in the Emu48.ini file, section [Emulator] which control the "real" speed +and key repetition slow down for each calculator type. Each numeric value is +representing the allowed CPU cycles in a 16384Hz time frame. Because the used +cycle statements (from SASM.DOC) in Emu48 doesn't correspond to the real values +of the CPU, the saved values are estimated by comparing the execution time of a +program to the real calculator. Increasing the value fitting to your ROM will +make the "real speed" HP faster and vice versa. No warranty to the functionality +of Emu48 when you go below the default values. ************************* @@ -299,33 +315,33 @@ the functionality of Emu48 when you go below the default values. The serial ports are emulated as well now. You may choose the same serial port for wire and IR. Remember that the IR port only work with 2400 Baud. If you want to change the serial port settings, but they are disabled, close the serial port -with the command CLOSEIO or power cycle the HP48 first. +with the command CLOSEIO or power cycle the HP first. -Now it's possible to make transfers between the HP48 and Emu48. If you have -problems with the connection please try the following. There's a simple way to -check if your serial port is used by another program. First disable the serial -settings in both combo boxes and very important close the settings dialog. -Reopen the settings dialog and choose the COM port in the wire combo box to the -port the HP48 is connected with. When you open this combo box you only see valid -(unused) serial ports. Don't use the IR combo box, it only works with 2400 Baud. -The next important thing are the serial settings of the HP48 and Emu48, they -must be equal. If this doesn't work then mostly there's a hardware or a resource -problem of the serial port. Check this with connecting the HP48 with a transfer -program you like on the same serial port. +Now it's possible to make transfers between the real calculator and Emu48. If +you have problems with the connection please try the following. There's a simple +way to check if your serial port is used by another program. First disable the +serial settings in both combo boxes and very important close the settings +dialog. Reopen the settings dialog and choose the COM port in the wire combo box +to the port the HP is connected with. When you open this combo box you only see +valid (unused) serial ports. Don't use the IR combo box, it only works with 2400 +Baud. The next important thing are the serial settings of the real calculator +and Emu48, they must be equal. If this doesn't work then mostly there's a +hardware or a resource problem of the serial port. Check this with connecting +the HP with a transfer program you like on the same serial port. **************** * DISASSEMBLER * **************** -With the internal disassembler you're able to disassemble the HP48 address area. -With the default Map setting the disassembler always see the mapped memory -address. If for example you configured the RAM at #00000 you will see the RAM -and not the ROM at this address. With the other Module settings you specify a -special module for disassembly. Each module will begin at address #00000 and -will not overlapped by other modules. For Port2 I use a linear address mode, -that means that the second port of a RAM card greater than 128KB is at address -#40000 (128 * 1024 * 2) and so on. The "Copy Data" button copies the selected +With the internal disassembler you're able to disassemble the Saturn chip +address area. With the default Map setting the disassembler always see the +mapped memory address. If for example you configured the RAM at #00000 you will +see the RAM and not the ROM at this address. With the other module settings you +specify a special module for disassembly. Each module use a linear address mode, +beginning at address #00000 and will not overlapped by other modules. So, for +example, you can access the second port of a HP48 RAM card greater than 128KB at +address #40000 (128 * 1024 * 2). The "Copy Data" button copies the selected disassembler lines to the PC clipboard. @@ -387,9 +403,9 @@ Other graphics and scripts are available at Casey's Emu48 Graphics Page: *************** * LEGAL STUFF * *************** - + Emu48 - An HP38/48/49 Emulator -Copyright (C) 1999 Sebastien Carlier & Christoph Gießelink +Copyright (C) 2000 Sebastien Carlier & Christoph Gießelink This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software diff --git a/sources/Emu48/CHANGES.TXT b/sources/Emu48/CHANGES.TXT index f32f064..0a74f43 100644 --- a/sources/Emu48/CHANGES.TXT +++ b/sources/Emu48/CHANGES.TXT @@ -1,3 +1,353 @@ +Service Pack 20 for Emu48 Version 1.0 + +DEBUGGER.C +- changed function UpdateStackWnd(), index of topmost item is now + saved at content update +- changed function CheckBreakpoint(), allow breakpoints in an area + now (used by memory breakpoints to get all touched addresses on a + r=DATx fs access) + +DEBUGGER.H +- changed prototype of function CheckBreakpoint() + +EMU48.C +- use semaphores to avoid GDI trouble with NT in OnPaint() +- specify processor affinity for the cpu emulation thread to avoid + problems with the QueryPerformanceCounter() function + +EMU48.H +- extern declaration of global variable + +EMU48.RC +- fixed multiple use of accelerator key in debugger menu +- changed version and copyright + +ENGINE.C +- bugfix in function Debugger(), check complete address range of + memory access area +- workaround for Win2k in function AdjustSpeed(), when command + sequence took over 50ms new synchronizing of the emulation slow + down part +- bugfix in function WorkerThread(), update NINT and NINT2 lines + after port status changing + +KML.C +- use semaphores to avoid GDI trouble with NT in RefreshButtons() + +MOPS.C +- bugfix in function UckBit() and F4096Hz(), depending on clock + value functions returned wrong results + +OPCODES.C +- moved address field start and length array from OPS.H and made + them public + +OPCODES.H +- extern declaration of global address field start and length + variables + +OPS.H +- moved address field start and length array to OPCODES.C +- bugfix in function FASTPTR(), address area is only 5 nibbles long + +RESOURCE.H +- deleted several unused definitions + +RPL.C +- added function Metakernel() to detect MK2.30 and prior +- changed function RPL_Pick() and RPL_Push() to support + "Save/Load Object" with a stack incompatible Metakernel versions + +SERIAL.C +- use own OVERLAPPED stucture for reading and writing +- bugfix in function CommClose(), added additional delay to fix + problems with some Kermit server +- bugfix in function CommTransmit(), wait until write completed + +SETTINGS.C +- changed function ReadSettings(), use variable content as default + + +Service Pack 19 for Emu48 Version 1.0 + +DISPLAY.C +- added io.h in header definition +- bugfix in function CreateMainBitmap(), check error condition and + realize palette also in memory DC +- bugfix in CreateLcdBitmap(), must set color palette for LCD DC +- bugfix in function UpdateAnnunciators(), annunciators are off if + timer2 is stopped + +DEBUGGER.C +- several modifications for memory breakpoint handling +- added step over interrupt code part setting +- bugfix in function NewValue(), at wrong input numbers ignore input + string at and set focus to edit control +- bugfix in function EnterAddr(), at wrong input numbers set focus + to edit control + +DEBUGGER.H +- added breakpoint type defines +- extern declaration of global variable +- changed prototype of function CheckBreakpoint() + +EMU48.C +- added io.h in header definition +- changed function SettingsProc(), added HP/Class mnemonic setting +- bugfix in function SettingsProc(), new implementation of card + detection port 1 +- moved initialize/remove of the Critical Section part from main + program to message handler +- changed function OnDropFiles(), OnStackCopy(), OnStackPaste(), + OnObjectLoad() and OnObjectSave(), don't wait for changed state + after function WaitForSleepState() +- bugfix in function OnObjectSave(), set info message when emulator + is busy +- changed function Disasm(), removed HP/Class mnemonic setting +- bugfix in function Disasm(), addresses > 0x1869F showed in wrong + format +- bugfix in function WinMain(), synchronized thread start + +EMU48.H +- changed definition of PORT1_PRESENT, PORT1_WRITE, PORT2_PRESENT + and PORT2_WRITE + +EMU48.RC +- added HP/Class mnemonics in "Settings" dialog +- added "Interrupts" part in debugger menu +- added "Enter breakpoint" dialog +- changed version and copyright + +ENGINE.C +- moved debugger part from function WorkerThread() to own function +- added C=DAT0 A, D0=D0+ 5, PC=(C) code sequence detection for RPL + breakpoint in debugger +- added step over interrupt code part handler in debugger section +- bugfix in function SwitchToState(), when switch from Invalid into + Run state then don't enter opcode loop on interrupt request or in + SHUTDN mode +- changed function WorkerThread(), added memory breakpoint handler +- bugfix in function WorkerThread(), timer emulation in debugger + part must check the timer RUN bit +- bugfix in function WorkerThread(), must set wakeup flag on SHUTDN + on interrupt request condition +- changed function WorkerThread(), minor optimization + +FILES.C +- added io.h in header definition +- bugfix in function OpenDocument(), check card detection only if + enabled and set NINT line to low on a MP interrupt +- changed function DibNumColors(), optimized by removing case switch + +IO.H +- added ANNCTRL and CARDSTAT definitions + +MOPS.C +- removed conditional compiling with WSMSET +- bugfix in function RomSwitch(), mirror smaller ROMs than 2MB +- bugfix in function Npeek(), wrong content of SRQ2 register (0x119) +- bugfix in function ReadIO(), wrong implementation of the SMP and + SWINT bit in the CARDCTL register (0x10E) +- bugfix in function ReadIO(), wrong implementation of the NINT2 and + NINT bit in the SRQ2 register (0x119) +- bugfix in function WriteIO(), wrong implementation of the SMP and + ECDT bit and removed some wrong stuff in the CARDCTL register + (0x10E) +- bugfix in function WriteIO(), the DA19 bit in the LINECOUNT + register (0x129) is also available in the Clarke hardware +- bugfix in function WriteIO(), the RUN bit in the TIMER2CTRL + register (0x12F) has an affection to the display annunciators + +OPCODES.C +- added io.h in header definition +- bugfix in RTI command, low NINT2 or NINT line reenter interrupt +- bugfix in function o808C() and o808E(), opcodes PC=(A) and PC=(C) + modify the CRC register + +OPCODES.H +- moved HST bit definition to TYPES.H + +RESOURCE.H +- added several definitions + +SETTINGS.C +- changed function ReadSettings() and WriteSettings(), added section + [Disassembler] in INI-File + +TYPES.H +- added definition of HST bits + + +Service Pack 18 for Emu48 Version 1.0 + +EMU48.RC +- changed version and copyright + +ENGINE.C +- bugfix in function WorkerThread(), ignore SHUTDN on interrupt + request + +SERIAL.C +- bugfix in function CommOpen(), must wait for EV_RXCHAR event + thread directly after opening the serial port + + +Service Pack 17 for Emu48 Version 1.0 + +DEBUGGER.C +- bugfix in function StrToReg() and OnLButtonUp(), wrong string to + HEX conversation with lowercase letters +- bugfix in function Debugger(), used wrong background color in + WM_CTLCOLORSTATIC message handler + +EMU48.C +- bugfix in function SettingsProc(), don't overwite Serial-Ir + setting in HP49 mode + +EMU48.DSP +- added i28f160.c sources + +EMU48.H +- extern declaration of global variable and functions + +EMU48.RC +- changed version and copyright + +FILES.C +- added new functions in the ROM patch part +- changed function PatchRom(), original content of patched address + is saved now +- changed function MapRom(), added ROM writing feature +- changed function UnmapRom(), restore original content before + closing +- bugfix in function MapPort2(), shared mode now works, when first + program instance opened the file with Read/Write access +- changed function NewDocument() and OpenDocument(), initialize + flash memory structure + +I28F160.C +- new modul with I28F160 flash memory implementation + +I28F160.H +- header file for flash memory implementation + +MOPS.C +- changed function MapROM(), added flash register mode +- changed function Npeek(), added flash access part +- changed function Nread(), completed flash access part +- changed function Nwrite(), completed flash access part + +OPS.H +- bugfix in function FASTPTR(), handle NULL pointer pages now +- bugfix in function Ninc(), Ndec(), Nadd(), Nsub(), Nrsub(), Nnot() + and Nneg(), wrong results in dec mode with illegal decimal number + entry + +SETTINGS.C +- changed function ReadSettings() and WriteSettings(), added section + [ROM] in INI-File + +TIMER.C +- bugfix in function CalcT2(), workaround to minimize skipping + timer2 values + + +Service Pack 16 for Emu48 Version 1.0 + +DEBUGGER.C +- added "MMU" and "Miscellaneous" view in debugger dialog +- added RPL exit breakpoint on A=DAT0 A, D0=D0+ 5, PC=(A) sequence +- added last instruction viewer in debugger +- renamed funtion name ViewStackWnd() to UpdateStackWnd() +- bugfix in function UpdateRegisterWnd() and OnCtlColorStatic(), + fixed problems of register update in RUN mode +- bugfix in function OnKeyF11(), in sleep mode break hasn't worked +- bugfix in function OnLButtonUp(), register setting wasn't disabled + in running state +- changed function NotifyDebugger(), added argument to detect RPL + breakpoint +- bugfix in function Debugger(), code disassembler wasn't reset to + mapped mode at startup and fixed a resource leak +- bugfix in function EditBreakpoint(), added breakpoint buffer + overflow check at add breakpoint command + +DEBUGGER.H +- extern declaration of global variables + +DISASM.C +- bugfix in function disasm_1(), the C=ss, ss=C and CssEX opcodes + showed always as A=ss, ss=A and AssEX opcodes +- bugfix in function disasm_8(), the r register in the rSRB.F fs + opcodes and the opcode lenght was wrong + +EMU48.C +- new function CopyItemsToClipboard(), copy selected items from + listbox to clipboard, extracted from function Disasm() +- added HP38G64K stuff to function UpdateWindowStatus() and + SettingsProc() +- changed function Disasm(), call function CopyItemsToClipboard() + now + +EMU48.H +- extern declaration of global functions + +EMU48.RC +- added "MMU" and "Miscellaneous" part in debugger dialog +- changed copyright date in IDD_ABOUT +- changed version and copyright + +ENGINE.C +- added RPL breakpoint handling +- added last instruction buffer update handling +- changed function WorkerThread(), added timer emulation in debugger + mode +- bugfix infunction WorkerThread(), in debugger mode Chipset.pc may + have been changed, so the FASTPTR access must be updated as well + +EXTERNAL.C +- some minor changes for the HP38G64K beeper emulation + +FILES.C +- added new document type for HP38G64K + +IO.H +- added CARDCTL and LCR definitions + +KEYBOARD.C +- bugfix in function ScanKeyboard(), update keyboard status only on + timer running and keyboard interrupt only occur on the rising edge + of "logic or" of IR[8:0] and not on the rising edge of each line; + the IRX15 interrupt (ON key) is level sensitive + +MOPS.C +- added function MapData() to detect the memory controller handle + the given address, needed for function Npeek(), Nread() and + Nwrite() +- bugfix in function MapROM(), no difference between Clarke and + Yorke chip in DA19 bit behavior, depends on ROM size +- bugfix in function Map(), on a HP49 NCE3 may mapped to ROM +- changed function CpuReset(), reset WSM state of flash memory +- bugfix in function Npeek(), update card status register in I/O + register area before reading and simulate an open data bus +- bugfix in function Nread(), loaded wrong bank switcher FF value + for a HP49 +- bugfix in function Nwrite(), loaded wrong bank switcher FF value + when writing on slot2 enabled (GX only) +- changed function Nread() and Nwrite(), added Flash memory access + detection part +- bugfix in function ReadIO(), the CARDSTATUS register (0x10F) + return zero when card detection is disabled +- bugfix in function WriteIO(), on a HP49 force new memory mapping + on changing the LED bit in the LCR register (0x11C) + +RESOURCE.H +- added several definitions + +TYPES.H +- conditional compiling of cycle counter (32bit EXE/64bit DLL) +- use position of Port2_NBanks for flash memory WSM state variable + + Service Pack 15 for Emu48 Version 1.0 DEBUGGER.C @@ -24,7 +374,7 @@ EMU48.C - replaced __argc and __argv variables by process variables - replaced INI file handling part - changed address in GPL license string -- added HP38 stuff to function UpdateWindowStatus() and +- added HP38 stuff to function UpdateWindowStatus() and SettingsProc() - changed function SettingsProc(), replaced Port1_Writeable variable - bugfix in function OnViewReset(), some registers wasn't reset, @@ -112,7 +462,7 @@ OPCODES.C - bugfix in function o08(), o09(), o0A(), o0B(), o3X(), o8086n(), o8087n(), o808An(), o808Bn() and o8Ed4(), changed number of cpu cycles - + RESOURCE.H - added several definitions @@ -200,7 +550,7 @@ EXTERNAL.C - some minor changes for the HP49 beeper emulation FILES.C -- added new document type for HP49G +- added new document type for HP49 - change in function WriteStack(), accept HP49 binary files now - the port2 open/close handling is now controlled by the document - several changes in port2 handling, the HP49 use an internal 128KB @@ -400,7 +750,7 @@ EMU48.C - bugfix in functions OnFileExit() and OnSysClose(), stop emulation thread before saving emulation data - changed function Disasm(), changed list box from single to multi - selectable items, added button to copy selected items to + selectable items, added button to copy selected items to clipboard - moved function DragAcceptFiles() to WM_CREATE handler - added function DragAcceptFiles() to WM_DESTROY handler @@ -533,14 +883,14 @@ MOPS.C - bugfix in function ReadIO(), the TIMER1CTRL and TIMER2CTRL register (0x12E,0x12F) must be updated before reading - bugfix in function WriteIO(), don't clear the XTRA bit in the - TIMER1CTRL register (0x12E) while setting + TIMER1CTRL register (0x12E) while setting - bugfix in function WriteIO(), after setting new TIMER1CTRL and TIMER2CTRL register (0x12E,0x12F) values the control bit condition must be checked - bugfix in function WriteIO(), handle start/stop of the LINECOUNT register when the DON bit in the DISPIO (0x100) register has changed -- bugfix in function WriteIO(), force new ROM mapping if DA19 in +- bugfix in function WriteIO(), force new ROM mapping if DA19 in the MSB LINECOUNT (0x129) register has changed on a G(X) ROM OPCODES.H @@ -876,7 +1226,7 @@ MOPS.C OPCODES.H - bugfix in INTON and INTOFF command, they aren't able to generate a keyboard interrupt -- bugfix in RSI command, call interrupt routine or set interrupt +- bugfix in RSI command, call interrupt routine or set interrupt pending flag if a bit of the IN register is high - RTI command changed, doesn't handle ON key here - added InfoMessage for BUSCB command @@ -1011,7 +1361,7 @@ FILES.C NT (NT GDI is 32 bit coded, Windows 95 mostly 16 bit -> the failed conversion doesn't matter with Windows 95) - bugfix in close file handling of function OpenDocument() -- InitKML() need chipset for contrast setting in RestoreBackup() +- InitKML() need chipset for contrast setting in RestoreBackup() KML.C - added global variables diff --git a/sources/Emu48/DEBUGGER.C b/sources/Emu48/DEBUGGER.C index c33a16d..346a964 100644 --- a/sources/Emu48/DEBUGGER.C +++ b/sources/Emu48/DEBUGGER.C @@ -19,6 +19,15 @@ #define MAXMEMITEMS 16 // number of address items in a memory window line #define MAXBREAKPOINTS 256 // max. number of breakpoints +#define REG_START IDC_REG_A // first register in register update table +#define REG_STOP IDC_MISC_BS // last register in register update table +#define REG_SIZE (REG_STOP-REG_START+1) // size of register update table + +// assert for register update +#define _ASSERTREG(r) _ASSERT(r >= REG_START && r <= REG_STOP) + +#define INSTRSIZE 256 // size of last instruction buffer + #define WM_UPDATE (WM_USER+0x1000) // update debugger dialog box #define MEMWNDMAX (sizeof(nCol) / sizeof(nCol[0])) @@ -31,8 +40,14 @@ static CONST int nCol[] = static CONST char cHex[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'}; +typedef struct // type of breakpoint table +{ + UINT nType; // breakpoint type + DWORD dwAddr; // breakpoint address +} BP_T; + static WORD wBreakpointCount = 0; // number of breakpoints -static DWORD dwBreakpoint[MAXBREAKPOINTS]; // breakpoint table +static BP_T sBreakpoint[MAXBREAKPOINTS]; // breakpoint table static DWORD dwAdrLine[MAXCODELINES]; // addresses of disassember lines in code window static DWORD dwAdrMem = 0; // start address of memory window @@ -41,28 +56,34 @@ static LONG lCharWidth; // width of a character (is a fix font) static HMENU hMenuCode,hMenuMem; // handle of context menues -static CHIPSET OldChipset; // 10.11.99 cg, new, old chipset content +static CHIPSET OldChipset; // old chipset content +static BOOL bRegUpdate[REG_SIZE]; // register update table HWND hDlgDebug = NULL; // handle for debugger dialog HANDLE hEventDebug; // event handle to stop cpu thread BOOL bDbgEnable = FALSE; // debugger mode enable flag -INT nDbgState = DBG_RUN; // 13.11.99 cg, new, state of debugger +INT nDbgState = DBG_RUN; // state of debugger -//BOOL bDbgSingle = FALSE; // 13.11.99 cg, removed -//BOOL bDbgOver = FALSE; // 13.11.99 cg, removed +BOOL bDbgNOP3 = FALSE; // halt on NOP3 (#420 opcode) +BOOL bDbgRPL = FALSE; // halt on RPL exit -BOOL bDbgNOP3 = FALSE; // halt on NOP3 (#420 opcode) +BOOL bDbgSkipInt = FALSE; // execute interrupt handler DWORD dwDbgRstkp; // stack recursion level of step over end -DWORD dwDbgRstk; // 13.11.99 cg, new, possible return address +DWORD dwDbgRstk; // possible return address + +DWORD *pdwInstrArray = NULL; // last instruction array +WORD wInstrSize = INSTRSIZE; // size of last instruction array +WORD wInstrWp; // write pointer of instruction array +WORD wInstrRp; // read pointer of instruction array // function prototypes static VOID OnNewValue(LPSTR lpszValue); static VOID OnEnterAddress(HWND hDlg, DWORD *dwValue); static BOOL OnEditBreakpoint(HWND hDlg); - +static BOOL OnInfoIntr(HWND hDlg); //################ //# @@ -79,11 +100,12 @@ static __inline VOID ToggleBreakpoint(DWORD dwAddr) for (i = 0; i < wBreakpointCount; ++i) // scan all breakpoints { - if (dwBreakpoint[i] == dwAddr) // breakpoint found + // code breakpoint found + if (sBreakpoint[i].nType == BP_EXEC && sBreakpoint[i].dwAddr == dwAddr) { // purge breakpoint for (++i; i < wBreakpointCount; ++i) - dwBreakpoint[i-1] = dwBreakpoint[i]; + sBreakpoint[i-1] = sBreakpoint[i]; --wBreakpointCount; return; } @@ -96,11 +118,26 @@ static __inline VOID ToggleBreakpoint(DWORD dwAddr) return; } - dwBreakpoint[wBreakpointCount] = dwAddr; + sBreakpoint[wBreakpointCount].nType = BP_EXEC; + sBreakpoint[wBreakpointCount].dwAddr = dwAddr; ++wBreakpointCount; return; } +// +// init bank switcher area +// +static __inline VOID InitBsArea(HWND hDlg) +{ + // HP48GX, HP49G + if (cCurrentRomType=='G' || cCurrentRomType=='X') + { + EnableWindow(GetDlgItem(hDlg,IDC_MISC_BS_TXT),TRUE); + EnableWindow(GetDlgItem(hDlg,IDC_MISC_BS),TRUE); + } + return; +} + // // convert nibble register to string // @@ -133,7 +170,8 @@ static VOID StrToReg(BYTE *pReg, WORD wNib, LPSTR lpszValue) } else { - pReg[i] = *lpszValue - '0'; // convert to number + // convert to number + pReg[i] = toupper(*lpszValue) - '0'; if (pReg[i] > 9) pReg[i] -= 7; ++lpszValue; } @@ -149,6 +187,7 @@ static VOID ViewCodeWnd(HWND hWnd, DWORD dwAddress) INT i,j; char szAddress[64]; + _ASSERT(disassembler_map == MEM_MAP); // disassemble in mapped mode SendMessage(hWnd,WM_SETREDRAW,FALSE,0); SendMessage(hWnd,LB_RESETCONTENT,0,0); for (i = 0; i < MAXCODELINES; ++i) @@ -186,7 +225,7 @@ static VOID ViewMemWnd(HWND hDlg, DWORD dwAddress) { BYTE byLineData[MAXMEMITEMS]; - // 12.11.99 cg, changed, fetch data line + // fetch data line Npeek(byLineData, dwAddress, MAXMEMITEMS); wsprintf(szBuffer,"%05lX",dwAddress); @@ -194,7 +233,7 @@ static VOID ViewMemWnd(HWND hDlg, DWORD dwAddress) for (k = 0, j = 0; j < MAXMEMITEMS; ++j) { - // 12.11.99 cg, changed, read from fetched data line + // read from fetched data line szItem[j&0x1] = cHex[byLineData[j]]; cChar = (cChar << 4) | byLineData[j]; @@ -216,26 +255,6 @@ static VOID ViewMemWnd(HWND hDlg, DWORD dwAddress) #undef TEXTOFF } -// -// write stack window -// -static VOID ViewStackWnd(HWND hDlg) -{ - INT i; - char szBuffer[64]; - - HWND hWnd = GetDlgItem(hDlg,IDC_DEBUG_STACK); - - SendMessage(hWnd,WM_SETREDRAW,FALSE,0); - SendMessage(hWnd,LB_RESETCONTENT,0,0); - for (i = 1; i <= sizeof(Chipset.rstk) / sizeof(Chipset.rstk[0]); ++i) - { - wsprintf(szBuffer,"%d: %05X", i, Chipset.rstk[(Chipset.rstkp-i)&7]); - SendMessage(hWnd,LB_ADDSTRING,0,(LPARAM) szBuffer); - } - SendMessage(hWnd,WM_SETREDRAW,TRUE,0); - return; -} //################ //# @@ -288,49 +307,93 @@ static VOID UpdateCodeWnd(HWND hDlg) static VOID UpdateRegisterWnd(HWND hDlg) { char szBuffer[64]; - + + _ASSERTREG(IDC_REG_A); + bRegUpdate[IDC_REG_A-REG_START] = memcmp(Chipset.A, OldChipset.A, sizeof(Chipset.A)) != 0; wsprintf(szBuffer,"A= %s",RegToStr(Chipset.A,16)); SetDlgItemText(hDlg,IDC_REG_A,szBuffer); + _ASSERTREG(IDC_REG_B); + bRegUpdate[IDC_REG_B-REG_START] = memcmp(Chipset.B, OldChipset.B, sizeof(Chipset.B)) != 0; wsprintf(szBuffer,"B= %s",RegToStr(Chipset.B,16)); SetDlgItemText(hDlg,IDC_REG_B,szBuffer); + _ASSERTREG(IDC_REG_C); + bRegUpdate[IDC_REG_C-REG_START] = memcmp(Chipset.C, OldChipset.C, sizeof(Chipset.C)) != 0; wsprintf(szBuffer,"C= %s",RegToStr(Chipset.C,16)); SetDlgItemText(hDlg,IDC_REG_C,szBuffer); + _ASSERTREG(IDC_REG_D); + bRegUpdate[IDC_REG_D-REG_START] = memcmp(Chipset.D, OldChipset.D, sizeof(Chipset.D)) != 0; wsprintf(szBuffer,"D= %s",RegToStr(Chipset.D,16)); SetDlgItemText(hDlg,IDC_REG_D,szBuffer); + _ASSERTREG(IDC_REG_R0); + bRegUpdate[IDC_REG_R0-REG_START] = memcmp(Chipset.R0, OldChipset.R0, sizeof(Chipset.R0)) != 0; wsprintf(szBuffer,"R0=%s",RegToStr(Chipset.R0,16)); SetDlgItemText(hDlg,IDC_REG_R0,szBuffer); + _ASSERTREG(IDC_REG_R1); + bRegUpdate[IDC_REG_R1-REG_START] = memcmp(Chipset.R1, OldChipset.R1, sizeof(Chipset.R1)) != 0; wsprintf(szBuffer,"R1=%s",RegToStr(Chipset.R1,16)); SetDlgItemText(hDlg,IDC_REG_R1,szBuffer); + _ASSERTREG(IDC_REG_R2); + bRegUpdate[IDC_REG_R2-REG_START] = memcmp(Chipset.R2, OldChipset.R2, sizeof(Chipset.R2)) != 0; wsprintf(szBuffer,"R2=%s",RegToStr(Chipset.R2,16)); SetDlgItemText(hDlg,IDC_REG_R2,szBuffer); + _ASSERTREG(IDC_REG_R3); + bRegUpdate[IDC_REG_R3-REG_START] = memcmp(Chipset.R3, OldChipset.R3, sizeof(Chipset.R3)) != 0; wsprintf(szBuffer,"R3=%s",RegToStr(Chipset.R3,16)); SetDlgItemText(hDlg,IDC_REG_R3,szBuffer); + _ASSERTREG(IDC_REG_R4); + bRegUpdate[IDC_REG_R4-REG_START] = memcmp(Chipset.R4, OldChipset.R4, sizeof(Chipset.R4)) != 0; wsprintf(szBuffer,"R4=%s",RegToStr(Chipset.R4,16)); SetDlgItemText(hDlg,IDC_REG_R4,szBuffer); + _ASSERTREG(IDC_REG_D0); + bRegUpdate[IDC_REG_D0-REG_START] = Chipset.d0 != OldChipset.d0; wsprintf(szBuffer,"D0=%05X",Chipset.d0); SetDlgItemText(hDlg,IDC_REG_D0,szBuffer); + _ASSERTREG(IDC_REG_D1); + bRegUpdate[IDC_REG_D1-REG_START] = Chipset.d1 != OldChipset.d1; wsprintf(szBuffer,"D1=%05X",Chipset.d1); SetDlgItemText(hDlg,IDC_REG_D1,szBuffer); + _ASSERTREG(IDC_REG_P); + bRegUpdate[IDC_REG_P-REG_START] = Chipset.P != OldChipset.P; wsprintf(szBuffer,"P=%X",Chipset.P); SetDlgItemText(hDlg,IDC_REG_P,szBuffer); + _ASSERTREG(IDC_REG_PC); + bRegUpdate[IDC_REG_PC-REG_START] = Chipset.pc != OldChipset.pc; wsprintf(szBuffer,"PC=%05X",Chipset.pc); SetDlgItemText(hDlg,IDC_REG_PC,szBuffer); + _ASSERTREG(IDC_REG_OUT); + bRegUpdate[IDC_REG_OUT-REG_START] = Chipset.out != OldChipset.out; wsprintf(szBuffer,"OUT=%03X",Chipset.out); SetDlgItemText(hDlg,IDC_REG_OUT,szBuffer); + _ASSERTREG(IDC_REG_IN); + bRegUpdate[IDC_REG_IN-REG_START] = Chipset.in != OldChipset.in; wsprintf(szBuffer,"IN=%04X",Chipset.in); SetDlgItemText(hDlg,IDC_REG_IN,szBuffer); + _ASSERTREG(IDC_REG_ST); + bRegUpdate[IDC_REG_ST-REG_START] = memcmp(Chipset.ST, OldChipset.ST, sizeof(Chipset.ST)) != 0; wsprintf(szBuffer,"ST=%s",RegToStr(Chipset.ST,4)); SetDlgItemText(hDlg,IDC_REG_ST,szBuffer); + _ASSERTREG(IDC_REG_CY); + bRegUpdate[IDC_REG_CY-REG_START] = Chipset.carry != OldChipset.carry; wsprintf(szBuffer,"CY=%d",Chipset.carry); SetDlgItemText(hDlg,IDC_REG_CY,szBuffer); + _ASSERTREG(IDC_REG_MODE); + bRegUpdate[IDC_REG_MODE-REG_START] = Chipset.mode_dec != OldChipset.mode_dec; wsprintf(szBuffer,"Mode=%c",Chipset.mode_dec ? 'D' : 'H'); SetDlgItemText(hDlg,IDC_REG_MODE,szBuffer); + _ASSERTREG(IDC_REG_MP); + bRegUpdate[IDC_REG_MP-REG_START] = ((Chipset.HST ^ OldChipset.HST) & MP) != 0; wsprintf(szBuffer,"MP=%d",(Chipset.HST & MP) != 0); SetDlgItemText(hDlg,IDC_REG_MP,szBuffer); + _ASSERTREG(IDC_REG_SR); + bRegUpdate[IDC_REG_SR-REG_START] = ((Chipset.HST ^ OldChipset.HST) & SR) != 0; wsprintf(szBuffer,"SR=%d",(Chipset.HST & SR) != 0); SetDlgItemText(hDlg,IDC_REG_SR,szBuffer); + _ASSERTREG(IDC_REG_SB); + bRegUpdate[IDC_REG_SB-REG_START] = ((Chipset.HST ^ OldChipset.HST) & SB) != 0; wsprintf(szBuffer,"SB=%d",(Chipset.HST & SB) != 0); SetDlgItemText(hDlg,IDC_REG_SB,szBuffer); + _ASSERTREG(IDC_REG_XM); + bRegUpdate[IDC_REG_XM-REG_START] = ((Chipset.HST ^ OldChipset.HST) & XM) != 0; wsprintf(szBuffer,"XM=%d",(Chipset.HST & XM) != 0); SetDlgItemText(hDlg,IDC_REG_XM,szBuffer); return; @@ -345,26 +408,135 @@ static VOID UpdateMemoryWnd(HWND hDlg) return; } +// +// update stack window +// +static VOID UpdateStackWnd(HWND hDlg) +{ + INT i; + LONG nPos; + char szBuffer[64]; + + HWND hWnd = GetDlgItem(hDlg,IDC_DEBUG_STACK); + + SendMessage(hWnd,WM_SETREDRAW,FALSE,0); + nPos = SendMessage(hWnd,LB_GETTOPINDEX,0,0); + SendMessage(hWnd,LB_RESETCONTENT,0,0); + for (i = 1; i <= sizeof(Chipset.rstk) / sizeof(Chipset.rstk[0]); ++i) + { + wsprintf(szBuffer,"%d: %05X", i, Chipset.rstk[(Chipset.rstkp-i)&7]); + SendMessage(hWnd,LB_ADDSTRING,0,(LPARAM) szBuffer); + } + SendMessage(hWnd,LB_SETTOPINDEX,nPos,0); + SendMessage(hWnd,WM_SETREDRAW,TRUE,0); + return; +} + +// +// update MMU window +// +static VOID UpdateMmuWnd(HWND hDlg) +{ + char szBuffer[64]; + + if (Chipset.IOCfig) + wsprintf(szBuffer,"%05X",Chipset.IOBase); + else + strcpy(szBuffer,"-----"); + SetDlgItemText(hDlg,IDC_MMU_IO_A,szBuffer); + if (Chipset.P0Cfig) + wsprintf(szBuffer,"%05X",Chipset.P0Base<<12); + else + strcpy(szBuffer,"-----"); + SetDlgItemText(hDlg,IDC_MMU_NCE2_A,szBuffer); + if (Chipset.P0Cfg2) + wsprintf(szBuffer,"%05X",(Chipset.P0Size^0xFF)<<12); + else + strcpy(szBuffer,"-----"); + SetDlgItemText(hDlg,IDC_MMU_NCE2_S,szBuffer); + if (Chipset.P1Cfig) + wsprintf(szBuffer,"%05X",Chipset.P1Base<<12); + else + strcpy(szBuffer,"-----"); + SetDlgItemText(hDlg,(cCurrentRomType=='S') ? IDC_MMU_CE1_A : IDC_MMU_CE2_A,szBuffer); + if (Chipset.P1Cfg2) + wsprintf(szBuffer,"%05X",(Chipset.P1Size^0xFF)<<12); + else + strcpy(szBuffer,"-----"); + SetDlgItemText(hDlg,(cCurrentRomType=='S') ? IDC_MMU_CE1_S : IDC_MMU_CE2_S,szBuffer); + if (Chipset.P2Cfig) + wsprintf(szBuffer,"%05X",Chipset.P2Base<<12); + else + strcpy(szBuffer,"-----"); + SetDlgItemText(hDlg,(cCurrentRomType=='S') ? IDC_MMU_CE2_A : IDC_MMU_NCE3_A,szBuffer); + if (Chipset.P2Cfg2) + wsprintf(szBuffer,"%05X",(Chipset.P2Size^0xFF)<<12); + else + strcpy(szBuffer,"-----"); + SetDlgItemText(hDlg,(cCurrentRomType=='S') ? IDC_MMU_CE2_S : IDC_MMU_NCE3_S,szBuffer); + if (Chipset.BSCfig) + wsprintf(szBuffer,"%05X",Chipset.BSBase<<12); + else + strcpy(szBuffer,"-----"); + SetDlgItemText(hDlg,(cCurrentRomType=='S') ? IDC_MMU_NCE3_A : IDC_MMU_CE1_A,szBuffer); + if (Chipset.BSCfg2) + wsprintf(szBuffer,"%05X",(Chipset.BSSize^0xFF)<<12); + else + strcpy(szBuffer,"-----"); + SetDlgItemText(hDlg,(cCurrentRomType=='S') ? IDC_MMU_NCE3_S : IDC_MMU_CE1_S,szBuffer); + return; +} + +// +// update miscellaneous window +// +static VOID UpdateMiscWnd(HWND hDlg) +{ + _ASSERTREG(IDC_MISC_INT); + bRegUpdate[IDC_MISC_INT-REG_START] = Chipset.inte != OldChipset.inte; + SetDlgItemText(hDlg,IDC_MISC_INT,Chipset.inte ? "On " : "Off"); + + _ASSERTREG(IDC_MISC_KEY); + bRegUpdate[IDC_MISC_KEY-REG_START] = Chipset.intk != OldChipset.intk; + SetDlgItemText(hDlg,IDC_MISC_KEY,Chipset.intk ? "On " : "Off"); + + _ASSERTREG(IDC_MISC_BS); + bRegUpdate[IDC_MISC_BS-REG_START] = FALSE; + + // HP48GX, HP49G + if (cCurrentRomType=='G' || cCurrentRomType=='X') + { + char szBuffer[64]; + + bRegUpdate[IDC_MISC_BS-REG_START] = (Chipset.Bank_FF & 0x7F) != (OldChipset.Bank_FF & 0x7F); + wsprintf(szBuffer,"%02X",Chipset.Bank_FF & 0x7F); + SetDlgItemText(hDlg,IDC_MISC_BS,szBuffer); + } + return; +} + // // update complete debugger dialog // VOID OnUpdate(HWND hDlg) { - nDbgState = DBG_STEPINTO; // 13.11.99 cg, changed, state "step into" + nDbgState = DBG_STEPINTO; // state "step into" // enable debug buttons EnableMenuItem(GetMenu(hDlg),ID_DEBUG_RUN,MF_ENABLED); EnableMenuItem(GetMenu(hDlg),ID_DEBUG_STEP,MF_ENABLED); EnableMenuItem(GetMenu(hDlg),ID_DEBUG_STEPOVER,MF_ENABLED); - // 12.11.99 cg, new EnableMenuItem(GetMenu(hDlg),ID_DEBUG_STEPOUT,MF_ENABLED); + EnableMenuItem(GetMenu(hDlg),ID_INFO_LASTINSTRUCTIONS,MF_ENABLED); // update windows UpdateCodeWnd(hDlg); // update code window UpdateRegisterWnd(hDlg); // update registers window UpdateMemoryWnd(hDlg); // update memory window - ViewStackWnd(hDlg); // update stack window - ShowWindow(hDlg,SW_RESTORE); // 15.11.99 cg, moved, pop up if minimized + UpdateStackWnd(hDlg); // update stack window + UpdateMmuWnd(hDlg); // update MMU window + UpdateMiscWnd(hDlg); // update bank switcher window + ShowWindow(hDlg,SW_RESTORE); // pop up if minimized SetFocus(hDlg); // set focus to debugger return; } @@ -403,7 +575,7 @@ static BOOL OnKeyF5(HWND hDlg) INT i,nPos; char szBuf[64]; - if (nDbgState != DBG_RUN) // 13.11.99 cg, changed, emulation stopped + if (nDbgState != DBG_RUN) // emulation stopped { hWnd = GetDlgItem(hDlg,IDC_DEBUG_CODE); nPos = SendMessage(hWnd,LB_GETCURSEL,0,0); @@ -426,11 +598,11 @@ static BOOL OnKeyF5(HWND hDlg) EnableMenuItem(GetMenu(hDlg),ID_DEBUG_RUN,MF_GRAYED); EnableMenuItem(GetMenu(hDlg),ID_DEBUG_STEP,MF_GRAYED); EnableMenuItem(GetMenu(hDlg),ID_DEBUG_STEPOVER,MF_GRAYED); - // 12.11.99 cg, new EnableMenuItem(GetMenu(hDlg),ID_DEBUG_STEPOUT,MF_GRAYED); + EnableMenuItem(GetMenu(hDlg),ID_INFO_LASTINSTRUCTIONS,MF_GRAYED); - nDbgState = DBG_RUN; // 13.11.99 cg, changed, state "run" - OldChipset = Chipset; // 10.11.99 cg, new, save chipset values + nDbgState = DBG_RUN; // state "run" + OldChipset = Chipset; // save chipset values SetEvent(hEventDebug); // run emulation } return -1; // call windows default handler @@ -442,10 +614,10 @@ static BOOL OnKeyF5(HWND hDlg) // static BOOL OnKeyF7(HWND hDlg) { - if (nDbgState != DBG_RUN) // 13.11.99 cg, changed, emulation stopped + if (nDbgState != DBG_RUN) // emulation stopped { - nDbgState = DBG_STEPINTO; // 13.11.99 cg, changed, state "step into" - OldChipset = Chipset; // 10.11.99 cg, new, save chipset values + nDbgState = DBG_STEPINTO; // state "step into" + OldChipset = Chipset; // save chipset values SetEvent(hEventDebug); // run emulation } return -1; // call windows default handler @@ -457,29 +629,29 @@ static BOOL OnKeyF7(HWND hDlg) // static BOOL OnKeyF8(HWND hDlg) { - if (nDbgState != DBG_RUN) // 13.11.99 cg, changed, emulation stopped + if (nDbgState != DBG_RUN) // emulation stopped { LPBYTE I = FASTPTR(Chipset.pc); dwDbgRstkp = Chipset.rstkp; // save stack level - nDbgState = DBG_STEPINTO; // 13.11.99 cg, changed, state "step into" + nDbgState = DBG_STEPINTO; // state "step into" if (I[0] == 0x7) // GOSUB 7aaa { - nDbgState = DBG_STEPOVER; // 13.11.99 cg, changed, state "step over" + nDbgState = DBG_STEPOVER; // state "step over" } if (I[0] == 0x8) // GOSUBL or GOSBVL { if (I[1] == 0xE) // GOSUBL 8Eaaaa { - nDbgState = DBG_STEPOVER; // 13.11.99 cg, changed, state "step over" + nDbgState = DBG_STEPOVER; // state "step over" } if (I[1] == 0xF) // GOSBVL 8Eaaaaa { - nDbgState = DBG_STEPOVER; // 13.11.99 cg, changed, state "step over" + nDbgState = DBG_STEPOVER; // state "step over" } } - OldChipset = Chipset; // 10.11.99 cg, new, save chipset values + OldChipset = Chipset; // save chipset values SetEvent(hEventDebug); // run emulation } return -1; // call windows default handler @@ -489,7 +661,6 @@ static BOOL OnKeyF8(HWND hDlg) // // step out key handler (F9) // -// 13.11.99 cg, new static BOOL OnKeyF9(HWND hDlg) { if (nDbgState != DBG_RUN) // emulation stopped @@ -499,10 +670,11 @@ static BOOL OnKeyF9(HWND hDlg) EnableMenuItem(GetMenu(hDlg),ID_DEBUG_STEP,MF_GRAYED); EnableMenuItem(GetMenu(hDlg),ID_DEBUG_STEPOVER,MF_GRAYED); EnableMenuItem(GetMenu(hDlg),ID_DEBUG_STEPOUT,MF_GRAYED); + EnableMenuItem(GetMenu(hDlg),ID_INFO_LASTINSTRUCTIONS,MF_GRAYED); dwDbgRstkp = (Chipset.rstkp-1)&7; // save stack data dwDbgRstk = Chipset.rstk[dwDbgRstkp]; - nDbgState = DBG_STEPOUT; // 13.11.99 cg, changed, state "step out" + nDbgState = DBG_STEPOUT; // state "step out" OldChipset = Chipset; // save chipset values SetEvent(hEventDebug); // run emulation } @@ -515,7 +687,9 @@ static BOOL OnKeyF9(HWND hDlg) // static BOOL OnKeyF11(HWND hDlg) { - nDbgState = DBG_STEPINTO; // 13.11.99 cg, changed, state "step into" + nDbgState = DBG_STEPINTO; // state "step into" + if (Chipset.Shutdn) // cpu thread stopped + SetEvent(hEventShutdn); // goto debug session return -1; // call windows default handler UNREFERENCED_PARAMETER(hDlg); } @@ -584,10 +758,10 @@ static BOOL OnMemGoAdr(HWND hDlg) // // clear all breakpoints // -static BOOL OnClearAll(HWND hDlg) // 13.12.99 cg, changed argument +static BOOL OnClearAll(HWND hDlg) { wBreakpointCount = 0; - // 13.12.99 cg, changed, redraw code window + // redraw code window InvalidateRect(GetDlgItem(hDlg,IDC_DEBUG_CODE),NULL,TRUE); return TRUE; } @@ -602,6 +776,26 @@ static BOOL OnNOP3Break(HWND hDlg) return TRUE; } +// +// toggle RPL breakpoint +// +static BOOL OnRplBreak(HWND hDlg) +{ + bDbgRPL = !bDbgRPL; // toggle RPL debug flag + CheckMenuItem(GetMenu(hDlg),ID_BREAKPOINTS_RPL,bDbgRPL ? MF_CHECKED : MF_UNCHECKED); + return TRUE; +} + +// +// toggle Step Over Interrupts +// +static OnInterruptsStepOverInt(HWND hDlg) +{ + bDbgSkipInt = !bDbgSkipInt; // toggle interrupt handler skip flag + CheckMenuItem(GetMenu(hDlg),ID_INTR_STEPOVERINT,bDbgSkipInt ? MF_CHECKED : MF_UNCHECKED); + return TRUE; +} + // // new register setting // @@ -614,6 +808,9 @@ static BOOL OnLButtonUp(HWND hDlg, LPARAM lParam) #define BUFLEN (sizeof(szBuffer) / sizeof(szBuffer[0])) + if (nDbgState != DBG_STEPINTO) // not in single step mode + return TRUE; + POINTSTOPOINT(pt,MAKEPOINTS(lParam)); hWnd = ChildWindowFromPoint(hDlg,pt); // handle of selected window nId = GetDlgCtrlID(hWnd); // control ID of window @@ -667,7 +864,7 @@ static BOOL OnLButtonUp(HWND hDlg, LPARAM lParam) break; case IDC_REG_P: // P OnNewValue(&szBuffer[2]); - Chipset.P = szBuffer[2] - '0'; + Chipset.P = toupper(szBuffer[2]) - '0'; if (Chipset.P > 9) Chipset.P -= 7; break; case IDC_REG_PC: // PC @@ -705,6 +902,27 @@ static BOOL OnLButtonUp(HWND hDlg, LPARAM lParam) case IDC_REG_XM: // XM Chipset.HST ^= XM; break; + case IDC_MISC_INT: // interrupt status + Chipset.inte = !Chipset.inte; + UpdateMiscWnd(hDlg); // update miscellaneous window + break; + case IDC_MISC_KEY: // 1ms keyboard scan + Chipset.intk = !Chipset.intk; + UpdateMiscWnd(hDlg); // update miscellaneous window + break; + case IDC_MISC_BS: // Bank switcher setting + if (IsWindowEnabled(hWnd)) // BS text enabled + { + OnNewValue(szBuffer); + sscanf(szBuffer,"%2X",&Chipset.Bank_FF); + Chipset.Bank_FF &= 0x7F; + RomSwitch(Chipset.Bank_FF); // update memory mapping + + UpdateCodeWnd(hDlg); // update code window + UpdateMemoryWnd(hDlg); // update memory window + UpdateMiscWnd(hDlg); // update miscellaneous window + } + break; } UpdateRegisterWnd(hDlg); // update register return TRUE; @@ -736,7 +954,7 @@ static BOOL OnDblClick(HWND hWnd, WORD wId) SendMessage(hWnd,LB_GETTEXT,i,(LPARAM) szBuffer); OnNewValue(szBuffer); sscanf(szBuffer,"%2X", &byData); - byData = (byData >> 4) | (byData << 4); // 12.11.99 cg, bugfix, change nibbles for writing + byData = (byData >> 4) | (byData << 4); // change nibbles for writing Write2(dwAddress, byData); // write data UpdateMemoryWnd(GetParent(hWnd)); // update memory window @@ -872,10 +1090,8 @@ static __inline BOOL OnKeyUpDown(HWND hWnd, WPARAM wParam) // // handle keys in code window // -// 13.12.99 cg, changed first argument static __inline BOOL OnKeyCodeWnd(HWND hDlg, WPARAM wParam) { - // 13.12.99 cg, new, get handle of code window (was argument) HWND hWnd = GetDlgItem(hDlg,IDC_DEBUG_CODE); WORD wKey = LOWORD(wParam); WORD wItem = HIWORD(wParam); @@ -913,7 +1129,8 @@ static __inline BOOL OnDrawCodeWnd(LPDRAWITEMSTRUCT lpdis) // get item text SendMessage(lpdis->hwndItem,LB_GETTEXT,lpdis->itemID,(LONG)(LPSTR)szBuf); - bBrk = CheckBreakpoint(dwAdrLine[lpdis->itemID]); // check breakpoint + // check for codebreakpoint + bBrk = CheckBreakpoint(dwAdrLine[lpdis->itemID],1,BP_EXEC); bPC = szBuf[5] == '-'; // check if line of program counter crTextColor = COLOR_WHITE; // standard text color @@ -968,57 +1185,14 @@ static __inline BOOL OnDrawCodeWnd(LPDRAWITEMSTRUCT lpdis) // // detect changed register // -// 10.11.99 cg, new static __inline BOOL OnCtlColorStatic(HWND hWnd) { - switch(GetDlgCtrlID(hWnd)) - { - case IDC_REG_A: // A - return memcmp(Chipset.A, OldChipset.A, sizeof(Chipset.A)); - case IDC_REG_B: // B - return memcmp(Chipset.B, OldChipset.B, sizeof(Chipset.B)); - case IDC_REG_C: // C - return memcmp(Chipset.C, OldChipset.C, sizeof(Chipset.C)); - case IDC_REG_D: // D - return memcmp(Chipset.D, OldChipset.D, sizeof(Chipset.D)); - case IDC_REG_R0: // R0 - return memcmp(Chipset.R0, OldChipset.R0, sizeof(Chipset.R0)); - case IDC_REG_R1: // R1 - return memcmp(Chipset.R1, OldChipset.R1, sizeof(Chipset.R1)); - case IDC_REG_R2: // R2 - return memcmp(Chipset.R2, OldChipset.R2, sizeof(Chipset.R2)); - case IDC_REG_R3: // R3 - return memcmp(Chipset.R3, OldChipset.R3, sizeof(Chipset.R3)); - case IDC_REG_R4: // R4 - return memcmp(Chipset.R4, OldChipset.R4, sizeof(Chipset.R4)); - case IDC_REG_D0: // D0 - return Chipset.d0 != OldChipset.d0; - case IDC_REG_D1: // D1 - return Chipset.d1 != OldChipset.d1; - case IDC_REG_P: // P - return Chipset.P != OldChipset.P; - case IDC_REG_PC: // PC - return Chipset.pc != OldChipset.pc; - case IDC_REG_OUT: // OUT - return Chipset.out != OldChipset.out; - case IDC_REG_IN: // IN - return Chipset.in != OldChipset.in; - case IDC_REG_ST: // ST - return memcmp(Chipset.ST, OldChipset.ST, sizeof(Chipset.ST)); - case IDC_REG_CY: // CY - return Chipset.carry != OldChipset.carry; - case IDC_REG_MODE: // MODE - return Chipset.mode_dec != OldChipset.mode_dec; - case IDC_REG_MP: // MP - return (Chipset.HST ^ OldChipset.HST) & MP; - case IDC_REG_SR: // SR - return (Chipset.HST ^ OldChipset.HST) & SR; - case IDC_REG_SB: // SB - return (Chipset.HST ^ OldChipset.HST) & SB; - case IDC_REG_XM: // XM - return (Chipset.HST ^ OldChipset.HST) & XM; - } - return FALSE; // not changed + BOOL bError = FALSE; // not changed + + int nId = GetDlgCtrlID(hWnd); + if (nId >= REG_START && nId <= REG_STOP) // in register area + bError = bRegUpdate[nId-REG_START]; // register changed? + return bError; } @@ -1029,28 +1203,32 @@ static __inline BOOL OnCtlColorStatic(HWND hWnd) //################ // -// check for code breakpoints +// check for breakpoints // -BOOL CheckBreakpoint(DWORD dwAddr) +BOOL CheckBreakpoint(DWORD dwAddr, DWORD dwRange, UINT nType) { INT i; for (i = 0; i < wBreakpointCount; ++i) // scan all breakpoints { - if (dwBreakpoint[i] == dwAddr) // breakpoint found + // check address range and type + if ( sBreakpoint[i].dwAddr >= dwAddr && sBreakpoint[i].dwAddr < dwAddr + dwRange + && (sBreakpoint[i].nType & nType) != 0) return TRUE; } return FALSE; + UNREFERENCED_PARAMETER(dwRange); } // // notify debugger that emulation stopped // -VOID NotifyDebugger(VOID) // 10.11.99 cg, new, update registers +VOID NotifyDebugger(BOOL bType) // update registers { _ASSERT(hDlgDebug); // debug dialog box open PostMessage(hDlgDebug,WM_UPDATE,0,0); return; + UNREFERENCED_PARAMETER(bType); } //################ @@ -1078,9 +1256,16 @@ static BOOL CALLBACK Debugger(HWND hDlg, UINT message, DWORD wParam, LONG lParam case WM_INITDIALOG: _ASSERT(hWnd); EnableMenuItem(GetMenu(hWnd),ID_TOOL_DEBUG,MF_GRAYED); - CheckMenuItem(GetMenu(hDlg),ID_BREAKPOINTS_NOP3,bDbgNOP3 ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(GetMenu(hDlg),ID_BREAKPOINTS_NOP3,bDbgNOP3 ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(GetMenu(hDlg),ID_BREAKPOINTS_RPL, bDbgRPL ? MF_CHECKED : MF_UNCHECKED); + CheckMenuItem(GetMenu(hDlg),ID_INTR_STEPOVERINT,bDbgSkipInt ? MF_CHECKED : MF_UNCHECKED); hDlgDebug = hDlg; // handle for debugger dialog - // 13.12.99 cg, changed, save handle of base menus + hEventDebug = CreateEvent(NULL,FALSE,FALSE,NULL); + if (hEventDebug == NULL) + { + AbortMessage("Event creation failed !"); + return TRUE; + } hMenuMainCode = LoadMenu(hApp,MAKEINTRESOURCE(IDR_DEBUG_CODE)); _ASSERT(hMenuMainCode); hMenuCode = GetSubMenu(hMenuMainCode, 0); @@ -1089,38 +1274,43 @@ static BOOL CALLBACK Debugger(HWND hDlg, UINT message, DWORD wParam, LONG lParam _ASSERT(hMenuMainMem); hMenuMem = GetSubMenu(hMenuMainMem, 0); _ASSERT(hMenuMem); - // 13.12.99 cg, end of changed part - hEventDebug = CreateEvent(NULL,FALSE,FALSE,NULL); - if (hEventDebug == NULL) - { - AbortMessage("Event creation failed !"); - return TRUE; - } // font settings SendDlgItemMessage(hDlg,IDC_STATIC_CODE, WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); SendDlgItemMessage(hDlg,IDC_STATIC_REGISTERS,WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); SendDlgItemMessage(hDlg,IDC_STATIC_MEMORY, WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); SendDlgItemMessage(hDlg,IDC_STATIC_STACK, WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); + SendDlgItemMessage(hDlg,IDC_STATIC_MMU, WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); + SendDlgItemMessage(hDlg,IDC_STATIC_MISC, WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); + + // init last instruction circular buffer + pdwInstrArray = LocalAlloc(LMEM_FIXED,wInstrSize*sizeof(*pdwInstrArray)); + wInstrWp = wInstrRp = 0; // write/read pointer + + InitBsArea(hDlg); // init bank switcher list box + + disassembler_map = MEM_MAP; // disassemble with mapped modules bDbgEnable = TRUE; // debugger active - nDbgState = DBG_STEPINTO; // 13.11.99 cg, changed, state "step into" + nDbgState = DBG_STEPINTO; // state "step into" if (Chipset.Shutdn) // cpu thread stopped SetEvent(hEventShutdn); // goto debug session - OldChipset = Chipset; // 10.11.99 cg, new, save chipset values + OldChipset = Chipset; // save chipset values return TRUE; case WM_DESTROY: // SetHP48Time(); // update HP48 time & date hDlgDebug = NULL; // debugger windows closed bDbgEnable = FALSE; // debugger inactive - nDbgState = DBG_RUN; // 13.11.99 cg, changed, state of debugger + nDbgState = DBG_RUN; // state of debugger bInterrupt = TRUE; // exit opcode loop SetEvent(hEventDebug); + // free last instruction circular buffer + if (pdwInstrArray) LocalFree(pdwInstrArray); CloseHandle(hEventDebug); - DestroyMenu(hMenuMainCode); // 13.12.99 cg, bugfix, used wrong handle - DestroyMenu(hMenuMainMem); // 13.12.99 cg, bugfix, used wrong handle + DestroyMenu(hMenuMainCode); + DestroyMenu(hMenuMainMem); _ASSERT(hWnd); EnableMenuItem(GetMenu(hWnd),ID_TOOL_DEBUG,MF_ENABLED); break; @@ -1161,9 +1351,11 @@ static BOOL CALLBACK Debugger(HWND hDlg, UINT message, DWORD wParam, LONG lParam case ID_DEBUG_CODE_GOPC: return OnCodeGoPC(hDlg); case ID_DEBUG_CODE_SETPCTOSELECT: return OnCodeSetPcToSelection(hDlg); case ID_BREAKPOINTS_CODEEDIT: return OnEditBreakpoint(hDlg); - // 13.12.99 cg, changed argument to dialog handle case ID_BREAKPOINTS_CLEARALL: return OnClearAll(hDlg); case ID_BREAKPOINTS_NOP3: return OnNOP3Break(hDlg); + case ID_BREAKPOINTS_RPL: return OnRplBreak(hDlg); + case ID_INFO_LASTINSTRUCTIONS: return OnInfoIntr(hDlg); + case ID_INTR_STEPOVERINT: return OnInterruptsStepOverInt(hDlg); case ID_DEBUG_MEM_GOADR: return OnMemGoAdr(hDlg); case ID_DEBUG_MEM_GOPC: return OnMemGoDx(hDlg, Chipset.pc); case ID_DEBUG_MEM_GOD0: return OnMemGoDx(hDlg, Chipset.d0); @@ -1187,7 +1379,6 @@ static BOOL CALLBACK Debugger(HWND hDlg, UINT message, DWORD wParam, LONG lParam { // handle code window case IDC_DEBUG_CODE: - // 13.12.99 cg, changed first argument to dialog handle return OnKeyCodeWnd(hDlg, wParam); // handle memory window @@ -1220,12 +1411,12 @@ static BOOL CALLBACK Debugger(HWND hDlg, UINT message, DWORD wParam, LONG lParam OnContextMenu(hDlg, lParam, wParam); break; - case WM_CTLCOLORSTATIC: // 10.11.99 cg, new, register color highlighting + case WM_CTLCOLORSTATIC: // register color highlighting // highlight text? if (OnCtlColorStatic((HWND) lParam)) { SetTextColor((HDC) wParam, COLOR_RED); - SetBkColor((HDC) wParam, GetSysColor(COLOR_INACTIVECAPTIONTEXT)); + SetBkColor((HDC) wParam, GetSysColor(CTLCOLOR_DLG)); return (BOOL) GetStockObject(NULL_BRUSH); // transparent brush } break; @@ -1258,7 +1449,7 @@ static BOOL CALLBACK Debugger(HWND hDlg, UINT message, DWORD wParam, LONG lParam return FALSE; } -LRESULT OnToolDebug() // 06.08.99 cg, new, debugger dialogbox call +LRESULT OnToolDebug() // debugger dialogbox call { if ((hDlgDebug = CreateDialog(hApp,MAKEINTRESOURCE(IDD_DEBUG),GetParent(hWnd), (DLGPROC)Debugger)) == NULL) @@ -1283,32 +1474,38 @@ static BOOL CALLBACK NewValue(HWND hDlg, UINT message, DWORD wParam, LONG lParam static LPSTR lpszBuffer; // handle of buffer static int nBufferlen; // length of buffer - LONG i; + char szBuffer[64]; + LONG i; + + HWND hWnd = GetDlgItem(hDlg,IDC_NEWVALUE); switch (message) { case WM_INITDIALOG: lpszBuffer = (LPSTR) lParam; nBufferlen = strlen(lpszBuffer)+1; // length with zero string terminator - SetDlgItemText(hDlg,IDC_NEWVALUE,lpszBuffer); + _ASSERT(sizeof(szBuffer) / sizeof(szBuffer[0]) >= nBufferlen); + SendMessage(hWnd,WM_SETTEXT,0,(LPARAM)lpszBuffer); return TRUE; case WM_COMMAND: switch(wParam) { case IDOK: - GetDlgItemText(hDlg,IDC_NEWVALUE,lpszBuffer,nBufferlen); + SendMessage(hWnd,WM_GETTEXT,(WPARAM)nBufferlen,(LPARAM)szBuffer); // test if valid hex address - for (i = 0; i < (LONG) strlen(lpszBuffer); ++i) + for (i = 0; i < (LONG) strlen(szBuffer); ++i) { - if (isxdigit(lpszBuffer[i]) == FALSE) + if (isxdigit(szBuffer[i]) == 0) { - SendDlgItemMessage(hDlg,IDC_NEWVALUE,EM_SETSEL,0,-1); + SendMessage(hWnd,EM_SETSEL,0,-1); + SetFocus(hWnd); // focus to edit control return FALSE; } } + strcpy(lpszBuffer,szBuffer); // copy valid value // no break case IDCANCEL: - EndDialog(hDlg, wParam); + EndDialog(hDlg,wParam); return TRUE; } } @@ -1338,31 +1535,34 @@ static BOOL CALLBACK EnterAddr(HWND hDlg, UINT message, DWORD wParam, LONG lPara char szBuffer[8]; LONG i; + + HWND hWnd = GetDlgItem(hDlg,IDC_ENTERADR); switch (message) { case WM_INITDIALOG: dwAddress = (DWORD *) lParam; - SetDlgItemText(hDlg,IDC_ENTERADR,""); + SendMessage(hWnd,WM_SETTEXT,0,(LPARAM)""); return TRUE; case WM_COMMAND: switch(wParam) { case IDOK: - GetDlgItemText(hDlg,IDC_ENTERADR,szBuffer,8); + SendMessage(hWnd,WM_GETTEXT,8,(LPARAM)szBuffer); // test if valid hex address for (i = 0; i < (LONG) strlen(szBuffer); ++i) { - if (isxdigit(szBuffer[i]) == FALSE) + if (isxdigit(szBuffer[i]) == 0) { - SendDlgItemMessage(hDlg,IDC_ENTERADR,EM_SETSEL,0,-1); + SendMessage(hWnd,EM_SETSEL,0,-1); + SetFocus(hWnd); // focus to edit control return FALSE; } } if (*szBuffer) sscanf(szBuffer,"%5X",dwAddress); // no break case IDCANCEL: - EndDialog(hDlg, wParam); + EndDialog(hDlg,wParam); return TRUE; } } @@ -1377,19 +1577,111 @@ static VOID OnEnterAddress(HWND hDlg, DWORD *dwValue) } +//################ +//# +//# Breakpoint dialog box +//# +//################ + +// +// enter breakpoint dialog +// +static BOOL CALLBACK EnterBreakpoint(HWND hDlg, UINT message, DWORD wParam, LONG lParam) +{ + static BP_T *sBp; + + char szBuffer[8]; + LONG i; + + HWND hWnd = GetDlgItem(hDlg,IDC_ENTERADR); + + switch (message) + { + case WM_INITDIALOG: + sBp = (BP_T *) lParam; + sBp->nType = BP_EXEC; + SendMessage(hWnd,WM_SETTEXT,0,(LPARAM)""); + SendDlgItemMessage(hDlg,IDC_BPCODE,BM_SETCHECK,1,0); + return TRUE; + case WM_COMMAND: + switch(wParam) + { + case IDC_BPCODE: sBp->nType = BP_EXEC; return TRUE; + case IDC_BPACCESS: sBp->nType = BP_ACCESS; return TRUE; + case IDC_BPREAD: sBp->nType = BP_READ; return TRUE; + case IDC_BPWRITE: sBp->nType = BP_WRITE; return TRUE; + case IDOK: + SendMessage(hWnd,WM_GETTEXT,8,(LPARAM)szBuffer); + // test if valid hex address + for (i = 0; i < (LONG) strlen(szBuffer); ++i) + { + if (isxdigit(szBuffer[i]) == 0) + { + SendMessage(hWnd,EM_SETSEL,0,-1); + SetFocus(hWnd); + return FALSE; + } + } + if (*szBuffer) sscanf(szBuffer,"%5X",&sBp->dwAddr); + // no break + case IDCANCEL: + EndDialog(hDlg,wParam); + return TRUE; + } + } + return FALSE; + UNREFERENCED_PARAMETER(wParam); +} + +static VOID OnEnterBreakpoint(HWND hDlg, BP_T *sValue) +{ + if (DialogBoxParam(hApp, MAKEINTRESOURCE(IDD_ENTERBREAK), hDlg, (DLGPROC)EnterBreakpoint, (LPARAM)sValue) == -1) + AbortMessage("Breakpoint Dialog Box Creation Error !"); +} + + //################ //# //# Edit breakpoint dialog box //# //################ +// +// draw breakpoint type +// +static VOID DrawBreakpoint(HWND hWnd, BP_T sBp) +{ + char *szFmt,szBuffer[32]; + + switch(sBp.nType) + { + case BP_EXEC: // code breakpoint + szFmt = "%05X (Code)"; + break; + case BP_READ: // read memory breakpoint + szFmt = "%05X (Memory Read)"; + break; + case BP_WRITE: // write memory breakpoint + szFmt = "%05X (Memory Write)"; + break; + case BP_ACCESS: // memory breakpoint + szFmt = "%05X (Memory Access)"; + break; + default: // unknown breakpoint type + _ASSERT(0); + } + wsprintf(szBuffer,szFmt,sBp.dwAddr); + SendMessage(hWnd,LB_ADDSTRING,0,(LPARAM) szBuffer); + return; +} + // // enter edit breakpoint dialog // static BOOL CALLBACK EditBreakpoint(HWND hDlg, UINT message, DWORD wParam, LONG lParam) { char szBuffer[32]; - DWORD dwAddress; + BP_T sBp; INT i; HWND hWnd = GetDlgItem(hDlg,IDC_BREAKEDIT_WND); @@ -1406,30 +1698,53 @@ static BOOL CALLBACK EditBreakpoint(HWND hDlg, UINT message, DWORD wParam, LONG SendMessage(hWnd,WM_SETREDRAW,FALSE,0); SendMessage(hWnd,LB_RESETCONTENT,0,0); for (i = 0; i < wBreakpointCount; ++i) - { - wsprintf(szBuffer,"%05X (Code)",dwBreakpoint[i]); - SendMessage(hWnd,LB_ADDSTRING,0,(LPARAM) szBuffer); - } + DrawBreakpoint(hWnd,sBreakpoint[i]); SendMessage(hWnd,WM_SETREDRAW,TRUE,0); return TRUE; case WM_COMMAND: switch(wParam) { case IDC_BREAKEDIT_ADD: - dwAddress = -1; // no address given - OnEnterAddress(hDlg, &dwAddress); - if (dwAddress != -1) + sBp.dwAddr = -1; // no breakpoint given + OnEnterBreakpoint(hDlg, &sBp); + if (sBp.dwAddr != -1) { for (i = 0; i < wBreakpointCount; ++i) { - if (dwBreakpoint[i] == dwAddress) - return FALSE; - } - - wsprintf(szBuffer,"%05X (Code)",dwAddress); - SendMessage(hWnd,LB_ADDSTRING,0,(LPARAM) szBuffer); + if (sBreakpoint[i].dwAddr == sBp.dwAddr) + { + // tried to add used code breakpoint + if ((sBreakpoint[i].nType & sBp.nType & BP_EXEC) != 0) + return FALSE; - dwBreakpoint[wBreakpointCount] = dwAddress; + // only modify memory breakpoints + if (sBreakpoint[i].nType != BP_EXEC && sBp.nType != BP_EXEC) + { + // replace breakpoint type + sBreakpoint[i].nType = sBp.nType; + + // redaw breakpoint list + SendMessage(hWnd,WM_SETREDRAW,FALSE,0); + SendMessage(hWnd,LB_RESETCONTENT,0,0); + for (i = 0; i < wBreakpointCount; ++i) + DrawBreakpoint(hWnd,sBreakpoint[i]); + SendMessage(hWnd,WM_SETREDRAW,TRUE,0); + return FALSE; + } + } + } + + // check for breakpoint buffer full + if (wBreakpointCount >= MAXBREAKPOINTS) + { + AbortMessage("Reached maximum number of breakpoints !"); + return FALSE; + } + + DrawBreakpoint(hWnd,sBp); + + sBreakpoint[wBreakpointCount].nType = sBp.nType; + sBreakpoint[wBreakpointCount].dwAddr = sBp.dwAddr; ++wBreakpointCount; } return TRUE; @@ -1440,14 +1755,16 @@ static BOOL CALLBACK EditBreakpoint(HWND hDlg, UINT message, DWORD wParam, LONG SendMessage(hWnd,LB_GETTEXT,i,(LPARAM) szBuffer); SendMessage(hWnd,LB_DELETESTRING,i,0); - sscanf(szBuffer,"%X",&dwAddress); + sBp.nType = (szBuffer[7] == 'C') ? BP_EXEC : BP_ACCESS; + sscanf(szBuffer,"%X",&sBp.dwAddr); for (i = 0; i < wBreakpointCount; ++i) { - if (dwBreakpoint[i] == dwAddress) + if ( (sBreakpoint[i].nType & sBp.nType) != 0 + && sBreakpoint[i].dwAddr == sBp.dwAddr) { // move rest to top for (++i; i < wBreakpointCount; ++i) - dwBreakpoint[i-1] = dwBreakpoint[i]; + sBreakpoint[i-1] = sBreakpoint[i]; --wBreakpointCount; break; @@ -1456,7 +1773,7 @@ static BOOL CALLBACK EditBreakpoint(HWND hDlg, UINT message, DWORD wParam, LONG return TRUE; case IDOK: case IDCANCEL: - EndDialog(hDlg, wParam); + EndDialog(hDlg,wParam); return TRUE; } } @@ -1473,3 +1790,68 @@ static BOOL OnEditBreakpoint(HWND hDlg) UpdateCodeWnd(hDlg); // update code window return -1; } + + +//################ +//# +//# Last Instruction dialog box +//# +//################ + +// +// view last instructions +// +static BOOL CALLBACK InfoIntr(HWND hDlg, UINT message, DWORD wParam, LONG lParam) +{ + char szBuffer[64]; + LONG lIndex; + WORD i,j; + + HWND hWnd = GetDlgItem(hDlg,IDC_INSTR_CODE); + + switch (message) + { + case WM_INITDIALOG: + // font settings + SendDlgItemMessage(hDlg,IDC_INSTR_TEXT, WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); + SendDlgItemMessage(hDlg,IDC_INSTR_CLEAR, WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); + SendDlgItemMessage(hDlg,IDC_INSTR_COPY, WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); + SendDlgItemMessage(hDlg,IDCANCEL, WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); + + SendMessage(hWnd,WM_SETREDRAW,FALSE,0); + SendMessage(hWnd,LB_RESETCONTENT,0,0); + for (i = wInstrRp; i != wInstrWp; i = (i + 1) % wInstrSize) + { + j = wsprintf(szBuffer,"%05lX ",pdwInstrArray[i]); + disassemble(pdwInstrArray[i],&szBuffer[j],VIEW_SHORT); + lIndex = SendMessage(hWnd,LB_ADDSTRING,0,(LPARAM) szBuffer); + } + SendMessage(hWnd,WM_SETREDRAW,TRUE,0); + SendMessage(hWnd,LB_SETCARETINDEX,lIndex,TRUE); + return TRUE; + case WM_COMMAND: + switch(wParam) + { + case IDC_INSTR_COPY: + CopyItemsToClipboard(hWnd); // copy selected items to clipboard + return TRUE; + case IDC_INSTR_CLEAR: // clear instruction buffer + wInstrRp = wInstrWp; + SendMessage(hWnd,LB_RESETCONTENT,0,0); + return TRUE; + case IDCANCEL: + EndDialog(hDlg,wParam); + return TRUE; + } + } + return FALSE; + UNREFERENCED_PARAMETER(wParam); + UNREFERENCED_PARAMETER(lParam); +} + +static BOOL OnInfoIntr(HWND hDlg) +{ + if (DialogBox(hApp, MAKEINTRESOURCE(IDD_INSTRUCTIONS), hDlg, (DLGPROC)InfoIntr) == -1) + AbortMessage("Last Instructions Dialog Box Creation Error !"); + return TRUE; +} diff --git a/sources/Emu48/DEBUGGER.H b/sources/Emu48/DEBUGGER.H index b20648f..5d0e0e9 100644 --- a/sources/Emu48/DEBUGGER.H +++ b/sources/Emu48/DEBUGGER.H @@ -7,12 +7,13 @@ * */ -// #define BP_EXEC 1 // code breakpoint -// #define BP_READ 2 // read memory breakpoint -// #define BP_WRITE 4 // write memory breakpoint -// #define BP_RANGE 8 // check address range +// breakpoint type definitions +#define BP_EXEC 1 // code breakpoint +#define BP_READ 2 // read memory breakpoint +#define BP_WRITE 4 // write memory breakpoint +#define BP_ACCESS (BP_READ|BP_WRITE) // read/write memory breakpoint -// 13.11.99 cg, new, debugger state definitions +// debugger state definitions #define DBG_RUN 0 #define DBG_STEPINTO 1 #define DBG_STEPOVER 2 @@ -22,10 +23,16 @@ extern HWND hDlgDebug; extern HANDLE hEventDebug; extern BOOL bDbgEnable; -extern INT nDbgState; // 13.11.99 cg, state of debugger +extern INT nDbgState; extern BOOL bDbgNOP3; +extern BOOL bDbgRPL; +extern BOOL bDbgSkipInt; extern DWORD dwDbgRstkp; -extern DWORD dwDbgRstk; // 13.11.99 cg, possible return address -extern BOOL CheckBreakpoint(DWORD dwAddr); -extern VOID NotifyDebugger(VOID); // 10.11.99 cg, notify debugger emulation stopped +extern DWORD dwDbgRstk; +extern DWORD *pdwInstrArray; +extern WORD wInstrSize; +extern WORD wInstrWp; +extern WORD wInstrRp; +extern BOOL CheckBreakpoint(DWORD dwAddr, DWORD wRange, UINT nType); +extern VOID NotifyDebugger(BOOL bType); extern LRESULT OnToolDebug(VOID); diff --git a/sources/Emu48/DISASM.C b/sources/Emu48/DISASM.C index a1e9d72..a35edea 100644 --- a/sources/Emu48/DISASM.C +++ b/sources/Emu48/DISASM.C @@ -503,13 +503,14 @@ static char *disasm_1 (DWORD *addr, char *out) case 0: case 1: fn = read_nibble (addr); - fn = (fn & 7); - if (fn > 4) - fn -= 4; + c = (fn < 8); // flag for operand register + fn = (fn & 7); // get register number + if (fn > 4) // illegal opcode + break; // no output switch (disassembler_mode) { case HP_MNEMONICS: - c = (char) ((fn < 8) ? 'A' : 'C'); + c = (char) (c ? 'A' : 'C'); if (n == 0) wsprintf (buf, "R%d=%c", fn, c); else @@ -519,7 +520,7 @@ static char *disasm_1 (DWORD *addr, char *out) case CLASS_MNEMONICS: p = append_str (out, "move.w"); p = append_tab (out); - c = (char) ((fn < 8) ? 'a' : 'c'); + c = (char) (c ? 'a' : 'c'); if (n == 0) wsprintf (buf, "%c, r%d", c, fn); else @@ -534,20 +535,21 @@ static char *disasm_1 (DWORD *addr, char *out) case 2: fn = read_nibble (addr); - fn = (fn & 7); - if (fn > 4) - fn -= 4; + c = (fn < 8); // flag for operand register + fn = (fn & 7); // get register number + if (fn > 4) // illegal opcode + break; // no output switch (disassembler_mode) { case HP_MNEMONICS: - c = (char) ((fn < 8) ? 'A' : 'C'); + c = (char) (c ? 'A' : 'C'); wsprintf (buf, "%cR%dEX", c, fn); p = append_str (out, buf); break; case CLASS_MNEMONICS: p = append_str (out, "exg.w"); p = append_tab (out); - c = (char) ((fn < 8) ? 'a' : 'c'); + c = (char) (c ? 'a' : 'c'); wsprintf (buf, "%c, r%d", c, fn); p = append_str (p, buf); break; @@ -1020,6 +1022,8 @@ static char *disasm_8 (DWORD *addr, char *out, BOOL view) break; case 9: + fn = read_nibble (addr); // get field selector + n = read_nibble (addr); // get register selector switch (disassembler_mode) { case HP_MNEMONICS: @@ -1027,11 +1031,11 @@ static char *disasm_8 (DWORD *addr, char *out, BOOL view) op_str_81[(n & 3) + 4 * disassembler_mode]); p = append_str (out, buf); p = append_tab (out); - p = append_field (p, read_nibble (addr)); + p = append_field (p, fn); break; case CLASS_MNEMONICS: p = append_str (out, "lsr"); - p = append_field (p, read_nibble (addr)); + p = append_field (p, fn); p = append_tab (out); p = append_str (p, "#1, "); p = append_str (p, op_str_81[(n & 3) + 4 * disassembler_mode]); diff --git a/sources/Emu48/DISPLAY.C b/sources/Emu48/DISPLAY.C index 9dae547..3f11721 100644 --- a/sources/Emu48/DISPLAY.C +++ b/sources/Emu48/DISPLAY.C @@ -9,6 +9,7 @@ #include "pch.h" #include "resource.h" #include "Emu48.h" +#include "io.h" #include "kml.h" #define LCD1_ROW 144 @@ -109,8 +110,13 @@ VOID CreateLcdBitmap() bmiLcd.Lcd_bmih.biWidth = LCD1_ROW * nLcdDoubled; bmiLcd.Lcd_bmih.biHeight = -64 * nLcdDoubled; hLcdDC = CreateCompatibleDC(hWindowDC); + _ASSERT(hLcdDC != NULL); hLcdBitmap = CreateDIBSection(hLcdDC, (BITMAPINFO*)&bmiLcd,DIB_RGB_COLORS, (LPVOID*)&pbyLcd, NULL, 0); + _ASSERT(hLcdBitmap != NULL); hOldLcdBitmap = SelectObject(hLcdDC, hLcdBitmap); + _ASSERT(hPalette != NULL); + SelectPalette(hLcdDC, hPalette, FALSE); // set palette for LCD DC + RealizePalette(hLcdDC); // realize palette UpdateContrast(Chipset.contrast); } @@ -135,7 +141,12 @@ VOID DestroyLcdBitmap() BOOL CreateMainBitmap(LPSTR szFilename) { + HPALETTE hAssertPalette; + + _ASSERT(hWindowDC != NULL); hMainDC = CreateCompatibleDC(hWindowDC); + _ASSERT(hMainDC != NULL); + if (hMainDC == NULL) return FALSE; // quit if failed hMainBitmap = LoadBitmapFile(szFilename); if (hMainBitmap == NULL) { @@ -143,7 +154,10 @@ BOOL CreateMainBitmap(LPSTR szFilename) return FALSE; } hOldMainBitmap = SelectObject(hMainDC, hMainBitmap); - SelectPalette(hMainDC, hPalette, FALSE); + _ASSERT(hPalette != NULL); + hAssertPalette = SelectPalette(hMainDC, hPalette, FALSE); + _ASSERT(hAssertPalette != NULL); + RealizePalette(hMainDC); return TRUE; } @@ -556,14 +570,17 @@ VOID UpdateAnnunciators() { BYTE c; - c = (BYTE)(Chipset.IORam[0xB] | (Chipset.IORam[0xC]<<4)); - if (!(c&0x80)) c=0; - DrawAnnunciator(1,c&0x01); - DrawAnnunciator(2,c&0x02); - DrawAnnunciator(3,c&0x04); - DrawAnnunciator(4,c&0x08); - DrawAnnunciator(5,c&0x10); - DrawAnnunciator(6,c&0x20); + c = (BYTE)(Chipset.IORam[ANNCTRL] | (Chipset.IORam[ANNCTRL+1]<<4)); + // switch annunciators off if timer stopped + if ((c & AON) == 0 || (Chipset.IORam[TIMER2_CTRL] & RUN) == 0) + c=0; + + DrawAnnunciator(1,c&LA1); + DrawAnnunciator(2,c&LA2); + DrawAnnunciator(3,c&LA3); + DrawAnnunciator(4,c&LA4); + DrawAnnunciator(5,c&LA5); + DrawAnnunciator(6,c&LA6); return; } diff --git a/sources/Emu48/EMU48.C b/sources/Emu48/EMU48.C index c6f05fb..c3d7724 100644 --- a/sources/Emu48/EMU48.C +++ b/sources/Emu48/EMU48.C @@ -9,10 +9,11 @@ #include "pch.h" #include "resource.h" #include "Emu48.h" +#include "io.h" #include "kml.h" #include "debugger.h" -#define VERSION "1.15" // 23.10.99 cg, changed version +#define VERSION "1.20" #define CF_HPOBJ "CF_HPOBJ" // clipboard format for DDE #define MAXPORTS 16 // number of COM ports @@ -28,7 +29,7 @@ LPSTR szAppName = "Emu48"; // application name for DDE server LPSTR szTopic = "Stack"; // topic for DDE server LPSTR szTitle = NULL; -static const char szLicence[] = // 30.11.99 cg, changed, update of address +static const char szLicence[] = "This program is free software; you can redistribute it and/or modify\r\n" "it under the terms of the GNU General Public License as published by\r\n" "the Free Software Foundation; either version 2 of the License, or\r\n" @@ -49,10 +50,11 @@ CRITICAL_SECTION csKeyLock; // critical section for key scan CRITICAL_SECTION csIOLock; // critical section for I/O access CRITICAL_SECTION csT1Lock; // critical section for timer1 access CRITICAL_SECTION csT2Lock; // critical section for timer2 access -CRITICAL_SECTION csRecvLock; // 24.10.99 cg, moved, critical section for receive byte -INT nArgc; // 08.11.99 cg, new, no. of command line arguments -LPCTSTR *ppArgv; // 08.11.99 cg, new, command line arguments -LARGE_INTEGER lFreq; // counter frequency +CRITICAL_SECTION csRecvLock; // critical section for receive byte +INT nArgc; // no. of command line arguments +LPCTSTR *ppArgv; // command line arguments +LARGE_INTEGER lFreq; // high performance counter frequency +LARGE_INTEGER lAppStart; // high performance counter value at Appl. start DWORD idDdeInst; // DDE server id UINT uCF_HpObj; // DDE clipboard format HANDLE hThread; @@ -110,10 +112,9 @@ VOID UpdateWindowStatus() if ((nState == 0)||(nState == 3)) { - // 02.12.99 cg, new, disable stack loading items on HP38G - UINT uStackEnable = (cCurrentRomType!='A') ? MF_ENABLED : MF_GRAYED; + // disable stack loading items on HP38G + UINT uStackEnable = (cCurrentRomType!='6' && cCurrentRomType!='A') ? MF_ENABLED : MF_GRAYED; - // 02.12.99 cg, changed implementation EnableMenuItem(hMenu,ID_FILE_SAVE,(szCurrentFilename[0]) ? MF_ENABLED : MF_GRAYED); EnableMenuItem(hMenu,ID_FILE_SAVEAS,MF_ENABLED); EnableMenuItem(hMenu,ID_FILE_CLOSE,MF_ENABLED); @@ -121,12 +122,10 @@ VOID UpdateWindowStatus() EnableMenuItem(hMenu,ID_BACKUP_SAVE,MF_ENABLED); EnableMenuItem(hMenu,ID_VIEW_COPY,MF_ENABLED); EnableMenuItem(hMenu,ID_VIEW_RESET,MF_ENABLED); - // 02.12.99 cg, new, added HP38G part EnableMenuItem(hMenu,ID_OBJECT_LOAD,uStackEnable); EnableMenuItem(hMenu,ID_OBJECT_SAVE,uStackEnable); EnableMenuItem(hMenu,ID_STACK_COPY,uStackEnable); EnableMenuItem(hMenu,ID_STACK_PASTE,uStackEnable); - // 02.12.99 cg, end of added part EnableMenuItem(hMenu,ID_TOOL_DISASM,MF_ENABLED); } else @@ -158,6 +157,70 @@ VOID UpdateWindowStatus() +//################ +//# +//# Clipboard Tool +//# +//################ + +VOID CopyItemsToClipboard(HWND hWnd) // save selected Listbox Items to Clipboard +{ + LONG i; + LPINT lpnCount; + + // get number of selections + if ((i = SendMessage(hWnd,LB_GETSELCOUNT,0,0)) == 0) + return; // no items selected + + if ((lpnCount = (LPINT) LocalAlloc(LMEM_FIXED,i * sizeof(INT))) != NULL) + { + HANDLE hClipObj; + LONG j,lMem = 0; + + // get indexes of selected items + i = SendMessage(hWnd,LB_GETSELITEMS,i,(LPARAM) lpnCount); + for (j = 0;j < i;++j) // scan all selected items + { + // calculate total amount of needed memory + lMem += SendMessage(hWnd,LB_GETTEXTLEN,lpnCount[j],0) + 2; + } + // allocate clipboard data + if ((hClipObj = GlobalAlloc(GMEM_MOVEABLE,lMem + 1)) != NULL) + { + LPBYTE lpData; + + if (lpData = GlobalLock(hClipObj)) + { + for (j = 0;j < i;++j) // scan all selected items + { + lpData += SendMessage(hWnd,LB_GETTEXT,lpnCount[j],(LPARAM) lpData); + *lpData++ = '\r'; + *lpData++ = '\n'; + } + *lpData = 0; // set end of string + GlobalUnlock(hClipObj); // unlock memory + } + + if (OpenClipboard(hWnd)) + { + if (EmptyClipboard()) + SetClipboardData(CF_TEXT,hClipObj); + else + GlobalFree(hClipObj); + CloseClipboard(); + } + else // clipboard open failed + { + GlobalFree(hClipObj); + } + } + LocalFree(lpnCount); // free item table + } + return; +} + + + //################ //# //# Settings @@ -210,6 +273,9 @@ static BOOL CALLBACK SettingsProc(HWND hDlg, UINT message, DWORD wParam, LONG lP CheckDlgButton(hDlg,IDC_AUTOSAVEONEXIT,bAutoSaveOnExit); CheckDlgButton(hDlg,IDC_ALWAYSDISPLOG,bAlwaysDisplayLog); + // set disassebler mode + CheckDlgButton(hDlg,(disassembler_mode == HP_MNEMONICS) ? IDC_DISASM_HP : IDC_DISASM_CLASS,BST_CHECKED); + // set combobox parameter SetCommList(hDlg,IDC_WIRE,szSerialWire); SetCommList(hDlg,IDC_IR,szSerialIr); @@ -219,12 +285,11 @@ static BOOL CALLBACK SettingsProc(HWND hDlg, UINT message, DWORD wParam, LONG lP EnableWindow(GetDlgItem(hDlg,IDC_IR),FALSE); } - // 01.12.99 cg, HP48SX/GX - if (cCurrentRomType!='A' && cCurrentRomType!='X') + // HP48SX/GX + if (cCurrentRomType!='6' && cCurrentRomType!='A' && cCurrentRomType!='X') { // init port1 enable checkbox CheckDlgButton(hDlg,IDC_PORT1EN,(Chipset.cards_status & PORT1_PRESENT) != 0); - // 19.11.99 cg, changed, use cards_status variable now CheckDlgButton(hDlg,IDC_PORT1WR,(Chipset.cards_status & PORT1_WRITE) != 0); // init port2 shared checkbox and set port2 filename CheckDlgButton(hDlg,IDC_PORT2ISSHARED,bPort2IsShared); @@ -245,7 +310,7 @@ static BOOL CALLBACK SettingsProc(HWND hDlg, UINT message, DWORD wParam, LONG lP CheckDlgButton(hDlg,IDC_PORT2ISSHARED,bPort2IsShared); SetDlgItemText(hDlg,IDC_PORT2,szPort2Filename); } - if (cCurrentRomType=='X') // 01.12.99 cg, HP48SX/GX + if (cCurrentRomType=='X') // HP49G { SendDlgItemMessage(hDlg,IDC_IR,CB_RESETCONTENT,0,0); EnableWindow(GetDlgItem(hDlg,IDC_IR),FALSE); @@ -264,19 +329,27 @@ static BOOL CALLBACK SettingsProc(HWND hDlg, UINT message, DWORD wParam, LONG lP if (Chipset.Port1Size && cCurrentRomType!='X') { UINT nOldState = SwitchToState(3); - // port1 disabled ?; + // save old card status + BYTE bCardsStatus = Chipset.cards_status; + + // port1 disabled? Chipset.cards_status &= ~(PORT1_PRESENT | PORT1_WRITE); if (IsDlgButtonChecked(hDlg, IDC_PORT1EN)) { Chipset.cards_status |= PORT1_PRESENT; - // 19.11.99 cg, changed, use dialog variable now if (IsDlgButtonChecked(hDlg, IDC_PORT1WR)) Chipset.cards_status |= PORT1_WRITE; } - if (Chipset.inte) + + // changed card status in slot1? + if ( ((bCardsStatus ^ Chipset.cards_status) & (PORT1_PRESENT | PORT1_WRITE)) != 0 + && (Chipset.IORam[CARDCTL] & ECDT) != 0 && (Chipset.IORam[TIMER2_CTRL] & RUN) != 0 + ) { - Chipset.Shutdn = FALSE; - Chipset.SoftInt = TRUE; + + Chipset.HST |= MP; // set Module Pulled + IOBit(SRQ2,NINT,FALSE); // set NINT to low + Chipset.SoftInt = TRUE; // set interrupt bInterrupt = TRUE; } Map(0x00,0xFF); @@ -293,9 +366,12 @@ static BOOL CALLBACK SettingsProc(HWND hDlg, UINT message, DWORD wParam, LONG lP bPort2IsShared = IsDlgButtonChecked(hDlg,IDC_PORT2ISSHARED); GetDlgItemText(hDlg,IDC_PORT2,szPort2Filename,sizeof(szPort2Filename)); } + // set disassebler mode + disassembler_mode = IsDlgButtonChecked(hDlg,IDC_DISASM_HP) ? HP_MNEMONICS : CLASS_MNEMONICS; // set combobox parameter GetDlgItemText(hDlg,IDC_WIRE,szSerialWire,sizeof(szSerialWire)); - GetDlgItemText(hDlg,IDC_IR ,szSerialIr ,sizeof(szSerialIr)); + if (cCurrentRomType!='X') // HP49G Ir port is not connected + GetDlgItemText(hDlg,IDC_IR,szSerialIr,sizeof(szSerialIr)); EndDialog(hDlg, wParam); } if (wParam == IDCANCEL) @@ -340,7 +416,7 @@ static UINT SaveChanges(BOOL bAuto) uReply = GetSaveAsFilename(); if (uReply != IDOK) return uReply; if (!SaveDocumentAs(szBufferFilename)) return IDCANCEL; - WriteLastDocument(szCurrentFilename); // 11.12.99 cg, changed, call function + WriteLastDocument(szCurrentFilename); return IDYES; } @@ -361,6 +437,13 @@ static UINT SaveChanges(BOOL bAuto) // static LRESULT OnCreate(HWND hWindow) { + InitializeCriticalSection(&csGDILock); + InitializeCriticalSection(&csKeyLock); + InitializeCriticalSection(&csIOLock); + InitializeCriticalSection(&csT1Lock); + InitializeCriticalSection(&csT2Lock); + InitializeCriticalSection(&csRecvLock); + hWnd = hWindow; hWindowDC = GetDC(hWnd); DragAcceptFiles(hWnd,TRUE); // support dropped files @@ -373,12 +456,18 @@ static LRESULT OnCreate(HWND hWindow) static LRESULT OnDestroy(HWND hWindow) { DragAcceptFiles(hWnd,FALSE); // no WM_DROPFILES message any more - // SwitchToState(2); // 01.12.99 cg, removed, doing in main program now - // WriteSettings(); // 01.12.99 cg, removed, doing in main program now ReleaseDC(hWnd, hWindowDC); SetWindowTitle(NULL); // free memory of title hWindowDC = NULL; // hWindowDC isn't valid any more hWnd = NULL; + + DeleteCriticalSection(&csGDILock); + DeleteCriticalSection(&csKeyLock); + DeleteCriticalSection(&csIOLock); + DeleteCriticalSection(&csT1Lock); + DeleteCriticalSection(&csT2Lock); + DeleteCriticalSection(&csRecvLock); + PostQuitMessage(0); return 0; UNREFERENCED_PARAMETER(hWindow); @@ -395,7 +484,12 @@ static LRESULT OnPaint(HWND hWindow) hPaintDC = BeginPaint(hWindow, &Paint); if (hMainDC != NULL) { - BitBlt(hPaintDC, 0, 0, nBackgroundW, nBackgroundH, hMainDC, nBackgroundX, nBackgroundY, SRCCOPY); + EnterCriticalSection(&csGDILock); // solving NT GDI problems + { + BitBlt(hPaintDC, 0, 0, nBackgroundW, nBackgroundH, hMainDC, nBackgroundX, nBackgroundY, SRCCOPY); + GdiFlush(); + } + LeaveCriticalSection(&csGDILock); if ((nState==0)||(nState==3)) { UpdateMainDisplay(); @@ -417,7 +511,8 @@ static LRESULT OnDropFiles(HANDLE hFilesInfo) WORD wNumFiles,wIndex; BOOL bSuccess; - if (cCurrentRomType=='A') return 0; // 06.12.99 cg, new, HP38 has no stack, ignore + // HP38 has no stack, ignore + if (cCurrentRomType=='6' || cCurrentRomType=='A') return 0; // get number of files dropped wNumFiles = DragQueryFile (hFilesInfo,(UINT)-1,NULL,0); @@ -438,7 +533,6 @@ static LRESULT OnDropFiles(HANDLE hFilesInfo) return 0; } - while (nState!=nNextState) Sleep(0); _ASSERT(nState==3); // get each name and load it into the emulator @@ -547,7 +641,7 @@ static LRESULT OnFileSaveAs() SwitchToState(0); return 0; } - WriteLastDocument(szCurrentFilename); // 11.12.99 cg, changed, call function + WriteLastDocument(szCurrentFilename); SwitchToState(0); return 0; @@ -606,7 +700,6 @@ static LRESULT OnStackCopy() // copy data from stack return 0; } - while (nState!=nNextState) Sleep(0); _ASSERT(nState==3); if ((dwAddress = RPL_Pick(1)) == 0) // pick address of level1 object @@ -682,7 +775,6 @@ static LRESULT OnStackPaste() // paste data to stack return 0; } - while (nState!=nNextState) Sleep(0); _ASSERT(nState==3); if (OpenClipboard(hWnd)) @@ -848,7 +940,7 @@ static LRESULT OnViewReset() if (YesNoMessage("Are you sure you want to press the Reset Button ?")==IDYES) { SwitchToState(3); - CpuReset(); // 19.03.99 cg, changed, register setting after Cpu Reset + CpuReset(); // register setting after Cpu Reset SwitchToState(0); } return 0; @@ -963,7 +1055,6 @@ static LRESULT OnObjectLoad() return 0; } - while (nState!=nNextState) Sleep(0); _ASSERT(nState==3); if (bWarning) @@ -971,7 +1062,7 @@ static LRESULT OnObjectLoad() UINT uReply = YesNoCancelMessage( "Warning: Trying to load an object while the emulator is busy \n" "will certainly result in a memory lost. Before loading an object \n" - "you should be sure that the HP48 is not doing anything. \n" + "you should be sure that the calculator is not doing anything. \n" "Do you want to see this warning next time you try to load an object ?"); switch (uReply) { @@ -1020,9 +1111,11 @@ static LRESULT OnObjectSave() } if (WaitForSleepState()) // wait for cpu SHUTDN then sleep state + { + InfoMessage("The emulator is busy."); return 0; + } - while (nState!=nNextState) Sleep(0); _ASSERT(nState==3); if (!GetSaveObjectFilename()) @@ -1046,9 +1139,8 @@ static BOOL CALLBACK Disasm(HWND hDlg, UINT message, DWORD wParam, LONG lParam) static DWORD dwAddress, dwAddressMax; LONG i; - LPINT lpnCount; char *cpStop,szAddress[256] = "0"; - + switch (message) { case WM_INITDIALOG: @@ -1059,11 +1151,7 @@ static BOOL CALLBACK Disasm(HWND hDlg, UINT message, DWORD wParam, LONG lParam) SendDlgItemMessage(hDlg,IDC_DISASM_RAM,WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); SendDlgItemMessage(hDlg,IDC_DISASM_PORT1,WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); SendDlgItemMessage(hDlg,IDC_DISASM_PORT2,WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); - SendDlgItemMessage(hDlg,IDC_DISASM_MNEMONICS,WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); - SendDlgItemMessage(hDlg,IDC_DISASM_HP,WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); - SendDlgItemMessage(hDlg,IDC_DISASM_CLASS,WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); SendDlgItemMessage(hDlg,IDC_DISASM_MAP,BM_SETCHECK,1,0); - SendDlgItemMessage(hDlg,IDC_DISASM_HP,BM_SETCHECK,1,0); SendDlgItemMessage(hDlg,IDC_ADDRESS,WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); SendDlgItemMessage(hDlg,IDC_DISASM_ADR,WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); SendDlgItemMessage(hDlg,IDC_DISASM_NEXT,WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); @@ -1071,7 +1159,6 @@ static BOOL CALLBACK Disasm(HWND hDlg, UINT message, DWORD wParam, LONG lParam) SendDlgItemMessage(hDlg,IDCANCEL,WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),MAKELPARAM(FALSE,0)); SetDlgItemText(hDlg,IDC_DISASM_ADR,szAddress); disassembler_map = MEM_MAP; // disassemble with mapped modules - disassembler_mode = HP_MNEMONICS; // use HP mnemonics for disassembling dwAddress = strtoul(szAddress,&cpStop,16); dwAddressMax = 0x100000; // greatest address (mapped mode) return TRUE; @@ -1101,12 +1188,6 @@ static BOOL CALLBACK Disasm(HWND hDlg, UINT message, DWORD wParam, LONG lParam) ? (Chipset.Port2Size * 2048) : ((dwPort2Mask != 0) ? ((dwPort2Mask + 1) << 18) : (dwPort2Size * 2048)); return TRUE; - case IDC_DISASM_HP: - disassembler_mode = HP_MNEMONICS; - return TRUE; - case IDC_DISASM_CLASS: - disassembler_mode = CLASS_MNEMONICS; - return TRUE; case IDOK: SendDlgItemMessage(hDlg,IDC_DISASM_ADR,EM_SETSEL,0,-1); GetDlgItemText(hDlg,IDC_DISASM_ADR,szAddress,sizeof(szAddress)); @@ -1121,7 +1202,7 @@ static BOOL CALLBACK Disasm(HWND hDlg, UINT message, DWORD wParam, LONG lParam) case IDC_DISASM_NEXT: if (dwAddress >= dwAddressMax) return FALSE; - i = wsprintf(szAddress,(dwAddress <= 99999) ? "%05lX " : "%06lX ",dwAddress); + i = wsprintf(szAddress,(dwAddress <= 0xFFFFF) ? "%05lX " : "%06lX ",dwAddress); dwAddress = disassemble(dwAddress,&szAddress[i],VIEW_LONG); i = SendDlgItemMessage(hDlg,IDC_DISASM_WIN,LB_ADDSTRING,0,(LPARAM) szAddress); SendDlgItemMessage(hDlg,IDC_DISASM_WIN,LB_SELITEMRANGE,FALSE,MAKELPARAM(0,i)); @@ -1129,54 +1210,8 @@ static BOOL CALLBACK Disasm(HWND hDlg, UINT message, DWORD wParam, LONG lParam) SendDlgItemMessage(hDlg,IDC_DISASM_WIN,LB_SETTOPINDEX,i,0); return TRUE; case IDC_DISASM_COPY: - // get number of selections - if ((i = SendDlgItemMessage(hDlg,IDC_DISASM_WIN,LB_GETSELCOUNT,0,0)) == 0) - break; // no items selected - - if ((lpnCount = (LPINT) LocalAlloc(LMEM_FIXED,i * sizeof(INT))) != NULL) - { - HANDLE hClipObj; - LONG j,lMem = 0; - - // get indexes of selected items - i = SendDlgItemMessage(hDlg,IDC_DISASM_WIN,LB_GETSELITEMS,i,(LPARAM) lpnCount); - for (j = 0;j < i;++j) // scan all selected items - { - // calculate total amount of needed memory - lMem += SendDlgItemMessage(hDlg,IDC_DISASM_WIN,LB_GETTEXTLEN,lpnCount[j],0) + 2; - } - // allocate clipboard data - if ((hClipObj = GlobalAlloc(GMEM_MOVEABLE,lMem + 1)) != NULL) - { - LPBYTE lpData; - - if (lpData = GlobalLock(hClipObj)) - { - for (j = 0;j < i;++j) // scan all selected items - { - lpData += SendDlgItemMessage(hDlg,IDC_DISASM_WIN,LB_GETTEXT,lpnCount[j],(LPARAM) lpData); - *lpData++ = '\r'; - *lpData++ = '\n'; - } - *lpData = 0; // set end of string - GlobalUnlock(hClipObj); // unlock memory - } - - if (OpenClipboard(hWnd)) - { - if (EmptyClipboard()) - SetClipboardData(CF_TEXT,hClipObj); - else - GlobalFree(hClipObj); - CloseClipboard(); - } - else // clipboard open failed - { - GlobalFree(hClipObj); - } - } - LocalFree(lpnCount); // free item table - } + // copy selected items to clipboard + CopyItemsToClipboard(GetDlgItem(hDlg,IDC_DISASM_WIN)); return TRUE; case IDCANCEL: EndDialog(hDlg, wParam); @@ -1335,29 +1370,17 @@ LRESULT CALLBACK MainWndProc(HWND hWindow, UINT uMsg, WPARAM wParam, LPARAM lPar return DefWindowProc(hWindow, uMsg, wParam, lParam); } -#if 0 // 01.12.99 cg, removed -static BOOL FlushMessages(LPMSG msg) -{ - while (PeekMessage(msg, NULL, 0, 0, PM_REMOVE)) - { - if (msg->message == WM_QUIT) return TRUE; - TranslateMessage(msg); - DispatchMessage(msg); - } - return FALSE; -} -#endif // 01.12.99 cg - int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { MSG msg; WNDCLASS wc; RECT rectWindow; HSZ hszService, hszTopic; // variables for DDE server + DWORD dwAffMask; hApp = hInst; - nArgc = __argc; // 08.11.99 cg, new, no. of command line arguments - ppArgv = (LPCTSTR*) __argv; // 08.11.99 cg, new, command line arguments + nArgc = __argc; // no. of command line arguments + ppArgv = (LPCTSTR*) __argv; // command line arguments wc.style = CS_BYTEALIGNCLIENT; wc.lpfnWndProc = (WNDPROC)MainWndProc; @@ -1379,7 +1402,6 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nC } // Create window - rectWindow.left = 0; rectWindow.top = 0; rectWindow.right = 256; @@ -1402,11 +1424,9 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nC ShowWindow(hWnd, nCmdShow); - // 01.12.99 cg, removed, useless and quit with resources open - // if (FlushMessages(&msg)) return msg.wParam; - // initialization QueryPerformanceFrequency(&lFreq); // init high resolution counter + QueryPerformanceCounter(&lAppStart); GetCurrentDirectory(sizeof(szCurrentDirectory), szCurrentDirectory); @@ -1419,20 +1439,25 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nC if (hEventShutdn == NULL) { AbortMessage("Event creation failed."); - DestroyWindow(hWnd); // 02.12.99 cg, bugfix, close DC + DestroyWindow(hWnd); return FALSE; } - nState = 1; // thread starts in an invalid state - nNextState = 1; - hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&WorkerThread, NULL, 0, &lThreadId); + nState = 0; // init state must be <> nNextState + nNextState = 1; // go into invalid state + hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)&WorkerThread, NULL, CREATE_SUSPENDED, &lThreadId); if (hThread == NULL) { CloseHandle(hEventShutdn); // close event handle AbortMessage("Thread creation failed."); - DestroyWindow(hWnd); // 01.12.99 cg, bugfix, close DC + DestroyWindow(hWnd); return FALSE; } + // on multiprocessor machines for QueryPerformanceCounter() + dwAffMask = SetThreadAffinityMask(hThread,1); + _ASSERT(dwAffMask != 0); + ResumeThread(hThread); // start thread + while (nState!=nNextState) Sleep(0); // wait for thread initialized // initialize DDE server if (DdeInitialize(&idDdeInst,(PFNCALLBACK) &DdeCallback, @@ -1440,42 +1465,36 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nC CBF_FAIL_EXECUTES | CBF_FAIL_ADVISES | CBF_SKIP_REGISTRATIONS | CBF_SKIP_UNREGISTRATIONS,0)) { - TerminateThread(hThread, 0); // 01.12.99 cg, bugfix, kill emulation thread - CloseHandle(hEventShutdn); // 01.12.99 cg, bugfix, close event handle + TerminateThread(hThread, 0); // kill emulation thread + CloseHandle(hEventShutdn); // close event handle AbortMessage("Could not initialize server!"); - DestroyWindow(hWnd); // 01.12.99 cg, bugfix, close DC + DestroyWindow(hWnd); return FALSE; } - // 08.11.99 cg, changed, replaced __argc and __argv variable + _ASSERT(hWnd != NULL); + _ASSERT(hWindowDC != NULL); + if (nArgc >= 2) // use decoded parameter line { CHAR szTemp[256] = "Loading "; strcat(szTemp, ppArgv[1]); SetWindowTitle(szTemp); - // 01.12.99 cg, removed, useless and quit with resources open - // if (FlushMessages(&msg)) return msg.wParam; if (OpenDocument(ppArgv[1])) goto start; } - // 08.11.99 cg, end of replacement - // 11.12.99 cg, changed, call function ReadLastDocument(szBufferFilename, sizeof(szBufferFilename)); if (szBufferFilename[0]) { CHAR szTemp[256] = "Loading "; strcat(szTemp, szBufferFilename); SetWindowTitle(szTemp); - // 01.12.99 cg, removed, useless and quit with resources open - // if (FlushMessages(&msg)) return msg.wParam; if (OpenDocument(szBufferFilename)) goto start; } SetWindowTitle("New Document"); - // 01.12.99 cg, removed, useless and quit with resources open - // if (FlushMessages(&msg)) return msg.wParam; if (NewDocument()) { SetWindowTitle("Untitled"); @@ -1485,13 +1504,6 @@ int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nC ResetDocument(); start: - InitializeCriticalSection(&csGDILock); - InitializeCriticalSection(&csKeyLock); - InitializeCriticalSection(&csIOLock); - InitializeCriticalSection(&csT1Lock); - InitializeCriticalSection(&csT2Lock); - InitializeCriticalSection(&csRecvLock); // 24.10.99 cg, moved, init critical section for receive byte - // init clipboard format and name service uCF_HpObj = RegisterClipboardFormat(CF_HPOBJ); hszService = DdeCreateStringHandle(idDdeInst,szAppName,0); @@ -1509,7 +1521,7 @@ start: } } - SwitchToState(2); // 01.12.99 cg, moved from OnDestroy(), exit emulation thread + SwitchToState(2); // exit emulation thread // clean up DDE server DdeNameService(idDdeInst, hszService, NULL, DNS_UNREGISTER); @@ -1517,14 +1529,7 @@ start: DdeFreeStringHandle(idDdeInst, hszTopic); DdeUninitialize(idDdeInst); - DeleteCriticalSection(&csGDILock); - DeleteCriticalSection(&csKeyLock); - DeleteCriticalSection(&csIOLock); - DeleteCriticalSection(&csT1Lock); - DeleteCriticalSection(&csT2Lock); - DeleteCriticalSection(&csRecvLock); // 24.10.99 cg, moved, end of critical section for receive byte - - WriteSettings(); // 01.12.99 cg, moved from OnDestroy() + WriteSettings(); // save emulation settings CloseHandle(hEventShutdn); // close event handle _ASSERT(nState == 2); // emulation thread down? diff --git a/sources/Emu48/EMU48.DSP b/sources/Emu48/EMU48.DSP index 906bd19..26a8027 100644 --- a/sources/Emu48/EMU48.DSP +++ b/sources/Emu48/EMU48.DSP @@ -112,13 +112,6 @@ SOURCE=.\Emu48.c # Begin Source File SOURCE=.\Emu48.rc - -!IF "$(CFG)" == "Emu48 - Win32 Release" - -!ELSEIF "$(CFG)" == "Emu48 - Win32 Debug" - -!ENDIF - # End Source File # Begin Source File @@ -138,6 +131,10 @@ SOURCE=.\files.c # End Source File # Begin Source File +SOURCE=.\i28f160.c +# End Source File +# Begin Source File + SOURCE=.\keyboard.c # End Source File # Begin Source File @@ -191,6 +188,10 @@ SOURCE=.\Emu48.h # End Source File # Begin Source File +SOURCE=.\i28f160.h +# End Source File +# Begin Source File + SOURCE=.\io.h # End Source File # Begin Source File diff --git a/sources/Emu48/EMU48.H b/sources/Emu48/EMU48.H index b63e135..0a84511 100644 --- a/sources/Emu48/EMU48.H +++ b/sources/Emu48/EMU48.H @@ -9,10 +9,10 @@ #include "types.h" // cards status -#define PORT1_PRESENT ((cCurrentRomType=='S')?0x1:0x2) -#define PORT1_WRITE ((cCurrentRomType=='S')?0x4:0x8) -#define PORT2_PRESENT ((cCurrentRomType=='S')?0x2:0x1) -#define PORT2_WRITE ((cCurrentRomType=='S')?0x8:0x4) +#define PORT1_PRESENT ((cCurrentRomType=='S')?P1C:P2C) +#define PORT1_WRITE ((cCurrentRomType=='S')?P1W:P2W) +#define PORT2_PRESENT ((cCurrentRomType=='S')?P2C:P1C) +#define PORT2_WRITE ((cCurrentRomType=='S')?P2W:P1W) #define BINARYHEADER48 "HPHP48-W" #define BINARYHEADER49 "HPHP49-W" @@ -56,28 +56,29 @@ extern CRITICAL_SECTION csKeyLock; extern CRITICAL_SECTION csIOLock; extern CRITICAL_SECTION csT1Lock; extern CRITICAL_SECTION csT2Lock; -extern CRITICAL_SECTION csRecvLock; // 24.10.99 cg, critical section for receive byte -extern INT nArgc; // 08.11.99 cg, no. of command line arguments -extern LPCTSTR *ppArgv; // 08.11.99 cg, command line arguments +extern CRITICAL_SECTION csRecvLock; +extern INT nArgc; +extern LPCTSTR *ppArgv; extern LARGE_INTEGER lFreq; +extern LARGE_INTEGER lAppStart; extern DWORD idDdeInst; extern UINT uCF_HpObj; extern HINSTANCE hApp; extern HWND hWnd; extern HDC hWindowDC; extern BOOL bPort2IsShared; -extern BOOL bAutoSave; // 11.12.99 cg, add declaration -extern BOOL bAutoSaveOnExit; // 11.12.99 cg, add declaration +extern BOOL bAutoSave; +extern BOOL bAutoSaveOnExit; extern BOOL bAlwaysDisplayLog; -// extern UINT uTimer1Period; // 11.12.99 cg, removed old definition -extern HANDLE hThread; // 11.12.99 cg, add declaration -extern DWORD lThreadId; // 11.12.99 cg, add declaration +extern HANDLE hThread; +extern DWORD lThreadId; extern VOID SetWindowTitle(LPSTR szString); +extern VOID CopyItemsToClipboard(HWND hWnd); extern VOID UpdateWindowStatus(VOID); // Settings.c -extern VOID ReadSettings(VOID); // 11.12.99 cg, moved from Emu48.c -extern VOID WriteSettings(VOID); // 11.12.99 cg, moved from Emu48.c +extern VOID ReadSettings(VOID); +extern VOID WriteSettings(VOID); extern VOID ReadLastDocument(LPTSTR szFileName, DWORD nSize); extern VOID WriteLastDocument(LPCTSTR szFilename); @@ -110,7 +111,7 @@ extern VOID ResizeWindow(VOID); extern BOOL bInterrupt; extern UINT nState; extern UINT nNextState; -extern BOOL bRealSpeed; // 11.12.99 cg, new, real speed flag +extern BOOL bRealSpeed; extern BOOL bKeySlow; extern CHIPSET Chipset; extern char szSerialWire[16]; @@ -141,6 +142,7 @@ extern char szPort2Filename[260]; extern LPBYTE pbyRom; extern DWORD dwRomSize; extern char cCurrentRomType; +extern BOOL bRomWriteable; extern LPBYTE pbyPort2; extern BOOL bPort2Writeable; extern BOOL bPort2IsShared; @@ -152,6 +154,7 @@ extern BOOL MapRom(LPCSTR szFilename); extern VOID UnmapRom(VOID); extern BOOL MapPort2(LPCSTR szFilename); extern VOID UnmapPort2(VOID); +extern VOID UpdatePatches(BOOL bPatch); extern BOOL PatchRom(LPCSTR szFilename); extern VOID ResetDocument(VOID); extern BOOL NewDocument(VOID); @@ -181,6 +184,7 @@ extern VOID SetT1(BYTE byValue); // MOps.c extern BOOL ioc_acc; extern BOOL ir_ctrl_acc; +extern BOOL bFlashRomArray; extern BYTE disp; extern LPBYTE RMap[256]; extern LPBYTE WMap[256]; @@ -190,7 +194,7 @@ extern VOID Config(VOID); extern VOID Uncnfg(VOID); extern VOID Reset(VOID); extern VOID C_Eq_Id(VOID); -extern VOID CpuReset(VOID); // 19.03.99 cg, register setting after Cpu Reset +extern VOID CpuReset(VOID); extern BYTE GetLineCounter(VOID); extern VOID Npeek(BYTE *a, DWORD d, UINT s); extern VOID Nread(BYTE *a, DWORD d, UINT s); @@ -231,7 +235,7 @@ extern WORD CommConnect(VOID); extern VOID CommOpen(LPSTR strWirePort,LPSTR strIrPort); extern VOID CommClose(VOID); extern VOID CommSetBaud(VOID); -extern VOID UpdateUSRQ(VOID); // 25.10.99 cg, USRQ handling +extern VOID UpdateUSRQ(VOID); extern VOID CommTransmit(VOID); extern VOID CommReceive(VOID); diff --git a/sources/Emu48/EMU48.RC b/sources/Emu48/EMU48.RC index 9f7774f..99456d5 100644 --- a/sources/Emu48/EMU48.RC +++ b/sources/Emu48/EMU48.RC @@ -32,9 +32,9 @@ BEGIN IDD_BREAKEDIT, DIALOG BEGIN LEFTMARGIN, 5 - RIGHTMARGIN, 105 + RIGHTMARGIN, 106 TOPMARGIN, 5 - BOTTOMMARGIN, 97 + BOTTOMMARGIN, 95 END IDD_ABOUT, DIALOG @@ -50,7 +50,7 @@ BEGIN LEFTMARGIN, 7 RIGHTMARGIN, 160 TOPMARGIN, 4 - BOTTOMMARGIN, 171 + BOTTOMMARGIN, 202 END IDD_CHOOSEKML, DIALOG @@ -80,9 +80,9 @@ BEGIN IDD_DEBUG, DIALOG BEGIN LEFTMARGIN, 5 - RIGHTMARGIN, 273 - TOPMARGIN, 5 - BOTTOMMARGIN, 212 + RIGHTMARGIN, 274 + TOPMARGIN, 2 + BOTTOMMARGIN, 252 END IDD_NEWVALUE, DIALOG @@ -100,6 +100,22 @@ BEGIN TOPMARGIN, 7 BOTTOMMARGIN, 43 END + + IDD_ENTERBREAK, DIALOG + BEGIN + LEFTMARGIN, 8 + RIGHTMARGIN, 149 + TOPMARGIN, 7 + BOTTOMMARGIN, 69 + END + + IDD_INSTRUCTIONS, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 179 + TOPMARGIN, 7 + BOTTOMMARGIN, 162 + END END #endif // APSTUDIO_INVOKED @@ -109,17 +125,17 @@ END // Dialog // -IDD_BREAKEDIT DIALOG DISCARDABLE 0, 0, 110, 102 +IDD_BREAKEDIT DIALOG DISCARDABLE 0, 0, 111, 100 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Edit breakpoints" FONT 8, "Courier New" BEGIN - DEFPUSHBUTTON "OK",IDOK,77,83,28,14 + DEFPUSHBUTTON "OK",IDOK,78,81,28,14 LTEXT "Current breakpoints:",IDC_STATIC_BREAKPOINT,5,5,82,8 - LISTBOX IDC_BREAKEDIT_WND,5,17,100,60,NOT LBS_NOTIFY | LBS_SORT | + LISTBOX IDC_BREAKEDIT_WND,5,17,101,58,NOT LBS_NOTIFY | LBS_SORT | LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP - PUSHBUTTON "&Add",IDC_BREAKEDIT_ADD,5,83,28,14 - PUSHBUTTON "&Delete",IDC_BREAKEDIT_DELETE,41,83,28,14 + PUSHBUTTON "&Add",IDC_BREAKEDIT_ADD,5,81,28,14 + PUSHBUTTON "&Delete",IDC_BREAKEDIT_DELETE,41,81,28,14 END IDD_ABOUT DIALOGEX 0, 0, 261, 160 @@ -130,21 +146,18 @@ BEGIN ICON IDI_EMU48,IDC_STATIC,7,6,20,20,SS_REALSIZEIMAGE, WS_EX_TRANSPARENT LTEXT "",IDC_VERSION,29,6,151,8,NOT WS_GROUP - LTEXT "Copyright © 1999 Sébastien Carlier && Christoph Gießelink", + LTEXT "Copyright © 2000 Sébastien Carlier && Christoph Gießelink", IDC_STATIC,29,18,181,8 DEFPUSHBUTTON "OK",IDOK,215,12,39,14 EDITTEXT IDC_LICENSE,7,33,247,112,ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY END -IDD_SETTINGS DIALOGEX 0, 0, 167, 178 +IDD_SETTINGS DIALOGEX 0, 0, 167, 209 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Settings" FONT 8, "MS Sans Serif", 0, 0, 0x1 BEGIN - GROUPBOX "General",IDC_STATIC,7,4,153,63 - GROUPBOX "Memory Cards",IDC_STATIC,7,70,153,50 - GROUPBOX "Serial Ports",IDC_STATIC,7,124,153,27 CONTROL "Authentic Calculator Speed",IDC_REALSPEED,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,13,13,104,10 CONTROL "Automatically Save Files",IDC_AUTOSAVE,"Button", @@ -154,22 +167,30 @@ BEGIN CONTROL "Always display KML Compilation Result", IDC_ALWAYSDISPLOG,"Button",BS_AUTOCHECKBOX | WS_TABSTOP, 13,52,136,10 + GROUPBOX "General",IDC_STATIC,7,4,153,63 + CONTROL "HP Mnemonics",IDC_DISASM_HP,"Button",BS_AUTORADIOBUTTON | + WS_GROUP | WS_TABSTOP,13,81,65,11 + CONTROL "Class Mnemonics",IDC_DISASM_CLASS,"Button", + BS_AUTORADIOBUTTON,84,81,70,11 + GROUPBOX "Disassembler",IDC_DISASM_MNEMONICS,7,70,153,28 CONTROL "Port 1 is Plugged",IDC_PORT1EN,"Button",BS_AUTOCHECKBOX | - WS_TABSTOP,13,79,67,10 + WS_TABSTOP,13,110,67,10 CONTROL "Port 1 is Writeable",IDC_PORT1WR,"Button", - BS_AUTOCHECKBOX | WS_TABSTOP,84,79,69,10 + BS_AUTOCHECKBOX | WS_TABSTOP,84,110,69,10 CONTROL "Port 2 is Shared",IDC_PORT2ISSHARED,"Button", - BS_AUTOCHECKBOX | WS_TABSTOP,13,92,65,10 - LTEXT "Port 2 File :",IDC_STATIC,13,105,37,8 - EDITTEXT IDC_PORT2,51,103,104,12,ES_AUTOHSCROLL - LTEXT "Wire:",IDC_STATIC,13,136,17,8 - COMBOBOX IDC_WIRE,31,134,48,42,CBS_DROPDOWNLIST | WS_VSCROLL | + BS_AUTOCHECKBOX | WS_TABSTOP,13,123,65,10 + LTEXT "Port 2 File :",IDC_STATIC,13,136,37,8 + EDITTEXT IDC_PORT2,51,134,104,12,ES_AUTOHSCROLL + GROUPBOX "Memory Cards",IDC_STATIC,7,101,153,50 + LTEXT "Wire:",IDC_STATIC,13,166,17,8 + COMBOBOX IDC_WIRE,31,164,48,42,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP,WS_EX_LEFTSCROLLBAR - LTEXT "IR:",IDC_STATIC,89,136,9,8 - COMBOBOX IDC_IR,107,134,48,43,CBS_DROPDOWNLIST | WS_VSCROLL | + LTEXT "IR:",IDC_STATIC,89,166,9,8 + COMBOBOX IDC_IR,107,164,48,43,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - DEFPUSHBUTTON "&Ok",IDOK,9,157,50,14 - PUSHBUTTON "&Cancel",IDCANCEL,107,157,50,14 + GROUPBOX "Serial Ports",IDC_STATIC,7,154,153,27 + DEFPUSHBUTTON "&Ok",IDOK,9,188,50,14 + PUSHBUTTON "&Cancel",IDCANCEL,107,188,50,14 END IDD_CHOOSEKML DIALOG DISCARDABLE 0, 0, 186, 66 @@ -215,90 +236,111 @@ BEGIN PUSHBUTTON "&Next Address",IDC_DISASM_NEXT,99,144,47,14 PUSHBUTTON "&Copy Data",IDC_DISASM_COPY,150,144,47,14 PUSHBUTTON "Cancel",IDCANCEL,201,144,47,14 - GROUPBOX "Module",IDC_DISASM_MODULE,7,5,166,26 + GROUPBOX "Module",IDC_DISASM_MODULE,7,5,241,26 CONTROL "Map",IDC_DISASM_MAP,"Button",BS_AUTORADIOBUTTON | - WS_GROUP | WS_TABSTOP,14,16,28,10 - CONTROL "ROM",IDC_DISASM_ROM,"Button",BS_AUTORADIOBUTTON,45,16, - 28,10 - CONTROL "RAM",IDC_DISASM_RAM,"Button",BS_AUTORADIOBUTTON,76,16, - 28,10 + WS_GROUP | WS_TABSTOP,14,16,37,10 + CONTROL "ROM",IDC_DISASM_ROM,"Button",BS_AUTORADIOBUTTON,61,16, + 37,10 + CONTROL "RAM",IDC_DISASM_RAM,"Button",BS_AUTORADIOBUTTON,108,16, + 37,10 CONTROL "Port 1",IDC_DISASM_PORT1,"Button",BS_AUTORADIOBUTTON, - 107,16,28,10 + 155,16,37,10 CONTROL "Port 2",IDC_DISASM_PORT2,"Button",BS_AUTORADIOBUTTON, - 138,16,28,10 - GROUPBOX "Mnemonics",IDC_DISASM_MNEMONICS,180,5,68,26 - CONTROL "HP",IDC_DISASM_HP,"Button",BS_AUTORADIOBUTTON | - WS_GROUP | WS_TABSTOP,187,16,24,10 - CONTROL "Class",IDC_DISASM_CLASS,"Button",BS_AUTORADIOBUTTON,215, - 16,31,10 + 202,16,37,10 LISTBOX IDC_DISASM_WIN,7,37,241,100,NOT LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP,WS_EX_NOPARENTNOTIFY END -IDD_DEBUG DIALOGEX 0, 0, 278, 217 +IDD_DEBUG DIALOGEX 0, 0, 279, 254 STYLE WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Debugger" MENU IDR_DEBUG FONT 8, "Courier New", 0, 0, 0x1 BEGIN - LISTBOX IDC_DEBUG_CODE,11,15,165,122,NOT LBS_NOTIFY | + LISTBOX IDC_DEBUG_CODE,11,12,165,122,NOT LBS_NOTIFY | LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT | WS_TABSTOP - GROUPBOX "Code",IDC_STATIC_CODE,5,5,177,138 - LTEXT "A= 0000000000000000",IDC_REG_A,192,15,77,8 - LTEXT "B= 0000000000000000",IDC_REG_B,192,22,77,8 - LTEXT "C= 0000000000000000",IDC_REG_C,192,29,77,8 - LTEXT "D= 0000000000000000",IDC_REG_D,192,36,77,8 - LTEXT "R0=0000000000000000",IDC_REG_R0,192,46,77,8 - LTEXT "R1=0000000000000000",IDC_REG_R1,192,53,77,8 - LTEXT "R2=0000000000000000",IDC_REG_R2,192,60,77,8 - LTEXT "R3=0000000000000000",IDC_REG_R3,192,67,77,8 - LTEXT "R4=0000000000000000",IDC_REG_R4,192,74,77,8 - LTEXT "D0=00000",IDC_REG_D0,192,85,33,8 - LTEXT "D1=00000",IDC_REG_D1,236,85,33,8 - LTEXT "P=0",IDC_REG_P,192,96,13,8 - LTEXT "PC=00000",IDC_REG_PC,236,96,33,8 - LTEXT "OUT=000",IDC_REG_OUT,192,107,29,8 - LTEXT "IN=0000",IDC_REG_IN,240,107,29,8 - LTEXT "ST=0000",IDC_REG_ST,192,118,29,8 - LTEXT "CY=0",IDC_REG_CY,224,118,17,8 - LTEXT "Mode=H",IDC_REG_MODE,244,118,25,8 - LTEXT "MP=0",IDC_REG_MP,192,128,17,8 - LTEXT "SR=0",IDC_REG_SR,212,128,17,8 - LTEXT "SB=0",IDC_REG_SB,232,128,17,8 - LTEXT "XM=0",IDC_REG_XM,252,128,17,8 - GROUPBOX "Registers",IDC_STATIC_REGISTERS,186,5,87,138 - CONTROL "",IDC_DEBUG_MEM,"Static",SS_WHITERECT | WS_GROUP,11,154, + GROUPBOX "Code",IDC_STATIC_CODE,5,2,177,138 + LTEXT "A= 0000000000000000",IDC_REG_A,192,12,77,8 + LTEXT "B= 0000000000000000",IDC_REG_B,192,19,77,8 + LTEXT "C= 0000000000000000",IDC_REG_C,192,26,77,8 + LTEXT "D= 0000000000000000",IDC_REG_D,192,33,77,8 + LTEXT "R0=0000000000000000",IDC_REG_R0,192,43,77,8 + LTEXT "R1=0000000000000000",IDC_REG_R1,192,50,77,8 + LTEXT "R2=0000000000000000",IDC_REG_R2,192,57,77,8 + LTEXT "R3=0000000000000000",IDC_REG_R3,192,64,77,8 + LTEXT "R4=0000000000000000",IDC_REG_R4,192,71,77,8 + LTEXT "D0=00000",IDC_REG_D0,192,82,33,8 + LTEXT "D1=00000",IDC_REG_D1,236,82,33,8 + LTEXT "P=0",IDC_REG_P,192,93,13,8 + LTEXT "PC=00000",IDC_REG_PC,236,93,33,8 + LTEXT "OUT=000",IDC_REG_OUT,192,104,29,8 + LTEXT "IN=0000",IDC_REG_IN,240,104,29,8 + LTEXT "ST=0000",IDC_REG_ST,192,115,29,8 + LTEXT "CY=0",IDC_REG_CY,224,115,17,8 + LTEXT "Mode=H",IDC_REG_MODE,244,115,25,8 + LTEXT "MP=0",IDC_REG_MP,192,125,17,8 + LTEXT "SR=0",IDC_REG_SR,212,125,17,8 + LTEXT "SB=0",IDC_REG_SB,232,125,17,8 + LTEXT "XM=0",IDC_REG_XM,252,125,17,8 + GROUPBOX "Registers",IDC_STATIC_REGISTERS,187,2,87,138 + CONTROL "",IDC_DEBUG_MEM,"Static",SS_WHITERECT | WS_GROUP,11,151, 165,52,WS_EX_CLIENTEDGE - LISTBOX IDC_DEBUG_MEM_ADDR,12,156,25,48,NOT LBS_NOTIFY | + LISTBOX IDC_DEBUG_MEM_ADDR,12,153,25,48,NOT LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | LBS_NOSEL | WS_DISABLED | NOT WS_BORDER - LISTBOX IDC_DEBUG_MEM_COL0,40,156,11,48,LBS_NOINTEGRALHEIGHT | + LISTBOX IDC_DEBUG_MEM_COL0,40,153,11,48,LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT | NOT WS_BORDER | WS_TABSTOP - LISTBOX IDC_DEBUG_MEM_COL1,52,156,11,48,LBS_NOINTEGRALHEIGHT | + LISTBOX IDC_DEBUG_MEM_COL1,52,153,11,48,LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT | NOT WS_BORDER - LISTBOX IDC_DEBUG_MEM_COL2,64,156,11,48,LBS_NOINTEGRALHEIGHT | + LISTBOX IDC_DEBUG_MEM_COL2,64,153,11,48,LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT | NOT WS_BORDER - LISTBOX IDC_DEBUG_MEM_COL3,76,156,11,48,LBS_NOINTEGRALHEIGHT | + LISTBOX IDC_DEBUG_MEM_COL3,76,153,11,48,LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT | NOT WS_BORDER - LISTBOX IDC_DEBUG_MEM_COL4,88,156,11,48,LBS_NOINTEGRALHEIGHT | + LISTBOX IDC_DEBUG_MEM_COL4,88,153,11,48,LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT | NOT WS_BORDER - LISTBOX IDC_DEBUG_MEM_COL5,100,156,11,48,LBS_NOINTEGRALHEIGHT | + LISTBOX IDC_DEBUG_MEM_COL5,100,153,11,48,LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT | NOT WS_BORDER - LISTBOX IDC_DEBUG_MEM_COL6,112,156,11,48,LBS_NOINTEGRALHEIGHT | + LISTBOX IDC_DEBUG_MEM_COL6,112,153,11,48,LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT | NOT WS_BORDER - LISTBOX IDC_DEBUG_MEM_COL7,124,156,11,48,LBS_NOINTEGRALHEIGHT | + LISTBOX IDC_DEBUG_MEM_COL7,124,153,11,48,LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT | NOT WS_BORDER - LISTBOX IDC_DEBUG_MEM_TEXT,139,156,35,48,NOT LBS_NOTIFY | + LISTBOX IDC_DEBUG_MEM_TEXT,139,153,35,48,NOT LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | LBS_NOSEL | WS_DISABLED | NOT WS_BORDER - GROUPBOX "Memory",IDC_STATIC_MEMORY,5,144,177,68 - LISTBOX IDC_DEBUG_STACK,192,154,75,52,NOT LBS_NOTIFY | + GROUPBOX "Memory",IDC_STATIC_MEMORY,5,141,177,68 + LISTBOX IDC_DEBUG_STACK,192,151,76,52,NOT LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | LBS_WANTKEYBOARDINPUT | WS_VSCROLL | WS_TABSTOP - GROUPBOX "Stack",IDC_STATIC_STACK,186,144,87,68 + GROUPBOX "Stack",IDC_STATIC_STACK,187,141,87,68 + LTEXT "Size Mask",IDC_STATIC,11,228,37,8 + LTEXT "Address",IDC_STATIC,11,236,29,8 + CTEXT "I/O",IDC_STATIC,55,220,21,8 + CTEXT "NCE2",IDC_STATIC,80,220,21,8 + CTEXT "CE1",IDC_STATIC,105,220,21,8 + CTEXT "CE2",IDC_STATIC,130,220,21,8 + CTEXT "NCE3",IDC_STATIC,155,220,21,8 + CTEXT "-----",IDC_MMU_IO_S,55,228,21,8 + CTEXT "-----",IDC_MMU_NCE2_S,80,228,21,8 + CTEXT "-----",IDC_MMU_CE1_S,105,228,21,8 + CTEXT "-----",IDC_MMU_CE2_S,130,228,21,8 + CTEXT "-----",IDC_MMU_NCE3_S,155,228,21,8 + CTEXT "-----",IDC_MMU_IO_A,55,236,21,8 + CTEXT "-----",IDC_MMU_CE1_A,105,236,21,8 + CTEXT "-----",IDC_MMU_CE2_A,130,236,21,8 + CTEXT "-----",IDC_MMU_NCE2_A,80,236,21,8 + CTEXT "-----",IDC_MMU_NCE3_A,155,236,21,8 + GROUPBOX "MMU",IDC_STATIC_MMU,5,210,177,39 + LTEXT "Interrupts =",IDC_STATIC,193,220,61,8 + LTEXT "Keyboard Scan =",IDC_STATIC,193,228,61,8 + LTEXT "Bank Switcher =",IDC_MISC_BS_TXT,193,236,61,8, + WS_DISABLED + LTEXT "",IDC_MISC_INT,256,220,13,8 + LTEXT "",IDC_MISC_KEY,256,228,13,8 + LTEXT "00",IDC_MISC_BS,256,236,13,8,WS_DISABLED + GROUPBOX "Miscellaneous",IDC_STATIC_MISC,187,210,87,39 END IDD_NEWVALUE DIALOG DISCARDABLE 0, 0, 175, 50 @@ -323,6 +365,40 @@ BEGIN PUSHBUTTON "Cancel",IDCANCEL,92,29,50,14 END +IDD_ENTERBREAK DIALOG DISCARDABLE 0, 0, 156, 76 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Enter breakpoint" +FONT 8, "MS Sans Serif" +BEGIN + LTEXT "Enter breakpoint (hexdezimal):",IDC_STATIC,8,9,96,8 + EDITTEXT IDC_ENTERADR,110,7,39,12,ES_AUTOHSCROLL + CONTROL "&Code",IDC_BPCODE,"Button",BS_AUTORADIOBUTTON | + WS_GROUP | WS_TABSTOP,17,24,33,10 + CONTROL "Memory &Access",IDC_BPACCESS,"Button", + BS_AUTORADIOBUTTON,79,24,63,10 + CONTROL "Memory &Read",IDC_BPREAD,"Button",BS_AUTORADIOBUTTON,17, + 38,60,10 + CONTROL "Memory &Write",IDC_BPWRITE,"Button",BS_AUTORADIOBUTTON, + 79,38,59,10 + DEFPUSHBUTTON "OK",IDOK,14,55,50,14 + PUSHBUTTON "Cancel",IDCANCEL,92,55,50,14 +END + +IDD_INSTRUCTIONS DIALOGEX 0, 0, 186, 169 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Last Instructions" +FONT 8, "Courier New", 0, 0, 0x1 +BEGIN + LTEXT "Instructions (disassembly maybe incorrect):", + IDC_INSTR_TEXT,7,7,173,8 + LISTBOX IDC_INSTR_CODE,7,18,172,122,NOT LBS_NOTIFY | + LBS_NOINTEGRALHEIGHT | LBS_EXTENDEDSEL | WS_VSCROLL | + WS_TABSTOP,WS_EX_NOPARENTNOTIFY + PUSHBUTTON "&Copy Data",IDC_INSTR_COPY,7,148,50,14 + PUSHBUTTON "C&lear Data",IDC_INSTR_CLEAR,68,148,50,14 + DEFPUSHBUTTON "Cancel",IDCANCEL,129,148,50,14 +END + #ifndef _MAC ///////////////////////////////////////////////////////////////////////////// @@ -331,8 +407,8 @@ END // VS_VERSION_INFO VERSIONINFO - FILEVERSION 1,1,5,0 - PRODUCTVERSION 1,1,5,0 + FILEVERSION 1,2,0,0 + PRODUCTVERSION 1,2,0,0 FILEFLAGSMASK 0x3fL #ifdef _DEBUG FILEFLAGS 0x21L @@ -347,15 +423,15 @@ BEGIN BEGIN BLOCK "00000000" BEGIN - VALUE "CompanyName", "Sebastien Carlier\0" - VALUE "FileDescription", "HP38/48/49 Emulator\0" - VALUE "FileVersion", "1, 1, 5, 0\0" + VALUE "CompanyName", "Sebastien Carlier & Christoph Gießelink\0" + VALUE "FileDescription", "HP38/39/40/48/49 Emulator\0" + VALUE "FileVersion", "1, 2, 0, 0\0" VALUE "InternalName", "Emu48\0" - VALUE "LegalCopyright", "Copyright © 1999\0" + VALUE "LegalCopyright", "Copyright © 2000\0" VALUE "OriginalFilename", "Emu48.exe\0" VALUE "ProductName", "Emu48\0" - VALUE "ProductVersion", "1, 1, 5, 0\0" - VALUE "SpecialBuild", "Service Pack 15, Christoph Gießelink\0" + VALUE "ProductVersion", "1, 2, 0, 0\0" + VALUE "SpecialBuild", "Service Pack 20, Christoph Gießelink\0" END END BLOCK "VarFileInfo" @@ -445,6 +521,16 @@ BEGIN MENUITEM "&Clear all breakpoints", ID_BREAKPOINTS_CLEARALL MENUITEM SEPARATOR MENUITEM "&NOP3 code breakpoints", ID_BREAKPOINTS_NOP3 + MENUITEM SEPARATOR + MENUITEM "&RPL breakpoints", ID_BREAKPOINTS_RPL + END + POPUP "I&nterrupts" + BEGIN + MENUITEM "&Step Over Interrupts", ID_INTR_STEPOVERINT + END + POPUP "&Info" + BEGIN + MENUITEM "&Last Instructions...", ID_INFO_LASTINSTRUCTIONS END END diff --git a/sources/Emu48/ENGINE.C b/sources/Emu48/ENGINE.C index 00c9bfe..160b2e9 100644 --- a/sources/Emu48/ENGINE.C +++ b/sources/Emu48/ENGINE.C @@ -9,7 +9,7 @@ #include "pch.h" #include "Emu48.h" #include "Opcodes.h" -#include "io.h" // 24.10.99 cg, renamed from Serial.h +#include "io.h" #include "debugger.h" #define SAMPLE 16384 // speed adjust sample frequency @@ -17,7 +17,7 @@ BOOL bInterrupt = FALSE; UINT nState = 1; UINT nNextState = 0; -BOOL bRealSpeed = FALSE; // 11.12.99 cg, moved from Emu48.c +BOOL bRealSpeed = FALSE; BOOL bKeySlow = FALSE; // slow down for key emulation CHIPSET Chipset; @@ -28,7 +28,10 @@ DWORD dwSXCycles = 82; // SX cpu cycles in interval DWORD dwGXCycles = 123; // GX cpu cycles in interval static BOOL bCommInit = FALSE; // COM port not open -static BOOL bCpuSlow = FALSE; // 11.12.99 cg, renamed, enable/disable real speed +static BOOL bCpuSlow = FALSE; // enable/disable real speed + +static DWORD dwEDbgT2 = 0; // debugger timer2 emulation +static DWORD dwEDbgCycles = 0; // debugger cycle counter static DWORD dwOldCyc; // cpu cycles at last event static DWORD dwSpeedRef; // timer value at last event @@ -36,6 +39,151 @@ static DWORD dwTickRef; // sample timer ticks #include "Ops.h" +// save last instruction in circular instruction buffer +static __inline VOID SaveInstrAddr(DWORD dwAddr) +{ + if (pdwInstrArray) // circular buffer allocated + { + pdwInstrArray[wInstrWp] = dwAddr; + wInstrWp = (wInstrWp + 1) % wInstrSize; + if (wInstrWp == wInstrRp) + wInstrRp = (wInstrRp + 1) % wInstrSize; + } + return; +} + +static __inline VOID Debugger(VOID) // debugger part +{ + LARGE_INTEGER lDummyInt; // sample timer ticks + BOOL bStopEmulation; + BOOL bRplBreak = FALSE; // flag for RPL breakpoint + + LPBYTE I = FASTPTR(Chipset.pc); // get opcode stream + + SaveInstrAddr(Chipset.pc); // save pc in last instruction buffer + + // check for code breakpoints + bStopEmulation = CheckBreakpoint(Chipset.pc, 1, BP_EXEC); + + // check for memory breakpoints, opcode #14x or #15x + if (I[0] == 0x1 && (I[1] == 0x4 || I[1] == 0x5)) + { + DWORD dwData = (I[2] & 0x1) ? Chipset.d1 : Chipset.d0; + UINT nType = (I[2] & 0x2) ? BP_READ : BP_WRITE; + + DWORD dwRange; + if (I[1] == 0x4) // B,A + { + dwRange = (I[2] & 0x8) ? 2 : 5; + } + else // P,WP,XS,X,S,M,W or number of nibbles + { + if (I[2] & 0x8) // number of nibbles + { + dwRange = I[3]+1; + } + else // P,WP,XS,X,S,M,W + { + dwData += F_s[I[3]]; + dwRange = F_l[I[3]]; + } + } + #if defined DEBUG_DEBUGGER + { + char buffer[256]; + wsprintf(buffer,"Memory breakpoint %.5lx, %u\n",dwData,dwRange); + OutputDebugString(buffer); + } + #endif + bStopEmulation |= CheckBreakpoint(dwData, dwRange, nType); + } + + // NOP3, opcode #420 (GOC) + if (bDbgNOP3 && I[0] == 0x4 && I[1] == 0x2 && I[2] == 0x0) + bStopEmulation = TRUE; + + // RPL breakpoints, PC=(A), opcode #808C or PC=(C), opcode #808E + if (bDbgRPL && I[0] == 0x8 && I[1] == 0x0 && I[2] == 0x8 && (I[3] == 0xC || I[3] == 0xE )) + { + BYTE byRplPtr[5]; + + // A=DAT0 A, D0=D0+ 5, PC=(A) + // C=DAT0 A, D0=D0+ 5, PC=(C) + Npeek(byRplPtr,Chipset.d0-5,5); + if (memcmp(byRplPtr,(I[3] == 0xC) ? Chipset.A : Chipset.C,5) == 0) + { + bRplBreak = TRUE; + bStopEmulation = TRUE; + } + } + + // step over interrupt execution + if (bDbgSkipInt && !bStopEmulation && !Chipset.inte) + return; + + // check for step into + bStopEmulation |= (nDbgState == DBG_STEPINTO); + + // check for step over + bStopEmulation |= (nDbgState == DBG_STEPOVER) && dwDbgRstkp == Chipset.rstkp; + + // check for step out, something was popped from hardware stack + if (nDbgState == DBG_STEPOUT && dwDbgRstkp == Chipset.rstkp) + { + _ASSERT(bStopEmulation == FALSE); + if ((bStopEmulation = (Chipset.pc == dwDbgRstk)) == FALSE) + { + // it was C=RSTK, check for next object popped from hardware stack + dwDbgRstkp = (Chipset.rstkp-1)&7; + dwDbgRstk = Chipset.rstk[dwDbgRstkp]; + } + } + + if (bStopEmulation) // stop condition + { + StopTimers(); // hold timer values when emulator is stopped + if (Chipset.IORam[TIMER2_CTRL]&RUN) // check if timer running + { + if (dwEDbgT2 == Chipset.t2) + { + // cpu cycles for one timer2 tick elapsed + if ((DWORD) (Chipset.cycles & 0xFFFFFFFF) - dwEDbgCycles + >= (SAMPLE / 8192) * (DWORD) T2CYCLES) + { + --Chipset.t2; + // adjust cycles reference + dwEDbgCycles += (SAMPLE / 8192) * T2CYCLES; + } + } + else // new timer2 value + { + // new cycle reference + dwEDbgCycles = (DWORD) (Chipset.cycles & 0xFFFFFFFF); + } + + // check rising edge of Bit 8 of timer2 + if ((dwEDbgT2 & 0x100) == 0 && (Chipset.t2 & 0x100) != 0) + Chipset.t1 = (Chipset.t1 - 1) & 0xF; + } + dwEDbgT2 = Chipset.t2; // timer2 check reference value + + // OutputDebugString("Emulator stopped...\n"); + NotifyDebugger(bRplBreak); + WaitForSingleObject(hEventDebug,INFINITE); + // OutputDebugString("Emulator running...\n"); + + StartTimers(); // continue timers + + Chipset.Shutdn = FALSE; + Chipset.bShutdnWake = FALSE; + + // init slow down part + dwOldCyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF); + QueryPerformanceCounter(&lDummyInt); + dwSpeedRef = lDummyInt.LowPart; + } +} + static __inline VOID CheckDisp(BOOL bSync) { if (disp == 0) return; // no display update need @@ -83,11 +231,12 @@ static __inline VOID CheckSerial(VOID) static __inline VOID AdjustSpeed(VOID) // adjust emulation speed { - if (bCpuSlow || bKeySlow) // 11.12.99 cg, changed, emulation slow down + if (bCpuSlow || bKeySlow) // emulation slow down { + DWORD dwCycles,dwTicks; + // cycles elapsed for next check - // 22.11.99 cg, changed, DWORD casting of Chipset.cycles - if ((DWORD) (Chipset.cycles & 0xFFFFFFFF)-dwOldCyc >= (DWORD) T2CYCLES) + if ((dwCycles = (DWORD) (Chipset.cycles & 0xFFFFFFFF)-dwOldCyc) >= (DWORD) T2CYCLES) { LARGE_INTEGER lAct; do @@ -95,10 +244,22 @@ static __inline VOID AdjustSpeed(VOID) // adjust emulation speed BOOL bErr = QueryPerformanceCounter(&lAct); _ASSERT(bErr); // no high-resolution performance counter } - while(lAct.LowPart-dwSpeedRef <= dwTickRef); + while((dwTicks = lAct.LowPart-dwSpeedRef) <= dwTickRef); - dwOldCyc += T2CYCLES; // adjust cycles reference - dwSpeedRef += dwTickRef; // adjust reference time + // workaround for QueryPerformanceCounter() in Win2k, + // if last command sequence took over 50ms -> synchronize + if(dwTicks > 819 * dwTickRef) // time for last commands > 50ms (819 / 16384Hz) + { + // new synchronizing + dwOldCyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF); + QueryPerformanceCounter(&lAct); // get timer ticks + dwSpeedRef = lAct.LowPart; // save reference time + } + else + { + dwOldCyc += T2CYCLES; // adjust cycles reference + dwSpeedRef += dwTickRef; // adjust reference time + } } } return; @@ -109,7 +270,7 @@ VOID AdjKeySpeed(VOID) // slow down key repeat WORD i; BOOL bKey; - if (bCpuSlow) return; // 11.12.99 cg, changed, no need to slow down + if (bCpuSlow) return; // no need to slow down bKey = FALSE; // search for a pressed key for (i = 0;i < sizeof(Chipset.Keyboard_Row) / sizeof(Chipset.Keyboard_Row[0]) && !bKey;++i) @@ -119,7 +280,6 @@ VOID AdjKeySpeed(VOID) // slow down key repeat { LARGE_INTEGER lTime; // sample timer ticks // save reference cycles - // 22.11.99 cg, changed, DWORD casting dwOldCyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF); QueryPerformanceCounter(&lTime); // get timer ticks dwSpeedRef = lTime.LowPart; // save reference time @@ -134,17 +294,15 @@ VOID SetSpeed(BOOL bAdjust) // set emulation speed { LARGE_INTEGER lTime; // sample timer ticks // save reference cycles - // 22.11.99 cg, changed, DWORD casting dwOldCyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF); QueryPerformanceCounter(&lTime); // get timer ticks dwSpeedRef = lTime.LowPart; // save reference time } - bCpuSlow = bAdjust; // 11.12.99 cg, changed, save emulation speed + bCpuSlow = bAdjust; // save emulation speed } VOID UpdateKdnBit(VOID) // update KDN bit { - // 22.11.99 cg, changed, DWORD casting of Chipset.cycles if (Chipset.intk && (DWORD) (Chipset.cycles & 0xFFFFFFFFF) - Chipset.dwKdnCycles > (DWORD) T2CYCLES * 16) IOBit(0x19,8,Chipset.in != 0); } @@ -214,7 +372,8 @@ UINT SwitchToState(UINT nNewState) { case 0: // -> Run nNextState = 0; - bInterrupt = FALSE; + // don't enter opcode loop on interrupt request + bInterrupt = Chipset.Shutdn || Chipset.SoftInt; SetEvent(hEventShutdn); while (nState!=nNextState) Sleep(0); UpdateWindowStatus(); @@ -297,13 +456,26 @@ loop: if (bPort2Writeable) // is card writeable Chipset.cards_status |= PORT2_WRITE; } + // card detection off and timer running + if ((Chipset.IORam[CARDCTL] & ECDT) == 0 && (Chipset.IORam[TIMER2_CTRL] & RUN) != 0) + { + BOOL bNINT2 = Chipset.IORam[SRQ1] == 0 && (Chipset.IORam[SRQ2] & LSRQ) == 0; + BOOL bNINT = (Chipset.IORam[CARDCTL] & SMP) == 0; + + // state of CDT2 + bNINT2 = bNINT2 && (Chipset.cards_status & (P2W|P2C)) != P2C; + // state of CDT1 + bNINT = bNINT && (Chipset.cards_status & (P1W|P1C)) != P1C; + + IOBit(SRQ2,NINT2,bNINT2); + IOBit(SRQ2,NINT,bNINT); + } RomSwitch(Chipset.Bank_FF); // select HP49G ROM bank and update memory mapping UpdateDisplayPointers(); UpdateMainDisplay(); UpdateMenuDisplay(); UpdateAnnunciators(); // init speed reference - // 22.11.99 cg, changed, DWORD casting dwOldCyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF); QueryPerformanceCounter(&lDummyInt); dwSpeedRef = lDummyInt.LowPart; @@ -313,65 +485,9 @@ loop: PCHANGED; while (!bInterrupt) { - LPBYTE I = FASTPTR(Chipset.pc); + if (bDbgEnable) Debugger(); // debugger active - if (bDbgEnable) // debugger active - { - BOOL bStopEmulation; - - // 13.11.99 cg, changed, check for step into - bStopEmulation = (nDbgState == DBG_STEPINTO); - - // check for step over - bStopEmulation |= (nDbgState == DBG_STEPOVER) && dwDbgRstkp == Chipset.rstkp; - - // 13.11.99 cg, new, check for step out, something was popped from hardware stack - if (nDbgState == DBG_STEPOUT && dwDbgRstkp == Chipset.rstkp) - { - _ASSERT(bStopEmulation == FALSE); - if ((bStopEmulation = (Chipset.pc == dwDbgRstk)) == FALSE) - { - // it was C=RSTK, check for next object popped from hardware stack - dwDbgRstkp = (Chipset.rstkp-1)&7; - dwDbgRstk = Chipset.rstk[dwDbgRstkp]; - } - } - // 13.11.99 cg, end of step out implementation - - // check for breakpoints - bStopEmulation |= CheckBreakpoint(Chipset.pc); - - // NOP3, opcode #420 (GOC) - if (bDbgNOP3 && I[0] == 0x4 && I[1] == 0x2 && I[2] == 0x0) - bStopEmulation = TRUE; - - if (bStopEmulation) // stop condition - { - StopTimers(); // hold timer values when emulator is stopped - - OutputDebugString("Emulator stopped...\n"); - NotifyDebugger(); // 10.11.99 cg, changed, update registers - WaitForSingleObject(hEventDebug,INFINITE); - OutputDebugString("Emulator running...\n"); - - - // @todo add timer emulation - - - StartTimers(); // continue timers - - Chipset.Shutdn = FALSE; - Chipset.bShutdnWake = FALSE; - - // init slow down part - // 22.11.99 cg, changed, DWORD casting - dwOldCyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF); - QueryPerformanceCounter(&lDummyInt); - dwSpeedRef = lDummyInt.LowPart; - } - } - - EvalOpcode(I); + EvalOpcode(FASTPTR(Chipset.pc)); // execute opcode CheckDisp(!Chipset.Shutdn); // check for display update CheckSerial(); // serial support @@ -382,14 +498,16 @@ loop: // enter SHUTDN handler only in RUN mode if (Chipset.Shutdn && !(nDbgState == DBG_STEPINTO || nDbgState == DBG_STEPOVER)) { - WaitForSingleObject(hEventShutdn,INFINITE); + if (!Chipset.SoftInt) // ignore SHUTDN on interrupt request + WaitForSingleObject(hEventShutdn,INFINITE); + else + Chipset.bShutdnWake = TRUE; // waked by interrupt if (Chipset.bShutdnWake) // waked up by timer, keyboard or serial { Chipset.bShutdnWake = FALSE; Chipset.Shutdn = FALSE; // init speed reference - // 22.11.99 cg, changed, DWORD casting dwOldCyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF); QueryPerformanceCounter(&lDummyInt); dwSpeedRef = lDummyInt.LowPart; @@ -405,12 +523,11 @@ loop: Chipset.pc = 0xf; } } - if (nNextState != 0) - { - StopTimers(); - Chipset.cards_status &= (Chipset.type=='S')?0x5:0xA; - } } + _ASSERT(nNextState != 0); + + StopTimers(); + while (nNextState == 3) // go into sleep state { nState = 3; // in sleep state diff --git a/sources/Emu48/EXTERNAL.C b/sources/Emu48/EXTERNAL.C index 9468714..2a7a68b 100644 --- a/sources/Emu48/EXTERNAL.C +++ b/sources/Emu48/EXTERNAL.C @@ -13,15 +13,18 @@ //#F0E4F #706D2 #80850 #80F0F =SFLAG53_56 // memory address for flags -53 to -56 -#define SFLAG53_56 ( (cCurrentRomType=='A') \ - ? 0xF0E4F \ - : ( (cCurrentRomType!='X') \ - ? ( (cCurrentRomType=='S') \ - ? 0x706D2 \ - : 0x80850 \ - ) \ - : 0x80F0F \ - ) \ +#define SFLAG53_56 ( (cCurrentRomType=='6') \ + ? 0xE0E4F \ + : ( (cCurrentRomType=='A') \ + ? 0xF0E4F \ + : ( (cCurrentRomType!='X') \ + ? ( (cCurrentRomType=='S') \ + ? 0x706D2 \ + : 0x80850 \ + ) \ + : 0x80F0F \ + ) \ + ) \ ) static __inline VOID Return(CHIPSET* w) diff --git a/sources/Emu48/FILES.C b/sources/Emu48/FILES.C index 8c2883d..8789ba7 100644 --- a/sources/Emu48/FILES.C +++ b/sources/Emu48/FILES.C @@ -8,7 +8,9 @@ */ #include "pch.h" #include "Emu48.h" +#include "io.h" // I/O register definitions #include "kml.h" +#include "i28f160.h" // flash support char szEmu48Directory[260]; char szCurrentDirectory[260]; @@ -24,6 +26,7 @@ static HANDLE hRomFile = NULL; static HANDLE hRomMap = NULL; DWORD dwRomSize = 0; char cCurrentRomType = 0; +BOOL bRomWriteable = FALSE; // flag if ROM writeable static HANDLE hPort2File = NULL; static HANDLE hPort2Map = NULL; @@ -33,7 +36,7 @@ BOOL bPort2IsShared = FALSE; DWORD dwPort2Size = 0; // size of mapped port2 DWORD dwPort2Mask = 0; -// 01.12.99 cg, new, HP38G signature +// document signatures static BYTE pbySignatureA[16] = "Emu38 Document\xFE"; static BYTE pbySignatureE[16] = "Emu48 Document\xFE"; static BYTE pbySignatureW[16] = "Win48 Document\xFE"; @@ -96,227 +99,13 @@ WORD WriteStack(LPBYTE lpBuf,DWORD dwSize) // separated from LoadObject() -//################ -//# -//# ROM -//# -//################ - -static WORD CrcRom(VOID) // 19.11.99 cg, new, calculate fingerprint of ROM -{ - DWORD dwCount; - DWORD dwFileSize; - DWORD dwCrc = 0; - - dwFileSize = GetFileSize(hRomFile, &dwCount); // get real filesize - _ASSERT(dwCount == 0); // isn't created by MapRom() - - _ASSERT(pbyRom); // view on ROM - // use checksum, because it's faster - for (dwCount = 0;dwCount < dwFileSize; dwCount+=sizeof(dwCrc)) - dwCrc += *((DWORD *) &pbyRom[dwCount]); - - return (WORD) dwCrc; -} - -BOOL MapRom(LPCSTR szFilename) -{ - DWORD dwFileSizeHigh; - - if (pbyRom != NULL) - { - return FALSE; - } - SetCurrentDirectory(szEmu48Directory); - hRomFile = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - SetCurrentDirectory(szCurrentDirectory); - if (hRomFile == INVALID_HANDLE_VALUE) - { - hRomFile = NULL; - return FALSE; - } - dwRomSize = GetFileSize(hRomFile, &dwFileSizeHigh); - if (dwFileSizeHigh != 0) - { // file is too large. - CloseHandle(hRomFile); - hRomFile = NULL; - dwRomSize = 0; - return FALSE; - } - hRomMap = CreateFileMapping(hRomFile, NULL, PAGE_WRITECOPY, 0, dwRomSize, NULL); - if (hRomMap == NULL) - { - CloseHandle(hRomFile); - hRomFile = NULL; - dwRomSize = 0; - return FALSE; - } - if (GetLastError() == ERROR_ALREADY_EXISTS) - { - AbortMessage("Sharing file mapping handle."); - } - pbyRom = MapViewOfFile(hRomMap, FILE_MAP_COPY, 0, 0, dwRomSize); - if (pbyRom == NULL) - { - CloseHandle(hRomMap); - CloseHandle(hRomFile); - hRomMap = NULL; - hRomFile = NULL; - dwRomSize = 0; - return FALSE; - } - return TRUE; -} - -VOID UnmapRom(VOID) -{ - if (pbyRom==NULL) return; - UnmapViewOfFile(pbyRom); - CloseHandle(hRomMap); - CloseHandle(hRomFile); - pbyRom = NULL; - hRomMap = NULL; - hRomFile = NULL; - dwRomSize = 0; - return; -} - - - -//################ -//# -//# Port2 -//# -//################ - -static WORD CrcPort2(VOID) // calculate fingerprint of port2 -{ - DWORD dwCount; - DWORD dwFileSize; - WORD wCrc = 0; - - // port2 CRC isn't available - if (cCurrentRomType=='X' || pbyPort2==NULL) return wCrc; - - dwFileSize = GetFileSize(hPort2File, &dwCount); // get real filesize - _ASSERT(dwCount == 0); // isn't created by MapPort2() - - for (dwCount = 0;dwCount < dwFileSize; ++dwCount) - wCrc = (wCrc >> 4) ^ (((wCrc ^ ((WORD) pbyPort2[dwCount])) & 0xf) * 0x1081); - return wCrc; -} - -BOOL MapPort2(LPCSTR szFilename) -{ - DWORD dwFileSizeLo; - DWORD dwFileSizeHi; - - if (pbyPort2 != NULL) return FALSE; - bPort2Writeable = TRUE; - dwPort2Size = 0; // reset size of port2 - - SetCurrentDirectory(szEmu48Directory); - if (bPort2IsShared) - { - hPort2File = CreateFile(szFilename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - SetCurrentDirectory(szCurrentDirectory); - if (hPort2File == INVALID_HANDLE_VALUE) - { - bPort2Writeable = FALSE; - SetCurrentDirectory(szEmu48Directory); - hPort2File = CreateFile(szFilename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - SetCurrentDirectory(szCurrentDirectory); - if (hPort2File == INVALID_HANDLE_VALUE) - { - hPort2File = NULL; - return FALSE; - } - } - } - else - { - hPort2File = CreateFile(szFilename, GENERIC_READ|GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); - SetCurrentDirectory(szCurrentDirectory); - if (hPort2File == INVALID_HANDLE_VALUE) - { - hPort2File = NULL; - return FALSE; - } - } - - dwFileSizeLo = GetFileSize(hPort2File, &dwFileSizeHi); - if (dwFileSizeHi != 0) - { // file is too large. - CloseHandle(hPort2File); - hPort2File = NULL; - dwPort2Mask = 0; - bPort2Writeable = FALSE; - return FALSE; - } - if (dwFileSizeLo & 0x3FFFF) - { // file size is wrong - CloseHandle(hPort2File); - hPort2File = NULL; - dwPort2Mask = 0; - bPort2Writeable = FALSE; - return FALSE; - } - dwPort2Mask = (dwFileSizeLo >> 18) - 1; // mask for valid address lines of the BS-FF - if (bPort2Writeable) - hPort2Map = CreateFileMapping(hPort2File, NULL, PAGE_READWRITE, 0, dwFileSizeLo, NULL); - else - hPort2Map = CreateFileMapping(hPort2File, NULL, PAGE_READONLY, 0, dwFileSizeLo, NULL); - if (hPort2Map == NULL) - { - CloseHandle(hPort2File); - hPort2File = NULL; - dwPort2Mask = 0; - bPort2Writeable = FALSE; - return FALSE; - } - if (bPort2Writeable) - pbyPort2 = MapViewOfFile(hPort2Map, FILE_MAP_WRITE, 0, 0, dwFileSizeLo); - else - pbyPort2 = MapViewOfFile(hPort2Map, FILE_MAP_READ, 0, 0, dwFileSizeLo); - if (pbyPort2 == NULL) - { - CloseHandle(hPort2Map); - CloseHandle(hPort2File); - hPort2Map = NULL; - hPort2File = NULL; - dwPort2Mask = 0; - bPort2Writeable = FALSE; - return FALSE; - } - dwPort2Size = dwFileSizeLo / 2048; // mapping size of port2 - if (dwPort2Size > 128) dwPort2Size = 128; - return TRUE; -} - -VOID UnmapPort2(VOID) -{ - if (pbyPort2==NULL) return; - UnmapViewOfFile(pbyPort2); - CloseHandle(hPort2Map); - CloseHandle(hPort2File); - pbyPort2 = NULL; - hPort2Map = NULL; - hPort2File = NULL; - dwPort2Size = 0; // reset size of port2 - dwPort2Mask = 0; - bPort2Writeable = FALSE; - return; -} - - - //################ //# //# Patch //# //################ -static BYTE Asc2Nib(char c) +static __inline BYTE Asc2Nib(char c) { if (c<'0') return 0; if (c<='9') return c-'0'; @@ -327,6 +116,76 @@ static BYTE Asc2Nib(char c) return 0; } +// functions to restore ROM patches +typedef struct tnode +{ + DWORD dwAddress; // patch address + BYTE byROM; // original ROM value + BYTE byPatch; // patched ROM value + struct tnode *next; // next node +} TREENODE; + +static TREENODE *nodePatch = NULL; + +static BOOL PatchNibble(DWORD dwAddress, BYTE byPatch) +{ + TREENODE *p; + + _ASSERT(pbyRom); // ROM defined + if((p = (TREENODE *) LocalAlloc(LMEM_FIXED,sizeof(TREENODE))) == NULL) + return TRUE; + + p->dwAddress = dwAddress; // save current values + p->byROM = pbyRom[dwAddress]; + p->byPatch = byPatch; + p->next = nodePatch; // save node + nodePatch = p; + + pbyRom[dwAddress] = byPatch; // patch ROM + return FALSE; +} + +static VOID RestorePatches(VOID) +{ + TREENODE *p; + + _ASSERT(pbyRom); // ROM defined + while (nodePatch != NULL) + { + // restore original data + pbyRom[nodePatch->dwAddress] = nodePatch->byROM; + + p = nodePatch->next; // save pointer to next node + LocalFree(nodePatch); // free node + nodePatch = p; // new node + } + return; +} + +VOID UpdatePatches(BOOL bPatch) +{ + TREENODE *p = nodePatch; + + _ASSERT(pbyRom); // ROM defined + while (p != NULL) + { + if (bPatch) + { + // save original data and patch address + p->byROM = pbyRom[p->dwAddress]; + pbyRom[p->dwAddress] = p->byPatch; + } + else + { + // restore original data + pbyRom[p->dwAddress] = p->byROM; + } + + p = p->next; // next node + } + return; +} + BOOL PatchRom(LPCSTR szFilename) { HANDLE hFile = NULL; @@ -393,7 +252,8 @@ BOOL PatchRom(LPCSTR szFilename) while (lpBuf[nPos]) { if (isxdigit(lpBuf[nPos]) == FALSE) break; - pbyRom[dwAddress] = Asc2Nib(lpBuf[nPos]); + // patch ROM and save original nibble + PatchNibble(dwAddress, Asc2Nib(lpBuf[nPos])); dwAddress = (dwAddress+1)&(dwRomSize-1); nPos++; } @@ -404,6 +264,266 @@ BOOL PatchRom(LPCSTR szFilename) +//################ +//# +//# ROM +//# +//################ + +static WORD CrcRom(VOID) // calculate fingerprint of ROM +{ + DWORD dwCount; + DWORD dwFileSize; + DWORD dwCrc = 0; + + dwFileSize = GetFileSize(hRomFile, &dwCount); // get real filesize + _ASSERT(dwCount == 0); // isn't created by MapRom() + + _ASSERT(pbyRom); // view on ROM + // use checksum, because it's faster + for (dwCount = 0;dwCount < dwFileSize; dwCount+=sizeof(dwCrc)) + dwCrc += *((DWORD *) &pbyRom[dwCount]); + + return (WORD) dwCrc; +} + +BOOL MapRom(LPCSTR szFilename) +{ + DWORD dwFileSizeHigh; + + // open ROM for writing + BOOL bWrite = (cCurrentRomType == 'X') ? bRomWriteable : FALSE; + + if (pbyRom != NULL) + { + return FALSE; + } + SetCurrentDirectory(szEmu48Directory); + if (bWrite) // ROM writeable + { + hRomFile = CreateFile(szFilename, + GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + if (hRomFile == INVALID_HANDLE_VALUE) + { + bWrite = FALSE; // ROM not writeable + hRomFile = CreateFile(szFilename, + GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + } + } + else // writing ROM disabled + { + hRomFile = CreateFile(szFilename, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + } + SetCurrentDirectory(szCurrentDirectory); + if (hRomFile == INVALID_HANDLE_VALUE) + { + hRomFile = NULL; + return FALSE; + } + dwRomSize = GetFileSize(hRomFile, &dwFileSizeHigh); + if (dwFileSizeHigh != 0) + { // file is too large. + CloseHandle(hRomFile); + hRomFile = NULL; + dwRomSize = 0; + return FALSE; + } + hRomMap = CreateFileMapping(hRomFile, NULL, bWrite ? PAGE_READWRITE : PAGE_WRITECOPY, 0, dwRomSize, NULL); + if (hRomMap == NULL) + { + CloseHandle(hRomFile); + hRomFile = NULL; + dwRomSize = 0; + return FALSE; + } + if (GetLastError() == ERROR_ALREADY_EXISTS) + { + AbortMessage("Sharing file mapping handle."); + } + pbyRom = MapViewOfFile(hRomMap, bWrite ? FILE_MAP_WRITE : FILE_MAP_COPY, 0, 0, dwRomSize); + if (pbyRom == NULL) + { + CloseHandle(hRomMap); + CloseHandle(hRomFile); + hRomMap = NULL; + hRomFile = NULL; + dwRomSize = 0; + return FALSE; + } + return TRUE; +} + +VOID UnmapRom(VOID) +{ + if (pbyRom==NULL) return; + RestorePatches(); // restore ROM Patches + UnmapViewOfFile(pbyRom); + CloseHandle(hRomMap); + CloseHandle(hRomFile); + pbyRom = NULL; + hRomMap = NULL; + hRomFile = NULL; + dwRomSize = 0; + return; +} + + + +//################ +//# +//# Port2 +//# +//################ + +static WORD CrcPort2(VOID) // calculate fingerprint of port2 +{ + DWORD dwCount; + DWORD dwFileSize; + WORD wCrc = 0; + + // port2 CRC isn't available + if (cCurrentRomType=='X' || pbyPort2==NULL) return wCrc; + + dwFileSize = GetFileSize(hPort2File, &dwCount); // get real filesize + _ASSERT(dwCount == 0); // isn't created by MapPort2() + + for (dwCount = 0;dwCount < dwFileSize; ++dwCount) + wCrc = (wCrc >> 4) ^ (((wCrc ^ ((WORD) pbyPort2[dwCount])) & 0xf) * 0x1081); + return wCrc; +} + +BOOL MapPort2(LPCSTR szFilename) +{ + DWORD dwFileSizeLo; + DWORD dwFileSizeHi; + + if (pbyPort2 != NULL) return FALSE; + bPort2Writeable = TRUE; + dwPort2Size = 0; // reset size of port2 + + SetCurrentDirectory(szEmu48Directory); + if (bPort2IsShared) + { + hPort2File = CreateFile(szFilename, + GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + SetCurrentDirectory(szCurrentDirectory); + if (hPort2File == INVALID_HANDLE_VALUE) + { + bPort2Writeable = FALSE; + SetCurrentDirectory(szEmu48Directory); + hPort2File = CreateFile(szFilename, + GENERIC_READ, + FILE_SHARE_READ|FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + SetCurrentDirectory(szCurrentDirectory); + if (hPort2File == INVALID_HANDLE_VALUE) + { + hPort2File = NULL; + return FALSE; + } + } + } + else + { + hPort2File = CreateFile(szFilename, + GENERIC_READ|GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + SetCurrentDirectory(szCurrentDirectory); + if (hPort2File == INVALID_HANDLE_VALUE) + { + hPort2File = NULL; + return FALSE; + } + } + dwFileSizeLo = GetFileSize(hPort2File, &dwFileSizeHi); + if (dwFileSizeHi != 0) + { // file is too large. + CloseHandle(hPort2File); + hPort2File = NULL; + dwPort2Mask = 0; + bPort2Writeable = FALSE; + return FALSE; + } + if (dwFileSizeLo & 0x3FFFF) + { // file size is wrong + CloseHandle(hPort2File); + hPort2File = NULL; + dwPort2Mask = 0; + bPort2Writeable = FALSE; + return FALSE; + } + dwPort2Mask = (dwFileSizeLo >> 18) - 1; // mask for valid address lines of the BS-FF + hPort2Map = CreateFileMapping(hPort2File, NULL, bPort2Writeable ? PAGE_READWRITE : PAGE_READONLY, + 0, dwFileSizeLo, NULL); + if (hPort2Map == NULL) + { + CloseHandle(hPort2File); + hPort2File = NULL; + dwPort2Mask = 0; + bPort2Writeable = FALSE; + return FALSE; + } + pbyPort2 = MapViewOfFile(hPort2Map, bPort2Writeable ? FILE_MAP_WRITE : FILE_MAP_READ, 0, 0, dwFileSizeLo); + if (pbyPort2 == NULL) + { + CloseHandle(hPort2Map); + CloseHandle(hPort2File); + hPort2Map = NULL; + hPort2File = NULL; + dwPort2Mask = 0; + bPort2Writeable = FALSE; + return FALSE; + } + dwPort2Size = dwFileSizeLo / 2048; // mapping size of port2 + if (dwPort2Size > 128) dwPort2Size = 128; + return TRUE; +} + +VOID UnmapPort2(VOID) +{ + if (pbyPort2==NULL) return; + UnmapViewOfFile(pbyPort2); + CloseHandle(hPort2Map); + CloseHandle(hPort2File); + pbyPort2 = NULL; + hPort2Map = NULL; + hPort2File = NULL; + dwPort2Size = 0; // reset size of port2 + dwPort2Mask = 0; + bPort2Writeable = FALSE; + return; +} + + + //################ //# //# Documents @@ -446,9 +566,9 @@ BOOL NewDocument(VOID) if (!InitKML(szCurrentKml,FALSE)) goto restore; Chipset.type = cCurrentRomType; - if (Chipset.type == 'A') // 01.12.99 cg, new, HP38G + if (Chipset.type == '6' || Chipset.type == 'A') // HP38G { - Chipset.Port0Size = 32; + Chipset.Port0Size = (Chipset.type == 'A') ? 32 : 64; Chipset.Port1Size = 0; Chipset.Port2Size = 0; Chipset.Port0 = (LPBYTE)LocalAlloc(0,Chipset.Port0Size*2048); @@ -471,7 +591,6 @@ BOOL NewDocument(VOID) Chipset.cards_status = 0x5; // use 2nd command line argument if defined - // 08.11.99 cg, changed, replaced __argc and __argv variable MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]); } if (Chipset.type == 'G') // HP48GX @@ -487,7 +606,6 @@ BOOL NewDocument(VOID) Chipset.cards_status = 0xA; // use 2nd command line argument if defined - // 08.11.99 cg, changed, replaced __argc and __argv variable MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]); } if (Chipset.type == 'X') // HP49G @@ -507,6 +625,8 @@ BOOL NewDocument(VOID) bPort2Writeable = TRUE; Chipset.cards_status = 0xF; + + FlashInit(); // init flash structure } RomSwitch(0); // boot ROM view of HP49G and map memory SaveBackup(); @@ -515,16 +635,14 @@ restore: RestoreBackup(); ResetBackup(); - // 01.12.99 cg, changed, added HP38G - if(Chipset.type != 'A' && Chipset.type != 'X') + // HP48SX/GX + if(Chipset.type != '6' && Chipset.type != 'A' && Chipset.type != 'X') { // use 2nd command line argument if defined - // 08.11.99 cg, changed, replaced __argc and __argv variable MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]); } if (pbyRom) { - // 09.12.99 cg, changed, removed typecast of the position data SetWindowPos(hWnd,NULL,Chipset.nPosX,Chipset.nPosY,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE); Map(0x00,0xFF); } @@ -562,7 +680,6 @@ BOOL OpenDocument(LPCSTR szFilename) switch (pbyFileSignature[0]) { case 'E': - // 01.12.99 cg, changed, added HP38 signature pbySig = (pbyFileSignature[3] == '3') ? pbySignatureA : ((pbyFileSignature[4] == '8') ? pbySignatureE : pbySignatureV); @@ -620,7 +737,6 @@ BOOL OpenDocument(LPCSTR szFilename) Chipset.Port1 = NULL; Chipset.Port2 = NULL; - // 09.12.99 cg, changed, removed typecast of the position data SetWindowPos(hWnd,NULL,Chipset.nPosX,Chipset.nPosY,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE); if (szCurrentKml == NULL) @@ -634,6 +750,8 @@ BOOL OpenDocument(LPCSTR szFilename) goto restore; } + FlashInit(); // init flash structure + if (Chipset.Port0Size) { Chipset.Port0 = (LPBYTE)LocalAlloc(0,Chipset.Port0Size*2048); @@ -662,13 +780,17 @@ BOOL OpenDocument(LPCSTR szFilename) if (cCurrentRomType!='X') // HP38G, HP48SX/GX { - if(cCurrentRomType!='A') // 01.12.99 cg, new, HP48SX/GX + // HP48SX/GX + if(cCurrentRomType!='6' && cCurrentRomType!='A') { - // 08.11.99 cg, changed, replaced __argc and __argv variable MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]); - if (Chipset.wPort2Crc != CrcPort2())// port2 changed + // port2 changed and card detection enabled + if ( Chipset.wPort2Crc != CrcPort2() + && (Chipset.IORam[CARDCTL] & ECDT) != 0 && (Chipset.IORam[TIMER2_CTRL] & RUN) != 0 + ) { - Chipset.HST |= 8; // set Module Pulled + Chipset.HST |= MP; // set Module Pulled + IOBit(SRQ2,NINT,FALSE); // set NINT to low Chipset.SoftInt = TRUE; // set interrupt bInterrupt = TRUE; } @@ -695,10 +817,8 @@ BOOL OpenDocument(LPCSTR szFilename) RomSwitch(Chipset.Bank_FF); // reload ROM view of HP49G and map memory - // 20.11.99 cg, new, check fingerprint of ROM - if (Chipset.wRomCrc != CrcRom()) // ROM changed + if (Chipset.wRomCrc != CrcRom()) // ROM changed CpuReset(); - // 20.11.99 cg, end of fingerprint check lstrcpy(szCurrentFilename, szFilename); _ASSERT(hCurrentFile == NULL); @@ -715,11 +835,10 @@ restore: RestoreBackup(); ResetBackup(); - // 01.12.99 cg, bugfix, add port2 only on HP48 calculators - if(cCurrentRomType!='A' && cCurrentRomType!='X') + // HP48SX/GX + if(cCurrentRomType!='6' && cCurrentRomType!='A' && cCurrentRomType!='X') { // use 2nd command line argument if defined - // 08.11.99 cg, changed, replaced __argc and __argv variable MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]); } return FALSE; @@ -741,8 +860,9 @@ BOOL SaveDocument(VOID) SetFilePointer(hCurrentFile,0,0,FILE_BEGIN); - // 01.12.99 cg, changed, added HP38G signature - pbySig = (Chipset.type=='A') ? pbySignatureA : ((Chipset.type!='X') ? pbySignatureE : pbySignatureV); + // get document signature + pbySig = (Chipset.type=='6' || Chipset.type=='A') + ? pbySignatureA : ((Chipset.type!='X') ? pbySignatureE : pbySignatureV); if (!WriteFile(hCurrentFile, pbySig, 16, &lBytesWritten, NULL)) { @@ -750,7 +870,7 @@ BOOL SaveDocument(VOID) return FALSE; } - Chipset.wRomCrc = CrcRom(); // 20.11.99 cg, new, save fingerprint of ROM + Chipset.wRomCrc = CrcRom(); // save fingerprint of ROM Chipset.wPort2Crc = CrcPort2(); // save fingerprint of port2 nLength = strlen(szCurrentKml); @@ -801,11 +921,10 @@ BOOL SaveBackup(VOID) if (pbyRom == NULL) return FALSE; - // 09.12.99 cg, changed, save window position + // save window position GetWindowRect(hWnd, &Rect); // update saved window position Chipset.nPosX = (SWORD)Rect.left; Chipset.nPosY = (SWORD)Rect.top; - // 09.12.99 cg, end of part lstrcpy(szBackupFilename, szCurrentFilename); lstrcpy(szBackupKml, szCurrentKml); @@ -859,17 +978,15 @@ BOOL RestoreBackup(VOID) Chipset.Port2 = (LPBYTE)LocalAlloc(0,Chipset.Port2Size*2048); CopyMemory(Chipset.Port2, BackupChipset.Port2, Chipset.Port2Size*2048); } - // 09.12.99 cg, bugfix, map port2 + // map port2 else { - if(cCurrentRomType!='A') // HP48SX/GX + if(cCurrentRomType!='6' && cCurrentRomType!='A') // HP48SX/GX { // use 2nd command line argument if defined MapPort2((nArgc < 3) ? szPort2Filename : ppArgv[2]); } } - // 09.12.99 cg, end of bugfix - // 09.12.99 cg, changed, removed typecast of the position data SetWindowPos(hWnd,NULL,Chipset.nPosX,Chipset.nPosY,0,0,SWP_NOSIZE|SWP_NOZORDER|SWP_NOACTIVATE); UpdateWindowStatus(); Map(0x00,0xFF); @@ -920,14 +1037,12 @@ BOOL GetOpenFilename(VOID) "\0\0"; if (cCurrentRomType!='X') // HP38G / HP48SX/GX { - // 01.12.99 cg, new, HP38G support - if (cCurrentRomType=='A') // HP38G + if (cCurrentRomType=='6' || cCurrentRomType=='A') // HP38G { ofn.lpstrDefExt = "E38"; ofn.nFilterIndex = 1; } else - // 01.12.99 cg, end { ofn.lpstrDefExt = "E48"; ofn.nFilterIndex = 2; @@ -965,14 +1080,12 @@ BOOL GetSaveAsFilename(VOID) "\0\0"; if (cCurrentRomType!='X') // HP38G / HP48SX/GX { - // 01.12.99 cg, new, HP38G support - if (cCurrentRomType=='A') // HP38G + if (cCurrentRomType=='6' || cCurrentRomType=='A') // HP38G { ofn.lpstrDefExt = "E38"; ofn.nFilterIndex = 1; } else - // 01.12.99 cg, end { ofn.lpstrDefExt = "E48"; ofn.nFilterIndex = 2; @@ -1128,25 +1241,12 @@ BOOL SaveObject(LPCSTR szFilename) // separated stack reading part //# //################ -static UINT DibNumColors (LPBITMAPINFOHEADER lpbi) +static __inline UINT DibNumColors (LPBITMAPINFOHEADER lpbi) { - UINT bits; - if (lpbi->biClrUsed != 0) return (UINT)lpbi->biClrUsed; - bits = lpbi->biBitCount; - switch (bits) - { - case 1: - return 2; - case 4: - return 16; - case 8: - return 256; - default: - /* A 24 bitcount DIB has no color table */ - return 0; - } + /* a 24 bitcount DIB has no color table */ + return (lpbi->biBitCount <= 8) ? (1 << lpbi->biBitCount) : 0; } static HPALETTE CreateBIPalette(LPBITMAPINFOHEADER lpbi) @@ -1292,7 +1392,8 @@ HBITMAP LoadBitmapFile(LPCSTR szFilename) pbyFile+pBmfh->bfOffBits, pBmi, DIB_RGB_COLORS); } - + _ASSERT(hBitmap != NULL); + quit: UnmapViewOfFile(pbyFile); CloseHandle(hMap); diff --git a/sources/Emu48/I28F160.C b/sources/Emu48/I28F160.C new file mode 100644 index 0000000..14948f4 --- /dev/null +++ b/sources/Emu48/I28F160.C @@ -0,0 +1,696 @@ +/* + * i28f160.c + * + * This file is part of Emu48 + * + * Copyright (C) 2000 Christoph Gießelink + * + */ +#include "pch.h" +#include "Emu48.h" +#include "i28f160.h" + +#define _64KB (64*1024) // define 64KB + +#define ARRAYSIZEOF(a) (sizeof(a) / sizeof(a[0])) + +// Flash Command Set +#define READ_ARRAY 0xFF +#define READ_ID_CODES 0x90 +#define READ_QUERY 0x98 +#define READ_STATUS_REG 0x70 +#define CLEAR_STATUS_REG 0x50 +#define WRITE_BUFFER 0xE8 +#define WORD_BYTE_PROG1 0x40 +#define WORD_BYTE_PROG2 0x10 +#define BLOCK_ERASE 0x20 +#define BLOCK_ERASE_SUSPEND 0xB0 +#define BLOCK_ERASE_RESUME 0xD0 +#define STS_CONFIG 0xB8 +#define SET_CLR_BLOCK_LOCK 0x60 +#define FULL_CHIP_ERASE 0x30 + +#define CONFIRM 0xD0 + +// Status Register Definition +#define WSMS 0x80 // WRITE STATE MACHINE STATUS +#define ESS 0x40 // ERASE SUSPEND STATUS +#define ECLBS 0x20 // ERASE AND CLEAR LOCK-BIT STATUS +#define BWSLBS 0x10 // PROGRAM AND SET LOCK-BIT STATUS +#define VPPS 0x08 // Vpp STATUS +#define BWSS 0x04 // PROGRAM SUSPEND STATUS +#define DPS 0x02 // DEVICE PROTECT STATUS + +// Extended Status Register Definition +#define WBS 0x80 // WRITE BUFFER STATUS + +// write state defines +#define WRS_DATA 0 // idle state +#define WRS_WR_BUFFER_N 1 // write buffer no. of data +#define WRS_WR_BUFFER_D 2 // write buffer data +#define WRS_WR_BUFFER_C 3 // write buffer confirm +#define WRS_WR_BYTE 4 // write byte/word +#define WRS_BLOCK_ERASE 5 // block erase +#define WRS_CHIP_ERASE 6 // full chip erase +#define WRS_STS_PIN_CONFIG 7 // STS pin Configuration +#define WRS_LOCK_BITS 8 // Set/Clear Block Lock-Bits + +// read state defines +#define RDS_DATA 0 +#define RDS_ID 1 +#define RDS_QUERY 2 +#define RDS_SR 3 +#define RDS_XSR 4 + +// global data +WSMSET WSMset; +BOOL bWP = FALSE; // WP# = low, locked blocks cannot be erased + +// function prototypes +// write function WSM state +static VOID WrStateIdle(BYTE a, DWORD d); +static VOID WrStateE8(DWORD d); +static VOID WrStateE8N(BYTE a, DWORD d); +static VOID WrStateE8D(BYTE a, DWORD d); +static VOID WrStateE8C(BYTE a, DWORD d); +static VOID WrState40(DWORD d); +static VOID WrState40D(BYTE a, DWORD d); +static VOID WrState20(DWORD d); +static VOID WrState20C(BYTE a, DWORD d); +static VOID WrState30(DWORD d); +static VOID WrState30C(BYTE a, DWORD d); +static VOID WrStateB8(DWORD d); +static VOID WrStateB8D(BYTE a, DWORD d); +static VOID WrState60(DWORD d); +static VOID WrState60D(BYTE a, DWORD d); + +static CONST VOID (*CONST fnWrState[])(BYTE a, DWORD d) = +{ + WrStateIdle, + WrStateE8N, WrStateE8D, WrStateE8C, + WrState40D, + WrState20C, + WrState30C, + WrStateB8D, + WrState60D +}; + +// read function WSM state +static BYTE RdStateData(DWORD d); +static BYTE RdStateId(DWORD d); +static BYTE RdStateQuery(DWORD d); +static BYTE RdStateSR(DWORD d); +static BYTE RdStateXSR(DWORD d); + +static CONST BYTE (*CONST fnRdState[])(DWORD d) = +{ + RdStateData, RdStateId, RdStateQuery, RdStateSR, RdStateXSR +}; + + +// read query table +// device address A16-A1, A0 unused +static CONST BYTE byQueryTab[] = +{ + // access with "Read Identifier Codes" command + // Identifier codes + 0xB0, // 00, Manufacturer Code + 0xD0, // 01, Device Code (16 Mbit) + 0x00, // 02, Block Lock Configuration + + 0x00, // 03, Reserved for vendor-specific information + 0x00, // 04, " + 0x00, // 05, " + 0x00, // 06, " + 0x00, // 07, " + 0x00, // 08, " + 0x00, // 09, " + 0x00, // 0A, " + 0x00, // 0B, " + 0x00, // 0C, " + 0x00, // 0D, " + 0x00, // 0E, " + 0x00, // 0F, " + + // access with "Read Query" command + // CFI query identification string + 0x51, // 10, Query-Unique ASCII string "Q" + 0x52, // 11, Query-Unique ASCII string "R" + 0x59, // 12, Query-Unique ASCII string "Y" + 0x01, // 13, Primary Vendor Command Set and Control Interface ID CODE + 0x00, // 14, " + 0x31, // 15, Address for Primary Algorithm Extended Query Table + 0x00, // 16, " + 0x00, // 17, Alternate Vendor Command Set and Control Interface ID Code + 0x00, // 18, " + 0x00, // 19, Address for Secondary Algorithm Extended Query Table + 0x00, // 1A, " + + // System interface information + 0x27, // 1B, Vcc Logic Supply Minimum Program/Erase Voltage + 0x55, // 1C, Vcc Logic Supply Maximum Program/Erase Voltage + 0x27, // 1D, Vpp [Programming] Supply Minimum Program/Erase Voltage + 0x55, // 1E, Vpp [Programming] Supply Maximum Program/Erase Voltage + 0x03, // 1F, Typical Time-Out per Single Byte/Word Program + 0x06, // 20, Typical Time-Out for Max. Buffer Write + 0x0A, // 21, Typical Time-Out per Individual Block Erase + 0x0F, // 22, Typical Time-Out for Full Chip Erase + 0x04, // 23, Maximum Time-Out for Byte/Word Program + 0x04, // 24, Maximum Time-Out for Buffer Write + 0x04, // 25, Maximum Time-Out per Individual Block Erase + 0x04, // 26, Maximum Time-Out for Full Chip Erase + 0x15, // 27, Device Size + 0x02, // 28, Flash Device Interface Description + 0x00, // 29, " + 0x05, // 2A, Maximum Number of Bytes in Write Buffer + 0x00, // 2B, " + 0x01, // 2C, Number of Erase Block Regions within Device + 0x1F, // 2D, Erase Block Region Information + 0x00, // 2E, " + 0x00, // 2F, " + 0x01, // 30, " + + // Intel-specific extended query table + 0x50, // 31, Primary Extended Query Table, Unique ASCII string "P" + 0x52, // 32, Primary Extended Query Table, Unique ASCII string "R" + 0x49, // 33, Primary Extended Query Table, Unique ASCII string "I" + 0x31, // 34, Major Version Number, ASCII + 0x30, // 35, Minor Version Number, ASCII + 0x0F, // 36, Optional Feature & Command Support + 0x00, // 37, " + 0x00, // 38, " + 0x00, // 39, " + 0x01, // 3A, Supported Functions after Suspend + 0x03, // 3B, Block Status Register Mask + 0x00, // 3C, " + 0x50, // 3D, Vcc Logic Supply Optimum Program/Erase voltage + 0x50 // 3E, Vpp [Programming] Supply Optimum Program/Erase voltage +}; + + +// +// write state functions +// + +static VOID WrStateIdle(BYTE a, DWORD d) +{ + WSMset.bRomArray = FALSE; // register access + + switch(a) + { + case READ_ARRAY: // read array mode, normal operation + WSMset.bRomArray = TRUE; // data array access + WSMset.uWrState = WRS_DATA; + WSMset.uRdState = RDS_DATA; + break; + case READ_ID_CODES: // read identifier codes register + WSMset.uRdState = RDS_ID; + break; + case READ_QUERY: // read query register + WSMset.uRdState = RDS_QUERY; + break; + case READ_STATUS_REG: // read status register + WSMset.uRdState = RDS_SR; + break; + case CLEAR_STATUS_REG: // clear status register + WSMset.byStatusReg = 0; + break; + case WRITE_BUFFER: // write to buffer + WrStateE8(d); + break; + case WORD_BYTE_PROG1: + case WORD_BYTE_PROG2: // byte/word program + WrState40(d); + break; + case BLOCK_ERASE: // block erase + WrState20(d); + break; + case BLOCK_ERASE_SUSPEND: // block erase, word/byte program suspend + WSMset.byStatusReg |= WSMS; // operation suspended + WSMset.byStatusReg &= ~ESS; // block erase completed (because no timing emulation) + WSMset.byStatusReg &= ~BWSS; // program completed (because no timing emulation) + WSMset.uRdState = RDS_SR; + break; + case BLOCK_ERASE_RESUME: // block erase, word/byte program resume + WSMset.byStatusReg &= ~WSMS; // operation in progress + WSMset.byStatusReg &= ~ESS; // block erase in progress + WSMset.byStatusReg &= ~BWSS; // program in progress + WSMset.byStatusReg |= WSMS; // operation completed (because no timing emulation) + WSMset.uRdState = RDS_SR; + break; + case STS_CONFIG: + WSMset.bRomArray = bFlashRomArray; // old access mode + WrStateB8(d); + break; + case SET_CLR_BLOCK_LOCK: // set/clear block lock-bits + WrState60(d); + break; + case FULL_CHIP_ERASE: // full chip erase + WrState30(d); + break; + default: // wrong command + WSMset.bRomArray = bFlashRomArray; // old access mode + break; + } + + if(bFlashRomArray != WSMset.bRomArray) // new access mode + { + bFlashRomArray = WSMset.bRomArray; // change register access + Map(0x00,0xFF); // update memory mapping + UpdatePatches(bFlashRomArray); // patch/unpatch ROM again + } + return; +} + +// write to buffer initial command +static VOID WrStateE8(DWORD d) +{ + // @todo add 2nd write buffer implementation + // @todo add program timing implementation + + WSMset.byExStatusReg = 0; // no write buffer + if (WSMset.byWrite1No == 0) // buffer1 available + { + WSMset.byWrite1No = 1; // buffer1 in use + WSMset.dwWrite1Addr = d; // byte block address of buffer1 + WSMset.byExStatusReg = WBS; // write buffer available + // fill write buffer + FillMemory(WSMset.pbyWrite1,ARRAYSIZEOF(WSMset.pbyWrite1),0xFF); + WSMset.uWrState = WRS_WR_BUFFER_N; // set state machine + WSMset.uRdState = RDS_XSR; + } + return; +} + +// write to buffer number of byte +static VOID WrStateE8N(BYTE a, DWORD d) +{ + _ASSERT(a <= 0x1F); // check buffer size + a &= 0x1F; // maximum write buffer size + WSMset.byWrite1No += a; // save no. of byte to program + WSMset.byWrite1Size = a; // save size to check write buffer boundaries + WSMset.dwWrite1Addr = d; // byte block address of buffer1 + WSMset.byStatusReg &= ~WSMS; // state machine busy + WSMset.uWrState = WRS_WR_BUFFER_D; + WSMset.uRdState = RDS_SR; + return; +} + +// write to buffer data +static VOID WrStateE8D(BYTE a, DWORD d) +{ + // first data byte + if (WSMset.byWrite1No == WSMset.byWrite1Size + 1) + { + // same block + if ((WSMset.dwWrite1Addr & ~(_64KB-1)) == (d & ~(_64KB-1))) + { + WSMset.dwWrite1Addr = d; // byte block address of buffer1 + WSMset.pbyWrite1[0] = a; // save byte + } + else + { + WSMset.byWrite1No = 0; // free write buffer + // improper command sequence + WSMset.byStatusReg |= (ECLBS | BWSLBS); + WSMset.byStatusReg |= WSMS; // data written + WSMset.uWrState = WRS_DATA; + return; + } + } + else + { + // write address within buffer + if (d >= WSMset.dwWrite1Addr && d <= WSMset.dwWrite1Addr + WSMset.byWrite1Size) + { + // save byte in buffer + WSMset.pbyWrite1[d-WSMset.dwWrite1Addr] = a; + } + else + { + WSMset.byWrite1No = 0; // free write buffer + // improper command sequence + WSMset.byStatusReg |= (ECLBS | BWSLBS); + WSMset.byStatusReg |= WSMS; // data written + WSMset.uWrState = WRS_DATA; + return; + } + } + + if (--WSMset.byWrite1No == 0) // last byte written + WSMset.uWrState = WRS_WR_BUFFER_C; // goto confirm state + return; +} + +// write to buffer confirm +static VOID WrStateE8C(BYTE a, DWORD d) +{ + if (CONFIRM == a) // write buffer confirm? + { + BYTE byPos; + + d = WSMset.dwWrite1Addr << 1; // nibble start address + + for (byPos = 0; byPos <= WSMset.byWrite1Size; ++byPos) + { + a = WSMset.pbyWrite1[byPos]; // get char from buffer + + _ASSERT(d+1 < dwRomSize); // address valid? + *(pbyRom+d) &= (a & 0x0f); // write LSB + if (*(pbyRom+d) != (a & 0x0f)) // check writing + WSMset.byStatusReg |= BWSLBS; + ++d; // next nibble + *(pbyRom+d) &= (a >> 4); // write MSB + if (*(pbyRom+d) != (a >> 4)) // check writing + WSMset.byStatusReg |= BWSLBS; + ++d; // next nibble + } + } + else + { + WSMset.byWrite1No = 0; // free write buffer + // improper command sequence + WSMset.byStatusReg |= (ECLBS | BWSLBS); + } + WSMset.byStatusReg |= WSMS; // data written + WSMset.uWrState = WRS_DATA; + return; +} + +// byte/word program initial command +static VOID WrState40(DWORD d) +{ + WSMset.byStatusReg &= ~WSMS; // state machine busy + WSMset.uWrState = WRS_WR_BYTE; + WSMset.uRdState = RDS_SR; + return; + UNREFERENCED_PARAMETER(d); +} + +// byte/word program data +static VOID WrState40D(BYTE a, DWORD d) +{ + d <<= 1; // nibble start address + _ASSERT(d+1 < dwRomSize); // address valid? + // no error set in BWSLBS, because i could alway program a "0" + *(pbyRom+d++) &= (a & 0x0f); // write LSB + *(pbyRom+d++) &= (a >> 4); // write MSB + WSMset.byStatusReg |= WSMS; // data written + WSMset.uWrState = WRS_DATA; + return; +} + +// block erase initial command +static VOID WrState20(DWORD d) +{ + WSMset.byStatusReg &= ~WSMS; // state machine busy + WSMset.uWrState = WRS_BLOCK_ERASE; + WSMset.uRdState = RDS_SR; + return; + UNREFERENCED_PARAMETER(d); +} + +// block erase data & confirm +static VOID WrState20C(BYTE a, DWORD d) +{ + if (CONFIRM == a) // block erase confirm? + { + _ASSERT((d>>16) < ARRAYSIZEOF(WSMset.byLockCnfg)); + if (WSMset.byLockCnfg[d>>16] & 1) // lock bit of block is set + { + WSMset.byStatusReg |= ECLBS; // error in block erasure + WSMset.byStatusReg |= DPS; // lock bit detected + } + else + { + LPBYTE pbyBlock; + UINT i; + + d &= ~(_64KB-1); // start of 64KB block + _ASSERT(2*(d+_64KB) <= dwRomSize); // address valid? + pbyBlock = pbyRom + (d << 1); // nibble start address + for (i = 0; i < 2*_64KB; ++i) // write 128K nibble + *pbyBlock++ = 0xf; // write nibble + } + } + else + { + // improper command sequence + WSMset.byStatusReg |= (ECLBS | BWSLBS); + } + WSMset.byStatusReg |= WSMS; // block erased + WSMset.uWrState = WRS_DATA; + return; +} + +// full chip erase initial command +static VOID WrState30(DWORD d) +{ + WSMset.byStatusReg &= ~WSMS; // state machine busy + WSMset.uWrState = WRS_CHIP_ERASE; + WSMset.uRdState = RDS_SR; + return; + UNREFERENCED_PARAMETER(d); +} + +// full chip erase confirm +static VOID WrState30C(BYTE a, DWORD d) +{ + LPBYTE pbyBlock; + UINT i,j; + + if (CONFIRM == a) // chip erase confirm? + { + pbyBlock = pbyRom; + for (i = 0; i < 32; ++i) // check all blocks + { + _ASSERT(2*((i+1)*_64KB) <= dwRomSize); + _ASSERT(i < ARRAYSIZEOF(WSMset.byLockCnfg)); + + // lock bit of block is set & WP# = low, locked blocks cannot be erased + if ((WSMset.byLockCnfg[i] & 1) != 0 && bWP == FALSE) + { + pbyBlock += 2*_64KB; // next block + WSMset.byStatusReg |= ECLBS; // error in block erasure + // WSMset.byStatusReg |= DPS; // lock bit detected + } + else + { + WSMset.byLockCnfg[i] = 0; // clear block lock bit + + // write 128K nibble + for (j = 0; j < 2*_64KB; ++j) + *pbyBlock++ = 0xf; // write nibble + } + } + } + else + { + // improper command sequence + WSMset.byStatusReg |= (ECLBS | BWSLBS); + } + WSMset.byStatusReg |= WSMS; // chip erased + WSMset.uWrState = WRS_DATA; + return; + UNREFERENCED_PARAMETER(d); +} + +// STS pin Configuration initial command +static VOID WrStateB8(DWORD d) +{ + WSMset.uWrState = WRS_STS_PIN_CONFIG; + return; + UNREFERENCED_PARAMETER(d); +} + +// STS pin Configuration data +static VOID WrStateB8D(BYTE a, DWORD d) +{ + // no emulation of STS pin Configuration + WSMset.uWrState = WRS_DATA; + return; + UNREFERENCED_PARAMETER(a); + UNREFERENCED_PARAMETER(d); +} + +// Set/Clear block Lock-Bits initial command +static VOID WrState60(DWORD d) +{ + WSMset.byStatusReg &= ~WSMS; // state machine busy + WSMset.uWrState = WRS_LOCK_BITS; + WSMset.uRdState = RDS_SR; + return; + UNREFERENCED_PARAMETER(d); +} + +// Set/Clear block Lock-Bits confirm +static VOID WrState60D(BYTE a, DWORD d) +{ + UINT i; + + switch(a) + { + case 0x01: // set block lock bit + _ASSERT((d>>16) < ARRAYSIZEOF(WSMset.byLockCnfg)); + if (bWP) // WP# = high, can change block lock status + WSMset.byLockCnfg[d>>16] = 1; // set block lock bit + else + WSMset.byStatusReg |= DPS; // device protect detect, WP# = low + break; + case CONFIRM: // clear block lock bits + if (bWP) // WP# = high, can change block lock status + { + for (i = 0; i < 32; ++i) // clear all lock bits + { + _ASSERT(i < ARRAYSIZEOF(WSMset.byLockCnfg)); + WSMset.byLockCnfg[i] = 0; // clear block lock bit + } + } + else + { + WSMset.byStatusReg |= DPS; // device protect detect, WP# = low + } + break; + default: // improper command sequence + WSMset.byStatusReg |= (ECLBS | BWSLBS); + } + WSMset.byStatusReg |= WSMS; // block lock-bit changed + WSMset.uWrState = WRS_DATA; + return; +} + + +// +// read state functions +// + +// read array +static BYTE RdStateData(DWORD d) +{ + d <<= 1; // nibble address + _ASSERT(d+1 < dwRomSize); // address valid? + return *(pbyRom+d)|(*(pbyRom+d+1)<<4); // get byte +} + +// read identifier codes +static BYTE RdStateId(DWORD d) +{ + BYTE byData; + + d >>= 1; // A0 is not connected, ignore it + _ASSERT((d & 0xFF) < 0x10); // id with valid address? + if ((d & 0xFF) != 0x02) // id code request + { + // get data from id/query table + byData = (d < ARRAYSIZEOF(byQueryTab)) ? byQueryTab[d] : 0; + } + else // block lock table + { + UINT uIndex = d >> 15; // index into lock table + _ASSERT(uIndex < ARRAYSIZEOF(WSMset.byLockCnfg)); + byData = WSMset.byLockCnfg[uIndex]; // get data from block lock table + } + return byData; +} + +// read query +static BYTE RdStateQuery(DWORD d) +{ + d >>= 1; // A0 is not connected, ignore it + // query with valid address? + _ASSERT(d >= 0x10 && d < ARRAYSIZEOF(byQueryTab)); + // get data from query table + return (d < ARRAYSIZEOF(byQueryTab)) ? byQueryTab[d] : 0; +} + +// read status register +static BYTE RdStateSR(DWORD d) +{ + return WSMset.byStatusReg; + UNREFERENCED_PARAMETER(d); +} + +// read extended status register +static BYTE RdStateXSR(DWORD d) +{ + return WSMset.byExStatusReg; + UNREFERENCED_PARAMETER(d); +} + + +// +// public functions +// + +VOID FlashInit(VOID) +{ + ZeroMemory(&WSMset,sizeof(WSMset)); + strcpy(WSMset.byType,"WSM"); // Write State Machine header + WSMset.uSize = sizeof(WSMset); // size of this structure + WSMset.byVersion = WSMVER; // version of flash implementation structure + + // factory setting of locking bits + WSMset.byLockCnfg[0] = 0x01; // first 64KB block is locked + + WSMset.uWrState = WRS_DATA; + WSMset.uRdState = RDS_DATA; + + // data mode of ROM + WSMset.bRomArray = bFlashRomArray = TRUE; + return; +} + +VOID FlashRead(BYTE *a, DWORD d, UINT s) +{ + BYTE v; + + while (s) // each nibble + { + // output muliplexer + _ASSERT(WSMset.uRdState < ARRAYSIZEOF(fnRdState)); + v = fnRdState[WSMset.uRdState](d>>1); + + if ((d & 1) == 0) // even address + { + *a++ = v & 0xf; ++d; --s; + } + if (s && (d & 1)) // odd address + { + *a++ = v >> 4; ++d; --s; + } + } + return; +} + +VOID FlashWrite(BYTE *a, DWORD d, UINT s) +{ + BYTE v; + DWORD p; + + while (s) // each nibble + { + p = d >> 1; // byte address + if (s > 1 && (d & 1) == 0) // more than one byte on even address + { + v = *a++; // LSB + v |= *a++ << 4; // MSB + d += 2; s -= 2; + } + else + { + // get byte from output muliplexer + _ASSERT(WSMset.uRdState < ARRAYSIZEOF(fnRdState)); + v = fnRdState[WSMset.uRdState](p); + + if (d & 1) // odd address + v = (v & 0x0f) | (*a << 4); // replace MSB + else // even address + v = (v & 0xf0) | *a; // replace LSB + ++a; ++d; --s; + } + + _ASSERT(WSMset.uWrState < ARRAYSIZEOF(fnWrState)); + fnWrState[WSMset.uWrState](v,p); // WSM + } + return; +} diff --git a/sources/Emu48/I28F160.H b/sources/Emu48/I28F160.H new file mode 100644 index 0000000..995fcae --- /dev/null +++ b/sources/Emu48/I28F160.H @@ -0,0 +1,40 @@ +/* + * i28f160.h + * + * This file is part of Emu48 + * + * Copyright (C) 2000 Christoph Gießelink + * + */ + +#define WSMVER 0 // version of flash implementation structure + +#define WSMSET WSMset_t +typedef struct +{ + BYTE byType[4]; // "WSM" + UINT uSize; // size of this structure + BYTE byVersion; // WSM version + + BOOL bRomArray; // copy of bFlashRomArray + BYTE byLockCnfg[32]; // block lock table + UINT uWrState; // state of write function WSM + UINT uRdState; // state of read function WSM + BYTE byStatusReg; // status register + BYTE byExStatusReg; // extended status register + BYTE byWrite1No; // no. of written data in write buffer1 + BYTE byWrite1Size; // no. of valid data in write buffer1 + DWORD dwWrite1Addr; // destination address of buffer1 + BYTE pbyWrite1[32]; // write buffer1 +// BYTE byWrite2No; // no. of written data in write buffer2 +// BYTE byWrite2Size; // no. of valid data in write buffer2 +// DWORD dwWrite2Addr; // destination address of buffer2 +// BYTE pbyWrite2[32]; // write buffer2 +} WSMset_t; + +// i28f160.h +extern WSMSET WSMset; +extern BOOL bWP; +extern VOID FlashInit(VOID); +extern VOID FlashRead(BYTE *a, DWORD d, UINT s); +extern VOID FlashWrite(BYTE *a, DWORD d, UINT s); diff --git a/sources/Emu48/IO.H b/sources/Emu48/IO.H index 3a667a1..d4729df 100644 --- a/sources/Emu48/IO.H +++ b/sources/Emu48/IO.H @@ -9,7 +9,10 @@ // I/O addresses without mapping offset #define CRC 0x04 // Crc (16 bit, LSB first) +#define ANNCTRL 0x0b // Annunciator Control (2 nibble) #define BAUD 0x0d // Baudrate (Bit 2-0) +#define CARDCTL 0x0e // card control +#define CARDSTAT 0x0f // card status #define IOC 0x10 // IO CONTROL #define RCS 0x11 // RCS #define TCS 0x12 // TCS @@ -21,11 +24,34 @@ #define SRQ1 0x18 // SRQ1 #define SRQ2 0x19 // SRQ2 #define IR_CTRL 0x1a // IR CONTROL +#define LCR 0x1c // Led Control Register #define TIMER1_CTRL 0x2e // Timer1 Control #define TIMER2_CTRL 0x2f // Timer2 Control #define TIMER1 0x37 // Timer1 (4 bit) #define TIMER2 0x38 // Timer2 (32 bit, LSB first) +// 0x0b Annunciator Control [AON XTRA LA6 LA5] [LA4 LA3 LA2 LA1] +#define AON 0x80 // Annunciators on +#define LXTRA 0x40 // does nothing +#define LA6 0x20 // LA6 - Transmitting +#define LA5 0x10 // LA5 - Busy +#define LA4 0x08 // LA4 - Alert +#define LA3 0x04 // LA3 - Alpha +#define LA2 0x02 // LA2 - ALT Shift +#define LA1 0x01 // LA1 - Shift + +// 0x0e Card Control [ECDT RCDT SMP SWINT] +#define ECDT 0x08 // Enable Card Detect +#define RCDT 0x04 // Run Card Detect +#define SMP 0x02 // Set module pulled +#define SWINT 0x01 // Software Interrupt + +// 0x0f Card Status [P2W P1W P2C P1C] +#define P2W 0x08 // High when Port2 writeable +#define P1W 0x04 // High when Port1 writeable +#define P2C 0x02 // High when Card in Port2 inserted +#define P1C 0x01 // High when Card in Port1 inserted + // 0x10 Serial I/O Control [SON ETBE ERBF ERBZ] #define SON 0x08 // Serial on #define ETBE 0x04 // Interrupt on transmit buffer empty @@ -62,6 +88,12 @@ #define EIRI 0x02 // Enable IR interrupt #define IRE 0x01 // IR event +// 0x1c Led Control Register [LED ELBE LBZ LBF] +#define LED 0x08 // Turn on LED +#define ELBE 0x04 // Enable Interrupt on Led Buffer empty +#define LBZ 0x02 // Led Port Busy +#define LBF 0x01 // Led Buffer Full + // 0x2e Timer1 Control [SRQ WKE INT XTRA] #define SRQ 0x08 // Service request #define WKE 0x04 // Wake up diff --git a/sources/Emu48/KEYBOARD.C b/sources/Emu48/KEYBOARD.C index 27fe5cd..42152fa 100644 --- a/sources/Emu48/KEYBOARD.C +++ b/sources/Emu48/KEYBOARD.C @@ -8,6 +8,7 @@ */ #include "pch.h" #include "Emu48.h" +#include "io.h" // I/O definitions static WORD Keyboard_GetIR(VOID) { @@ -38,16 +39,19 @@ VOID ScanKeyboard(BOOL bReset) WORD wOldIn = Chipset.in; // save old Chipset.in state UpdateKdnBit(); // update KDN bit - // 22.11.99 cg, changed, DWORD casting Chipset.dwKdnCycles = (DWORD) (Chipset.cycles & 0xFFFFFFFF); - // update Chipset.in register - Chipset.in = Keyboard_GetIR() | Chipset.IR15X; + // update keyboard only if timer is running + if (Chipset.IORam[TIMER2_CTRL]&RUN) // timer running + Chipset.in = Keyboard_GetIR(); // update Chipset.in register + else // timer stopped, no keyboard update + Chipset.in &= 0x1FF; // IR[0:8], old keystate without ON key + Chipset.in |= Chipset.IR15X; // add ON key // interrrupt for any new pressed keys ? - bKbdInt = ((Chipset.in & (Chipset.in ^ wOldIn)) != 0) || bReset; + bKbdInt = (Chipset.in && (wOldIn & 0x1FF) == 0) || Chipset.IR15X || bReset; - // update keyboard interrupt pending flag + // update keyboard interrupt pending flag when 1ms keyboard scan is disabled Chipset.intd = Chipset.intd || (bKbdInt && !Chipset.intk); // keyboard interrupt enabled @@ -87,7 +91,7 @@ VOID KeyboardEvent(BOOL bPress, UINT out, UINT in) } else { - _ASSERT(out<9); + _ASSERT(out < sizeof(Chipset.Keyboard_Row) / sizeof(Chipset.Keyboard_Row[0])); if (bPress) // key pressed Chipset.Keyboard_Row[out] |= in; // set key marker in keyboard row else diff --git a/sources/Emu48/KML.C b/sources/Emu48/KML.C index 580b1da..a301cb4 100644 --- a/sources/Emu48/KML.C +++ b/sources/Emu48/KML.C @@ -1626,7 +1626,12 @@ VOID RefreshButtons() { UINT x0 = pButton[i].nOx; UINT y0 = pButton[i].nOy; - BitBlt(hWindowDC, x0, y0, pButton[i].nCx, pButton[i].nCy, hMainDC, x0, y0, SRCCOPY); + EnterCriticalSection(&csGDILock); // solving NT GDI problems + { + BitBlt(hWindowDC, x0, y0, pButton[i].nCx, pButton[i].nCy, hMainDC, x0, y0, SRCCOPY); + GdiFlush(); + } + LeaveCriticalSection(&csGDILock); } DrawButton(i); // redraw pressed button } @@ -1861,7 +1866,7 @@ BOOL InitKML(LPCSTR szFilename, BOOL bNoLog) DWORD lBytesRead; LPBYTE lpBuf; Block* pBlock; - BOOL bOk = FALSE; // 08.12.99 cg, bugfix, variable not initialized at error condition + BOOL bOk = FALSE; BYTE bySum = 0; KillKML(); diff --git a/sources/Emu48/MOPS.C b/sources/Emu48/MOPS.C index 0054321..1c19144 100644 --- a/sources/Emu48/MOPS.C +++ b/sources/Emu48/MOPS.C @@ -8,11 +8,13 @@ */ #include "pch.h" #include "Emu48.h" -#include "opcodes.h" // 25.10.99 cg, new, added for HST bits -#include "io.h" // 24.10.99 cg, renamed from Serial.h +#include "opcodes.h" +#include "io.h" +#include "i28f160.h" // flash support // #define DEBUG_SERIAL // switch for SERIAL debug purpose // #define DEBUG_IO // switch for I/O debug purpose +// #define DEBUG_FLASH // switch for FLASH MEMORY debug purpose // defines for reading an open data bus #define READEVEN 0x0D @@ -24,9 +26,14 @@ #define P2MAPBASE ((BYTE)(Chipset.P2Base & ~Chipset.P2Size)) #define BSMAPBASE ((BYTE)(Chipset.BSBase & ~Chipset.BSSize)) +// values for mapping area +enum MMUMAP { M_IO, M_ROM, M_RAM, M_P1, M_P2, M_BS }; + BOOL ioc_acc = FALSE; // flag ioc changed BOOL ir_ctrl_acc = FALSE; // flag ir_ctl changed +BOOL bFlashRomArray = TRUE; // flag ROM mode + BYTE disp = 0; // flag for update display area static LPBYTE pbyRomView[2] = {NULL,NULL}; // HP49G ROM views @@ -57,8 +64,22 @@ static __inline BYTE UckBit(BYTE byBaudIndex) QueryPerformanceCounter(&lLC); // get counter value // calculate UCK frequency - return (((BYTE)((lLC.LowPart * dwBaudrates[byBaudIndex]) / lFreq.LowPart) & 0x1) << 3) - & Chipset.IORam[IOC]; + return (((BYTE)(((lLC.QuadPart - lAppStart.QuadPart) * dwBaudrates[byBaudIndex]) + / lFreq.QuadPart) & 0x1) << 3) & Chipset.IORam[IOC]; +} + +// calculate nibble based linear flash address +static __inline DWORD FlashROMAddr(DWORD d) +{ + DWORD dwLinAddr; + + // 6 bit of latch (was A6-A1 of address bus) + dwLinAddr = (Chipset.Bank_FF >> 1) & 0x3f; + // decode A21-A18 + dwLinAddr = ((d & 0x40000) ? (dwLinAddr & 0xf) : (dwLinAddr >> 4)) << 18; + // decode A21-A18, A17-A0 + dwLinAddr |= d & 0x3FFFF; + return dwLinAddr; } // LCD line counter calculation @@ -67,8 +88,31 @@ static BYTE F4096Hz(VOID) // get a 6 bit 4096Hz down counter value LARGE_INTEGER lLC; QueryPerformanceCounter(&lLC); // get counter value + // calculate 4096 Hz frequency down counter value - return -(BYTE)((lLC.LowPart * 4096) / lFreq.LowPart) & 0x3F; + return -(BYTE)(((lLC.QuadPart - lAppStart.QuadPart) << 12) / lFreq.QuadPart) & 0x3F; +} + +// port configuration +static enum MMUMAP MapData(DWORD d) // check MMU area +{ + BYTE u = (BYTE) (d>>12); + + if (Chipset.IOCfig && ((d&0xFFFC0)==Chipset.IOBase)) return M_IO; + if (Chipset.P0Cfig && (((u^Chipset.P0Base) & ~Chipset.P0Size) == 0)) return M_RAM; + if (cCurrentRomType=='S') + { + if (Chipset.P2Cfig && (((u^Chipset.P2Base) & ~Chipset.P2Size) == 0)) return M_P2; + if (Chipset.P1Cfig && (((u^Chipset.P1Base) & ~Chipset.P1Size) == 0)) return M_P1; + if (Chipset.BSCfig && (((u^Chipset.BSBase) & ~Chipset.BSSize) == 0)) return M_BS; + } + else + { + if (Chipset.P1Cfig && (((u^Chipset.P1Base) & ~Chipset.P1Size) == 0)) return M_P1; + if (Chipset.BSCfig && (((u^Chipset.BSBase) & ~Chipset.BSSize) == 0)) return M_BS; + if (Chipset.P2Cfig && (((u^Chipset.P2Base) & ~Chipset.P2Size) == 0)) return M_P2; + } + return M_ROM; } // port mapping @@ -152,7 +196,7 @@ static VOID MapP1(BYTE a, BYTE b) m = (Chipset.Port1Size*2048)-1; // real size of module, address mask for mirroring p = (a<<12)&m; // offset to begin of P1 in nibbles - if (Chipset.cards_status & PORT1_WRITE) // 19.11.99 cg, changed, port1 write enabled + if (Chipset.cards_status & PORT1_WRITE) // port1 write enabled { for (i=a; i<=b; i++) // scan each 2KB page { @@ -274,24 +318,35 @@ static VOID MapROM(BYTE a, BYTE b) if (cCurrentRomType == 'X') // HP49G { - _ASSERT(pbyRomView[0]); // check ROM bank set - _ASSERT(pbyRomView[1]); - - m = (128*1024*2)-1; // mapped in 128KB pages - p = (a<<12)&m; // offset to the begin of ROM in nibbles - for (i=a; i<=b; i++) // scan each 2KB page + if (bFlashRomArray) // view flash ROM data { - RMap[i]=pbyRomView[(i & 0x40)!=0] + p; - WMap[i]=NULL; // no writing - p = (p+0x1000)&m; + _ASSERT(pbyRomView[0]); // check ROM bank set + _ASSERT(pbyRomView[1]); + + m = (128*1024*2)-1; // mapped in 128KB pages + p = (a<<12)&m; // offset to the begin of ROM in nibbles + for (i=a; i<=b; i++) // scan each 2KB page + { + RMap[i]=pbyRomView[(i & 0x40)!=0] + p; + WMap[i]=NULL; // no writing + p = (p+0x1000)&m; + } + } + else // view flash ROM register + { + for (i=a; i<=b; i++) // scan each 2KB page + { + RMap[i]=NULL; // view flash register + WMap[i]=NULL; // no writing + } } return; } - // HP48SX / HP48GX - m = (dwRomSize-1)&0xFF000; // ROM address mask for mirroring - // when G(X) ROM and DA19=0 (ROM disabled) - if (cCurrentRomType != 'S' && (Chipset.IORam[0x29]&0x8) == 0) + // HP38G / HP48SX / HP48GX + m = dwRomSize - 1; // ROM address mask for mirroring + // when 512KB ROM and DA19=0 (ROM disabled) + if ((m & 0x80000) != 0 && (Chipset.IORam[0x29]&0x8) == 0) m >>= 1; // mirror ROM at #80000 (AR18=0) p = (a*0x1000)&m; // data offset in nibbles for (i=a;i<=b;i++) // scan each 2KB page @@ -318,7 +373,14 @@ VOID Map(BYTE a, BYTE b) // maps 2KB pages with priority } else // HP48GX / HP49G { - if (Chipset.P2Cfig) MapP2(a,b); // NCE3, port2 + if (Chipset.P2Cfig) // NCE3, port2 + { + // LED bit set on a HP49 + if (cCurrentRomType=='X' && (Chipset.IORam[LCR]&LED)) + MapROM(a,b); // NCE3, ROM + else + MapP2(a,b); // NCE3, port2 + } if (Chipset.BSCfig) MapBS(a,b); // CE1, bank select (lower priority than CE2) if (Chipset.P1Cfig) MapP1(a,b); // CE2, port1 (higher priority than CE1) } @@ -331,12 +393,11 @@ VOID RomSwitch(DWORD adr) if (cCurrentRomType=='X') // only HP49G { Chipset.Bank_FF = adr; // save address line - // 13.12.99 cg, bugfix, A6 was always zero adr = (adr >> 1) & 0x3f; // 6 bit of latch (was A6-A1 of address bus) // lower 4 bit (16 banks) for 2nd ROM view - pbyRomView[1] = pbyRom + ((adr & 0xf) * 128 * 1024 * 2); + pbyRomView[1] = pbyRom + (((adr & 0xf) * 128 * 1024 * 2) & (dwRomSize - 1)); // higher 2 bit (4 banks) for 1st ROM view - pbyRomView[0] = pbyRom + ((adr >> 4) * 128 * 1024 * 2); + pbyRomView[0] = pbyRom + (((adr >> 4) * 128 * 1024 * 2) & (dwRomSize - 1)); } Map(0x00,0xFF); // update memory mapping return; @@ -546,18 +607,16 @@ VOID C_Eq_Id() return; } - -VOID CpuReset(VOID) // 19.03.99 cg, new, register setting after Cpu Reset +VOID CpuReset(VOID) // register setting after Cpu Reset { Chipset.pc = 0; - // 23.10.99 cg, bugfix, additional settings Chipset.rstkp = 0; FillMemory(Chipset.rstk,sizeof(Chipset.rstk),0); Chipset.HST = 0; - // 23.10.99 cg, end of additional settings Chipset.inte = TRUE; Chipset.Shutdn = FALSE; Chipset.SoftInt = TRUE; + Chipset.FlashRomState = 0; // WSM state of flash memory Reset(); // reset MMU bInterrupt = TRUE; return; @@ -565,27 +624,28 @@ VOID CpuReset(VOID) // 19.03.99 cg, new, register setting after Cpu Reset BYTE GetLineCounter(VOID) // get line counter value { + _ASSERT(byVblRef < 0x40); return (0x40 + F4096Hz() - byVblRef) & 0x3F; } VOID Npeek(BYTE *a, DWORD d, UINT s) { + enum MMUMAP eMap; DWORD u, v; UINT c; BYTE *p; do { - // 12.11.99 cg, new, added reading of I/O area - // test Chipset.IOCfig for mapping - if ((Chipset.IOCfig)&&((d&0xFFFC0)==Chipset.IOBase)) + eMap = MapData(d); // get active memory controller + if (M_IO == eMap) // I/O access { - // update crc + // update CRC Nunpack(Chipset.IORam+CRC, Chipset.crc, 4); + // update CARDSTAT + Chipset.IORam[CARDSTAT] = (Chipset.IORam[CARDCTL] & ECDT) ? Chipset.cards_status : 0; // update SRQ2 - UpdateKdnBit(); - IOBit(SRQ2,NINT2,Chipset.IORam[SRQ1] == 0 && (Chipset.IORam[SRQ2] & LSRQ) == 0); - IOBit(SRQ2,NINT,(Chipset.HST & MP) == 0); + ReadIO(a,SRQ2,1); // update timer1 Chipset.IORam[TIMER1] = Chipset.t1; // update timer2 @@ -596,12 +656,34 @@ VOID Npeek(BYTE *a, DWORD d, UINT s) memcpy(a, Chipset.IORam+v, c); } else - // 12.11.99 cg, end of new part { u = d>>12; v = d&0xFFF; c = MIN(s,0x1000-v); - if ((p=RMap[u]) != NULL) memcpy(a, p+v, c); + // Flash memory Read access + if (cCurrentRomType == 'X' && (Chipset.IORam[LCR] & LED) && M_P2 == eMap) + { + FlashRead(a, FlashROMAddr(d), s); + } + else + { + if ((p=RMap[u]) != NULL) // module mapped + { + memcpy(a, p+v, c); + } + // simulate open data bus + else // open data bus + { + for (u=0; u>12; v = d&0xFFF; c = MIN(s,0x1000-v); - if ( (cCurrentRomType != 'S') - && (Chipset.BSCfig && (((u^Chipset.BSBase) & ~Chipset.BSSize) == 0)) - && !(Chipset.P0Cfig && (((u^Chipset.P0Base) & ~Chipset.P0Size) == 0)) - && !(Chipset.P1Cfig && (((u^Chipset.P1Base) & ~Chipset.P1Size) == 0)) - ) + // bank switcher access + if (cCurrentRomType != 'S' && M_BS == eMap) { if (cCurrentRomType == 'G') // HP48GX { Chipset.Bank_FF = v+c; // save FF value Map(Chipset.P2Base,Chipset.P2End); } - else // HP49G + if (cCurrentRomType == 'X') // HP49G { - if (0!=c) RomSwitch(d+c-1); + RomSwitch(v+c); } } - if ((p=RMap[u]) != NULL) // module mapped + // Flash memory Read access + if (cCurrentRomType == 'X' && (Chipset.IORam[LCR] & LED) && M_P2 == eMap) { - memcpy(a, p+v, c); - for (u=0; u>12; v = d&0xFFF; c = MIN(s,0x1000-v); - if ( (cCurrentRomType != 'S') - && (Chipset.BSCfig && (((u^Chipset.BSBase) & ~Chipset.BSSize) == 0)) - && !(Chipset.P0Cfig && (((u^Chipset.P0Base) & ~Chipset.P0Size) == 0)) - && !(Chipset.P1Cfig && (((u^Chipset.P1Base) & ~Chipset.P1Size) == 0)) - ) + // bank switcher access + if (cCurrentRomType != 'S' && M_BS == eMap) { - if (cCurrentRomType == 'G') // HP48GX - { - BOOL bMap = FALSE; + BOOL bWrite = FALSE; + // write enabled + if (Chipset.cards_status & PORT2_WRITE) + { + Chipset.Bank_FF = v+c-1;// save FF value + bWrite = TRUE; // bank switched + } + else // write disabled, so latch last read cycle + { if ((v & 1) != 0) // low address odd { Chipset.Bank_FF = v;// save FF value - bMap = TRUE; // bank switched + bWrite = TRUE; // bank switched } if ((v+c & 1) != 0) // high address odd { Chipset.Bank_FF = v+c-1;// save FF value - bMap = TRUE; // bank switched + bWrite = TRUE; // bank switched } - - if (bMap) Map(Chipset.P2Base,Chipset.P2End); } - else // HP49G + + if (bWrite) // write cycle? { - if (0!=c) RomSwitch(d+c-1); + // HP48GX + if (cCurrentRomType == 'G') Map(Chipset.P2Base,Chipset.P2End); + // HP49G + if (cCurrentRomType == 'X') RomSwitch(Chipset.Bank_FF); } } - if ((p=WMap[u]) != NULL) memcpy(p+v, a, c); + // Flash memory Write access + if (cCurrentRomType == 'X' && (Chipset.IORam[LCR] & LED) && M_P2 == eMap) + { + DWORD dwLinAddr = FlashROMAddr(d); + + FlashWrite(a, dwLinAddr, s); + + #if defined DEBUG_FLASH + { + char buffer[256]; + DWORD j; + int i; + + i = wsprintf(buffer,"%.5lx: Flash Write: %.5x (%.6x),%u = ",Chipset.pc,d,dwLinAddr,s); + for (j = 0;j < s;++j,++i) + { + buffer[i] = a[j]; + if (buffer[i] > 9) buffer[i] += 0x27; + buffer[i] += '0'; + } + buffer[i++] = '\n'; + buffer[i] = 0; + OutputDebugString(buffer); + } + #endif + } + else + { + if ((p=WMap[u]) != NULL) memcpy(p+v, a, c); + } } a+=c; d+=c; @@ -805,6 +937,9 @@ VOID IOBit(DWORD d, BYTE b, BOOL s) // set/clear bit in I/O section VOID ReadIO(BYTE *a, DWORD d, DWORD s) { + BOOL bNINT; + BOOL bNINT2; + BYTE c = 0xFF; // LINECOUNT not initialized BOOL rbr_acc = FALSE; // flag to receive data @@ -844,11 +979,27 @@ VOID ReadIO(BYTE *a, DWORD d, DWORD s) #endif *a |= UckBit(*a); // add UCK bit to BAUD rate register break; -// case 0x0E: *a = Chipset.IORam[d]; break; - case 0x0F: - // On a HP49G Chipset.cards_status bust always be 0xF - _ASSERT(cCurrentRomType!='X' || Chipset.cards_status == 0xF); - *a = Chipset.cards_status; + case 0x0E: + // SMP is !NINT and SWINT is always 0 + // clear SMP and SWINT bit + Chipset.IORam[d] &= (ECDT | RCDT); + // SMP is !NINT + if ((Chipset.IORam[SRQ2] & NINT) == 0) + Chipset.IORam[d] |= SMP; + *a = Chipset.IORam[d]; + break; + case 0x0F: + // card detection disabled + if ((Chipset.IORam[CARDCTL] & ECDT) == 0) + { + *a = 0; // no cards + } + else + { + // on a HP49G Chipset.cards_status bust always be 0xF + _ASSERT(cCurrentRomType!='X' || Chipset.cards_status == 0xF); + *a = Chipset.cards_status; + } break; case 0x10: // IO CONTROL *a = Chipset.IORam[d]; // return IO CONTROL value @@ -887,7 +1038,7 @@ VOID ReadIO(BYTE *a, DWORD d, DWORD s) case 0x15: // RBR MSB Chipset.IORam[RCS]&=~RBF; *a = Chipset.IORam[d]; // return RBR value - UpdateUSRQ(); // 25.10.99 cg, bugfix, update USRQ + UpdateUSRQ(); // update USRQ rbr_acc = TRUE; // search for new RBR value #if defined DEBUG_SERIAL { @@ -903,10 +1054,18 @@ VOID ReadIO(BYTE *a, DWORD d, DWORD s) break; case 0x19: // SREQ? MSB UpdateKdnBit(); // update KDN bit - // 25.10.99 cg, bugfix, update NINT2 bit - IOBit(SRQ2,NINT2,Chipset.IORam[SRQ1] == 0 && (Chipset.IORam[SRQ2] & LSRQ) == 0); - // 25.10.99 cg, bugfix, update NINT bit - IOBit(SRQ2,NINT,(Chipset.HST & MP) == 0); + bNINT2 = Chipset.IORam[SRQ1] == 0 && (Chipset.IORam[SRQ2] & LSRQ) == 0; + bNINT = (Chipset.IORam[SRQ2] & NINT) != 0; + // card detection off and timer running + if ((Chipset.IORam[CARDCTL] & ECDT) == 0 && (Chipset.IORam[TIMER2_CTRL] & RUN) != 0) + { + // state of CDT2 + bNINT2 = bNINT2 && (Chipset.cards_status & (P2W|P2C)) != P2C; + // state of CDT1 + bNINT = bNINT && (Chipset.cards_status & (P1W|P1C)) != P1C; + } + IOBit(SRQ2,NINT2,bNINT2); + IOBit(SRQ2,NINT,bNINT); // no break! case 0x18: // SREQ? LSB *a = Chipset.IORam[d]; // return SREQ value @@ -1149,29 +1308,51 @@ VOID WriteIO(BYTE *a, DWORD d, DWORD s) #endif break; -// 0010E = NS:CARDCTRL +// 0010E = NS:CARDCTL // 0010E @ [ECDT RCDT SMP SWINT] (read/write) // 0010E @ Enable Card Det., Run Card Det., Set Module Pulled, Software interrupt case 0x0E: - Chipset.IORam[d]=c; -#if 1 - if ( (RMap[4]!=(pbyRom+0x4000)) && (12 != c) ) - { - c |= 0x10; - } -#endif - if (c&1) - { - Chipset.SoftInt = TRUE; - bInterrupt = TRUE; - } - if (c&2) - { - Chipset.HST |= 8; // MP -// Chipset.SoftInt = TRUE; -// bInterrupt = TRUE; - } - break; + if (c & SWINT) // SWINT bit set + { + c &= (ECDT | RCDT | SMP); // clear SWINT bit + Chipset.SoftInt = TRUE; + bInterrupt = TRUE; + } + if ((c & SMP) == 0) // SMP bit cleared + { + BOOL bNINT = TRUE; // ack NINT interrupt -> NINT high + // card detect disabled and CDT1 low -> retrigger + if ((c & ECDT) == 0 && (Chipset.IORam[TIMER2_CTRL] & RUN) != 0) + bNINT = (Chipset.cards_status & (P1W|P1C)) != P1C; + IOBit(SRQ2,NINT,bNINT); + } + // falling edge of Enable Card Detect bit and timer running + if ( ((c^Chipset.IORam[d]) & ECDT) != 0 && (c & ECDT) == 0 + && (Chipset.IORam[TIMER2_CTRL] & RUN) != 0) + { + BOOL bNINT = (Chipset.IORam[SRQ2] & NINT) != 0; + // card in slot1 isn't Read Only + if ((Chipset.cards_status & (P1W|P1C)) != P1C) + { + // use random state of NINT line + bNINT = bNINT && (ReadT2() & 0x1) != 0; + } + IOBit(SRQ2,NINT,bNINT); + + Chipset.HST |= MP; // set Module Pulled + + // Port1 and Port2 plugged and writeable or NINT2/NINT interrupt + if ( Chipset.cards_status != (P2W|P1W|P2C|P1C) + || (Chipset.IORam[SRQ2] & NINT2) == 0 + || (Chipset.IORam[SRQ2] & NINT ) == 0 + ) + { + Chipset.SoftInt = TRUE; // set interrupt + bInterrupt = TRUE; + } + } + Chipset.IORam[d]=c; + break; // 0010F = NS:CARDSTATUS // 0010F @ [P2W P1W P2C P1C] (read-only) Port 2 writable .. Port 1 inserted @@ -1182,7 +1363,7 @@ VOID WriteIO(BYTE *a, DWORD d, DWORD s) // 00110 @ Serial On, Interrupt On Recv.Buf.Empty, Full, Buzy case 0x10: Chipset.IORam[d]=c; - UpdateUSRQ(); // 25.10.99 cg, bugfix, update USRQ + UpdateUSRQ(); // update USRQ ioc_acc = TRUE; // new IOC value #if defined DEBUG_SERIAL { @@ -1244,7 +1425,7 @@ VOID WriteIO(BYTE *a, DWORD d, DWORD s) case 0x17: Chipset.IORam[d]=c; Chipset.IORam[TCS]|=TBF; - UpdateUSRQ(); // 25.10.99 cg, bugfix, update USRQ + UpdateUSRQ(); // update USRQ tbr_acc = TRUE; // new TBR value #if defined DEBUG_SERIAL { @@ -1282,7 +1463,22 @@ VOID WriteIO(BYTE *a, DWORD d, DWORD s) // 0011C = NS:LCR // 0011C @ Led Control Register [LED ELBE LBZ LBF] (Setting LED is draining) - case 0x1C: Chipset.IORam[d]=c; break; + case 0x1C: + // HP49G new mapping on LED bit change + if (cCurrentRomType=='X' && ((c^Chipset.IORam[d])&LED)) + { + Chipset.IORam[d]=c; // save new value for mapping + Map(Chipset.P2Base,Chipset.P2End); // new ROM mapping + #if defined DEBUG_FLASH + { + char buffer[256]; + wsprintf(buffer,"%.5lx: NCE3: R%cM\n",Chipset.pc,(c&LED) ? 'O' : 'A'); + OutputDebugString(buffer); + } + #endif + } + Chipset.IORam[d]=c; + break; // 0011D = NS:LBR // 0011D @ Led Buffer Register [0 0 0 LBO] (bits 1-3 read zero) @@ -1325,8 +1521,7 @@ VOID WriteIO(BYTE *a, DWORD d, DWORD s) // 00128 @ Normally has 55 -> Menu starts at display row 56 case 0x28: case 0x29: - // when G(X) and writing to register 0x29 - if (cCurrentRomType != 'S' && d == 0x29) + if (d == 0x29) { if ((c^Chipset.IORam[d])&8) // DA19 changed { @@ -1359,6 +1554,7 @@ VOID WriteIO(BYTE *a, DWORD d, DWORD s) StartTimers(); else StopTimers(); + disp |= DISP_ANNUN; // update annunciators break; // 00130 = NS:MENUADDR diff --git a/sources/Emu48/OPCODES.C b/sources/Emu48/OPCODES.C index f0ad488..fa24b03 100644 --- a/sources/Emu48/OPCODES.C +++ b/sources/Emu48/OPCODES.C @@ -10,6 +10,7 @@ #include "pch.h" #include "Emu48.h" #include "Opcodes.h" +#include "io.h" // I/O register definitions #define w Chipset #define GOYES3 {if(w.carry) o_goyes3(I);else{w.pc+=2;return;}} @@ -19,6 +20,10 @@ #include "Ops.h" +// Fields start and length +UINT F_s[16] = {0/*P*/,0,2,0,15,3,0,0,0,0,0,0,0,0,0,0}; +UINT F_l[16] = {1,1/*P+1*/,1,3,1,12,2,16,0,0,0,0,0,0,0,5}; + VOID o00(LPBYTE I) // RTNSXM { w.cycles+=9; @@ -84,7 +89,7 @@ VOID o07(LPBYTE I) // C=RSTK VOID o08(LPBYTE I) // CLRST { - w.cycles+=5; // 22.10.99 cg, bugfix, fixed no. of cycles + w.cycles+=5; w.pc+=2; memset(w.ST, 0, 3); return; @@ -92,7 +97,7 @@ VOID o08(LPBYTE I) // CLRST VOID o09(LPBYTE I) // C=ST { - w.cycles+=5; // 22.10.99 cg, bugfix, fixed no. of cycles + w.cycles+=5; w.pc+=2; memcpy(w.C, w.ST, 3); return; @@ -100,7 +105,7 @@ VOID o09(LPBYTE I) // C=ST VOID o0A(LPBYTE I) // ST=C { - w.cycles+=5; // 22.10.99 cg, bugfix, fixed no. of cycles + w.cycles+=5; w.pc+=2; memcpy(w.ST, w.C, 3); return; @@ -108,7 +113,7 @@ VOID o0A(LPBYTE I) // ST=C VOID o0B(LPBYTE I) // CSTEX { - w.cycles+=5; // 22.10.99 cg, bugfix, fixed no. of cycles + w.cycles+=5; w.pc+=2; Nxchg(w.C, w.ST, 3); return; @@ -276,11 +281,29 @@ VOID o0F(LPBYTE I) // RTI INTERRUPT; // restart interrupt handler } + // low interrupt lines + { + BOOL bNINT2 = Chipset.IORam[SRQ1] == 0 && (Chipset.IORam[SRQ2] & LSRQ) == 0; + BOOL bNINT = (Chipset.IORam[SRQ2] & NINT) != 0; + + // card detection off and timer running + if ((Chipset.IORam[CARDCTL] & ECDT) == 0 && (Chipset.IORam[TIMER2_CTRL] & RUN) != 0) + { + // state of CDT2 + bNINT2 = bNINT2 && (Chipset.cards_status & (P2W|P2C)) != P2C; + // state of CDT1 + bNINT = bNINT && (Chipset.cards_status & (P1W|P1C)) != P1C; + } + + if (!bNINT2 || !bNINT) // NINT2 or NINT interrupt line low + INTERRUPT; // restart interrupt handler + } + // restart interrupt handler when timer interrupt - if (w.IORam[0x2E]&0x02) // INT bit of timer1 is set + if (w.IORam[TIMER1_CTRL]&INTR) // INT bit of timer1 is set ReadT1(); // check for int - if (w.IORam[0x2F]&0x02) // INT bit of timer2 is set + if (w.IORam[TIMER2_CTRL]&INTR) // INT bit of timer2 is set ReadT2(); // check for int return; } @@ -834,7 +857,7 @@ VOID o3X(LPBYTE I) // LCHEX { UINT n=I[1]+1; UINT d=16-w.P; - w.cycles+=3+n; // 22.10.99 cg, bugfix, fixed no. of cycles + w.cycles+=3+n; w.pc+=2; I+=2; // UNSAFE if (n<=d) @@ -1010,7 +1033,6 @@ VOID o8080(LPBYTE I) // INTON w.cycles+=5; w.pc+=4; // cpu cycles at start of 1ms key handler - // 22.11.99 cg, changed, DWORD casting w.dwKdnCycles = (DWORD) (w.cycles & 0xFFFFFFFF); w.intk = TRUE; @@ -1029,7 +1051,6 @@ VOID o80810(LPBYTE I) // RSI ScanKeyboard(TRUE); // one input bit high ? // enable KDN update - // 22.11.99 cg, changed, DWORD casting w.dwKdnCycles = (DWORD) (w.cycles & 0xFFFFFFFF) - (DWORD) T2CYCLES * 16; if (w.in && w.inte == FALSE) // key interrupt pending @@ -1083,7 +1104,7 @@ VOID o8085n(LPBYTE I) // ABIT=1 n VOID o8086n(LPBYTE I) // ?ABIT=0 n { - w.cycles+=9; // 22.10.99 cg, bugfix, fixed no. of cycles + w.cycles+=9; w.pc+=5; Tbit0(w.A, I[4]); GOYES5; @@ -1091,7 +1112,7 @@ VOID o8086n(LPBYTE I) // ?ABIT=0 n VOID o8087n(LPBYTE I) // ?ABIT=1 n { - w.cycles+=9; // 22.10.99 cg, bugfix, fixed no. of cycles + w.cycles+=9; w.pc+=5; Tbit1(w.A, I[4]); GOYES5; @@ -1115,7 +1136,7 @@ VOID o8089n(LPBYTE I) // CBIT=1 n VOID o808An(LPBYTE I) // ?CBIT=0 n { - w.cycles+=9; // 22.10.99 cg, bugfix, fixed no. of cycles + w.cycles+=9; w.pc+=5; Tbit0(w.C, I[4]); GOYES5; @@ -1123,7 +1144,7 @@ VOID o808An(LPBYTE I) // ?CBIT=0 n VOID o808Bn(LPBYTE I) // ?CBIT=1 n { - w.cycles+=9; // 22.10.99 cg, bugfix, fixed no. of cycles + w.cycles+=9; w.pc+=5; Tbit1(w.C, I[4]); GOYES5; @@ -1131,8 +1152,10 @@ VOID o808Bn(LPBYTE I) // ?CBIT=1 n VOID o808C(LPBYTE I) // PC=(A) { + BYTE p[5]; w.cycles+=23; - w.pc=Read5(Npack(w.A,5)); + Nread(p,Npack(w.A,5),5); // read (A) and update CRC + w.pc=Npack(p,5); return; } @@ -1147,8 +1170,10 @@ VOID o808D(LPBYTE I) // BUSCD VOID o808E(LPBYTE I) // PC=(C) { + BYTE p[5]; w.cycles+=23; - w.pc=Read5(Npack(w.C,5)); + Nread(p,Npack(w.C,5),5); // read (C) and update CRC + w.pc=Npack(p,5); return; } @@ -2085,7 +2110,7 @@ VOID o8Ed4(LPBYTE I) // GOSUBL #dddd DWORD d=Npack(I+2,4); rstkpush(w.pc+6); if (d&0x8000) w.pc-=0xfffa-d; else w.pc+=d+6; - w.cycles+=14; // 22.10.99 cg, bugfix, fixed no. of cycles + w.cycles+=14; w.pc&=0xFFFFF; return; } diff --git a/sources/Emu48/OPCODES.H b/sources/Emu48/OPCODES.H index 56a3965..590b37c 100644 --- a/sources/Emu48/OPCODES.H +++ b/sources/Emu48/OPCODES.H @@ -7,17 +7,14 @@ * */ -// HST bits -#define XM 1 -#define SB 2 -#define SR 4 -#define MP 8 - #define PCHANGED ((void)(F_s[0]=Chipset.P,F_l[1]=Chipset.P+1)) #define INTERRUPT ((void)(Chipset.SoftInt=TRUE,bInterrupt=TRUE)) #define T2CYCLES ((cCurrentRomType=='S')?dwSXCycles:dwGXCycles) +extern UINT F_s[16]; +extern UINT F_l[16]; + extern VOID o00(LPBYTE I); // RTNSXM extern VOID o01(LPBYTE I); // RTN extern VOID o02(LPBYTE I); // RTNSC diff --git a/sources/Emu48/OPS.H b/sources/Emu48/OPS.H index d4bd351..b761b63 100644 --- a/sources/Emu48/OPS.H +++ b/sources/Emu48/OPS.H @@ -8,10 +8,6 @@ */ // #pragma warning(disable:4244) -// Fields start and length -static UINT F_s[16] = {0/*P*/,0,2,0,15,3,0,0,0,0,0,0,0,0,0,0}; -static UINT F_l[16] = {1,1/*P+1*/,1,3,1,12,2,16,0,0,0,0,0,0,0,5}; - #define NFunpack(a, b, f) Nunpack((a)+F_s[f], b, F_l[f]) #define NFread(a, b, f) Nread((a)+F_s[f], b, F_l[f]) #define NFwrite(a, b, f) Nwrite((a)+F_s[f], b, F_l[f]) @@ -42,10 +38,24 @@ static UINT F_l[16] = {1,1/*P+1*/,1,3,1,12,2,16,0,0,0,0,0,0,0,5}; static __inline LPBYTE FASTPTR(DWORD d) { + static BYTE pbyNULL[16]; + LPBYTE lpbyPage; + + d &= 0xFFFFF; // handle address overflows + if ((Chipset.IOCfig)&&((d&0xFFFC0)==Chipset.IOBase)) return Chipset.IORam+d-Chipset.IOBase; - return RMap[d>>12]+(d&0xFFF); + if((lpbyPage = RMap[d>>12]) != NULL) // page valid + { + lpbyPage += d & 0xFFF; // full address + } + else + { + lpbyPage = pbyNULL; // memory allocation + Npeek(lpbyPage, d, 13); // fill with data (LA(8) = longest opcode) + } + return lpbyPage; } static __inline void rstkpush(DWORD d) @@ -90,19 +100,34 @@ static __inline void Nxchg(BYTE *a, BYTE *b, UINT s) static __inline void Ninc(BYTE *a, UINT s, UINT d) { UINT i; - BYTE cBase = Chipset.mode_dec ? 10 : 16; - for (i=d; i= 10) a[i&0xf] &= 0x7; + + a[i&0xf] += c; + c = (a[i&0xf] >= 10); + if (c) a[i&0xf] -= 10; } - a[i&0xf] -= cBase; + Chipset.carry = (c==1); + } + else + { + for (i=d; i= 10) + a[i] &= 0x7; + a[i] += b[i] + c; if (a[i] >= cBase) { @@ -195,11 +224,11 @@ static __inline void Nsub(BYTE *a, BYTE *b, UINT s) UINT i; BYTE c = 0; BYTE cBase = Chipset.mode_dec ? 10 : 16; - + for (i=0; i= cBase) + if ((a[i&0xf] & 0xF0) != 0) // check overflow { a[i] += cBase; c = 1; @@ -219,7 +248,7 @@ static __inline void Nrsub(BYTE *a, BYTE *b, UINT s) for (i=0; i= cBase) + if ((a[i&0xf] & 0xF0) != 0) // check overflow { a[i] += cBase; c = 1; @@ -244,7 +273,12 @@ static __inline void Nnot(BYTE *a, UINT s) { BYTE cBase = Chipset.mode_dec ? 9 : 15; - while (s--) a[s] = cBase - a[s]; + while (s--) + { + a[s] = cBase - a[s]; + if ((a[s] & 0xF0) != 0) // check overflow (dec mode only) + a[s] &= 0x7; + } Chipset.carry = FALSE; } @@ -257,8 +291,14 @@ static __inline void Nneg(BYTE *a, UINT s) if (Chipset.carry = (i!=s)) // value was non-zero { a[i] = cBase - a[i]; // first non-zero digit + if ((a[i] & 0xF0) != 0) // check overflow (dec mode only) + a[i] &= 0x7; for (--cBase, ++i; i 0); // first stack elememt is one if (l==0) return 0; + if (METAKERNEL) ++l; // Metakernel support stkp = Read5(DSKTOP) + (l-1)*5; return Read5(stkp); } @@ -256,6 +288,7 @@ VOID RPL_Replace(DWORD n) DWORD stkp; stkp = Read5(DSKTOP); + if (METAKERNEL) stkp+=5; // Metakernel support Write5(stkp,n); return; } @@ -264,14 +297,23 @@ VOID RPL_Replace(DWORD n) VOID RPL_Push(DWORD n) { DWORD stkp, avmem; - + avmem = Read5(AVMEM); // amount of free memory if (avmem==0) return; // no memory free avmem--; // fetch memory Write5(AVMEM,avmem); // save new amount of free memory stkp = Read5(DSKTOP); // get pointer to stack level 1 - stkp-=5; // fetch new stack entry - Write5(stkp,n); // save pointer to new object on stack level 1 + if (METAKERNEL) // Metakernel running ? + { + Write5(stkp-5,Read5(stkp)); // copy object pointer of stack level 1 to new stack level 1 entry + Write5(stkp,n); // save pointer to new object on stack level 2 + stkp-=5; // fetch new stack entry + } + else + { + stkp-=5; // fetch new stack entry + Write5(stkp,n); // save pointer to new object on stack level 1 + } Write5(DSKTOP,stkp); // save new pointer to stack level 1 return; } diff --git a/sources/Emu48/SERIAL.C b/sources/Emu48/SERIAL.C index fab7f93..ed8283f 100644 --- a/sources/Emu48/SERIAL.C +++ b/sources/Emu48/SERIAL.C @@ -8,34 +8,32 @@ */ #include "pch.h" #include "Emu48.h" -#include "io.h" // 24.10.99 cg, renamed from Serial.h +#include "io.h" #define INTERRUPT ((void)(Chipset.SoftInt=TRUE,bInterrupt=TRUE)) -// 25.10.99 cg, new, state of USRQ +// state of USRQ #define NINT2ERBZ ((Chipset.IORam[IOC] & ERBZ) != 0 && (Chipset.IORam[RCS] & RBZ) != 0) #define NINT2ERBF ((Chipset.IORam[IOC] & ERBF) != 0 && (Chipset.IORam[RCS] & RBF) != 0) #define NINT2ETBE ((Chipset.IORam[IOC] & ETBE) != 0 && (Chipset.IORam[TCS] & TBF) == 0) #define NINT2USRQ (NINT2ERBZ || NINT2ERBF || NINT2ETBE) -static OVERLAPPED os = { 0 }; static HANDLE hComm = NULL; static HANDLE hCThread = NULL; static DWORD lSerialThreadId = 0; -static BOOL bReading = TRUE; static WORD wPort = PORT_CLOSE; +static BOOL bReading; static BYTE cBuffer[128]; static WORD nRp; static DWORD dwBytesRead = 0L; -// static CRITICAL_SECTION csRecv; // 24.10.99 cg, moved to main function - static DWORD WINAPI SerialThread(LPVOID pParam) { DWORD dwEvent; - SetCommMask(hComm,EV_RXCHAR); // event on RX + + bReading = TRUE; // flag for SerialThread started while (bReading) { _ASSERT(hComm != NULL); @@ -83,16 +81,16 @@ VOID CommOpen(LPSTR strWirePort,LPSTR strIrPort) if(hComm != INVALID_HANDLE_VALUE) { - // InitializeCriticalSection(&csRecv); - wPort = (Chipset.IORam[IR_CTRL] & EIRU) ? PORT_IR : PORT_WIRE; SetCommTimeouts(hComm,&CommTimeouts); CommSetBaud(); // set event RXD handler - bReading = TRUE; + bReading = FALSE; + SetCommMask(hComm,EV_RXCHAR); // event on RX hCThread = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)&SerialThread,NULL,0,&lSerialThreadId); _ASSERT(lSerialThreadId); + while (!bReading) Sleep(0); // wait for SerialThread started } else hComm = NULL; @@ -112,6 +110,7 @@ VOID CommClose(VOID) { if (hComm != NULL) // port open { + Sleep(25); // workaround to fix problems with some Kermit server bReading = FALSE; // kill read thread SetCommMask(hComm,0L); // clear all events and force WaitCommEvent to return while (lSerialThreadId != 0) Sleep(0); // wait for termination @@ -120,7 +119,6 @@ VOID CommClose(VOID) #if defined DEBUG_SERIAL OutputDebugString("COM port closed.\n"); #endif - // DeleteCriticalSection(&csRecv); wPort = PORT_CLOSE; } return; @@ -165,7 +163,7 @@ VOID CommSetBaud(VOID) return; } -VOID UpdateUSRQ(VOID) // 25.10.99 cg, new, USRQ handling +VOID UpdateUSRQ(VOID) // USRQ handling { IOBit(SRQ1,USRQ,NINT2USRQ); // update USRQ bit return; @@ -173,7 +171,8 @@ VOID UpdateUSRQ(VOID) // 25.10.99 cg, new, USRQ handling VOID CommTransmit(VOID) { - DWORD dwWritten; + OVERLAPPED os = { 0 }; + DWORD dwWritten; BYTE tbr = (Chipset.IORam[TBR_MSB] << 4) | Chipset.IORam[TBR_LSB]; @@ -188,7 +187,6 @@ VOID CommTransmit(VOID) } #endif - // 23.10.99 cg, bugfix, add serial loopback support if (Chipset.IORam[TCS] & LPB) // is loopback bit set { cBuffer[nRp+dwBytesRead] = tbr; // save character in receive buffer @@ -196,13 +194,17 @@ VOID CommTransmit(VOID) CommReceive(); // receive byte available } - // 23.10.99 cg, end of implementation if (hComm != NULL) // com port open + { + os.hEvent = CreateEvent(NULL,TRUE,FALSE,NULL); WriteFile(hComm,(LPCVOID) &tbr,1,&dwWritten,&os); + GetOverlappedResult(hComm,&os,&dwWritten,TRUE); + CloseHandle(os.hEvent); + } Chipset.IORam[TCS] &= (~TBF); // clear transmit buffer - UpdateUSRQ(); // 25.10.99 cg, bugfix, update USRQ bit + UpdateUSRQ(); // update USRQ bit if (Chipset.IORam[IOC] & ETBE) // interrupt on transmit buffer empty INTERRUPT; return; @@ -210,6 +212,8 @@ VOID CommTransmit(VOID) VOID CommReceive(VOID) { + OVERLAPPED os = { 0 }; + if (!(Chipset.IORam[IOC] & SON)) // UART off { dwBytesRead = 0L; // no bytes received @@ -222,7 +226,7 @@ VOID CommReceive(VOID) if (Chipset.IORam[RCS] & RBF) // receive buffer full break; - // 23.10.99 cg, bugfix, reject reading if com port is closed and not whole operation + // reject reading if com port is closed and not whole operation if (hComm && dwBytesRead == 0L) // com port open and buffer empty { if(ReadFile(hComm,&cBuffer,sizeof(cBuffer),&dwBytesRead,&os) == FALSE) @@ -251,7 +255,7 @@ VOID CommReceive(VOID) --dwBytesRead; Chipset.IORam[RCS] |= RBF; // receive buffer full - UpdateUSRQ(); // 25.10.99 cg, bugfix, update USRQ bit + UpdateUSRQ(); // update USRQ bit if (Chipset.IORam[IOC] & ERBF) // interrupt on recv buffer full INTERRUPT; } diff --git a/sources/Emu48/SETTINGS.C b/sources/Emu48/SETTINGS.C index 0c909fa..7c4e3e9 100644 --- a/sources/Emu48/SETTINGS.C +++ b/sources/Emu48/SETTINGS.C @@ -9,6 +9,7 @@ */ #include "pch.h" #include "Emu48.h" +#include "i28f160.h" #define EMU48_INI "Emu48.ini" @@ -31,14 +32,19 @@ VOID ReadSettings(VOID) GetPrivateProfileString("Port2","Filename","SHARED.BIN",szPort2Filename,sizeof(szPort2Filename),EMU48_INI); // KML bAlwaysDisplayLog = GetPrivateProfileInt("KML","AlwaysDisplayLog",bAlwaysDisplayLog,EMU48_INI); + // Disassebler + disassembler_mode = GetPrivateProfileInt("Disassembler","Mnemonics",disassembler_mode,EMU48_INI); // Emulator bRealSpeed = GetPrivateProfileInt("Emulator","RealSpeed",0,EMU48_INI); - dwSXCycles = GetPrivateProfileInt("Emulator","SXCycles",82,EMU48_INI); - dwGXCycles = GetPrivateProfileInt("Emulator","GXCycles",123,EMU48_INI); + dwSXCycles = GetPrivateProfileInt("Emulator","SXCycles",dwSXCycles,EMU48_INI); + dwGXCycles = GetPrivateProfileInt("Emulator","GXCycles",dwGXCycles,EMU48_INI); SetSpeed(bRealSpeed); // set speed // Serial GetPrivateProfileString("Serial","Wire",NO_SERIAL,szSerialWire,sizeof(szSerialWire),EMU48_INI); GetPrivateProfileString("Serial","Ir",NO_SERIAL,szSerialIr,sizeof(szSerialIr),EMU48_INI); + // ROM + bRomWriteable = GetPrivateProfileInt("ROM","Writeable",TRUE,EMU48_INI); + bWP = GetPrivateProfileInt("ROM","WP#",FALSE,EMU48_INI); return; } @@ -53,6 +59,8 @@ VOID WriteSettings(VOID) WritePrivateProfileString("Port2","Filename",szPort2Filename,EMU48_INI); // KML WritePrivateProfileInt("KML","AlwaysDisplayLog",bAlwaysDisplayLog,EMU48_INI); + // Disassebler + WritePrivateProfileInt("Disassembler","Mnemonics",disassembler_mode,EMU48_INI); // Emulator WritePrivateProfileInt("Emulator","RealSpeed",bRealSpeed,EMU48_INI); WritePrivateProfileInt("Emulator","SXCycles",dwSXCycles,EMU48_INI); @@ -60,6 +68,8 @@ VOID WriteSettings(VOID) // Serial WritePrivateProfileString("Serial","Wire",szSerialWire,EMU48_INI); WritePrivateProfileString("Serial","Ir",szSerialIr,EMU48_INI); + // ROM + WritePrivateProfileInt("ROM","Writeable",bRomWriteable,EMU48_INI); return; } diff --git a/sources/Emu48/TIMER.C b/sources/Emu48/TIMER.C index 2f73b5a..2f754db 100644 --- a/sources/Emu48/TIMER.C +++ b/sources/Emu48/TIMER.C @@ -8,7 +8,7 @@ */ #include "pch.h" #include "Emu48.h" -#include "io.h" // 24.10.99 cg, new, I/O definitions +#include "io.h" // I/O definitions #define AUTO_OFF 10 // Time in minutes for 'auto off' @@ -30,13 +30,15 @@ static BOOL bOutRange = FALSE; // flag if timer value out of range static UINT uT1TimerId = 0; static UINT uT2TimerId = 0; -static BOOL bNINT2T1 = FALSE; // 24.10.99 cg, new, state of NINT2 affected form Timer1 -static BOOL bNINT2T2 = FALSE; // 24.10.99 cg, new, state of NINT2 affected form Timer2 +static BOOL bNINT2T1 = FALSE; // state of NINT2 affected from timer1 +static BOOL bNINT2T2 = FALSE; // state of NINT2 affected from timer2 static BOOL bAccurateTimer; // flag if accurate timer is used static LARGE_INTEGER lT2Ref; // counter value at timer2 start static TIMECAPS tc; // timer information +static BOOL bT2Val; // last access values valid + static __inline MAX(int a, int b) {return (a>b)?a:b;} static void CALLBACK TimeProc(UINT uEventId, UINT uMsg, DWORD dwUser, DWORD dw1, DWORD dw2); @@ -46,22 +48,51 @@ static DWORD CalcT2(VOID) // calculate timer2 value DWORD dwT2 = Chipset.t2; // get value from chipset if (bStarted) // timer2 running { + static DWORD dwT2Ref; // timer2 value at last timer2 access + static DWORD dwT2Cyc; // cpu cycle counter at last timer2 access + LARGE_INTEGER lT2Act; + DWORD dwT2Dif; + QueryPerformanceCounter(&lT2Act); // actual time // calculate ticks since reference point dwT2 -= (DWORD) (((lT2Act.QuadPart - lT2Ref.QuadPart) * T2_FREQ) / lFreq.QuadPart); + + // 2nd timer call in a time 32ms time frame + dwT2Dif = dwT2Ref - dwT2; + if (bT2Val && dwT2Dif > 0x01 && dwT2Dif <= 0x100) + { + #define CYC_PER_TICK (2 * 72) + DWORD dwT2Ticks = ((DWORD) (Chipset.cycles & 0xFFFFFFFF) - dwT2Cyc) / CYC_PER_TICK; + + if (dwT2Ticks >= dwT2Dif) + { + dwT2Cyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF); + } + else + { + dwT2 = dwT2Ref - dwT2Ticks; + dwT2Cyc += dwT2Ticks * CYC_PER_TICK; + } + #undef CYC_PER_TICK + } + else + { + dwT2Cyc = (DWORD) (Chipset.cycles & 0xFFFFFFFF); + } + dwT2Ref = dwT2; + bT2Val = TRUE; // access values valid } return dwT2; } static VOID CheckT1(BYTE nT1) { - // 24.10.99 cg, bugfix, implementation of TSRQ + // implementation of TSRQ bNINT2T1 = (Chipset.IORam[TIMER1_CTRL]&INTR) != 0 && (nT1&8) != 0; IOBit(SRQ1,TSRQ,bNINT2T1 || bNINT2T2); - // 24.10.99 cg, end of implementation if ((nT1&8) == 0) // timer1 MSB not set { @@ -94,10 +125,9 @@ static VOID CheckT1(BYTE nT1) static VOID CheckT2(DWORD dwT2) { - // 24.10.99 cg, bugfix, implementation of TSRQ + // implementation of TSRQ bNINT2T2 = (Chipset.IORam[TIMER2_CTRL]&INTR) != 0 && (dwT2&0x80000000) != 0; IOBit(SRQ1,TSRQ,bNINT2T1 || bNINT2T2); - // 24.10.99 cg, end of implementation if ((dwT2&0x80000000) == 0) // timer2 MSB not set { @@ -134,6 +164,7 @@ static VOID RescheduleT2(BOOL bRefPoint) _ASSERT(uT2TimerId == 0); // timer2 must stopped if (bRefPoint) // save reference time { + bT2Val = FALSE; // init thread interrupt workaround QueryPerformanceCounter(&lT2Ref); // time of corresponding Chipset.t2 value uDelay = Chipset.t2; // timer value for delay } @@ -273,10 +304,9 @@ VOID StartTimers(VOID) if (Chipset.IORam[TIMER2_CTRL]&RUN) // start timer1 and timer2 ? { bStarted = TRUE; // flag timer running - // 24.10.99 cg, new, initialisation of NINT2 lines + // initialisation of NINT2 lines bNINT2T1 = (Chipset.IORam[TIMER1_CTRL]&INTR) != 0 && (Chipset.t1 & 8) != 0; bNINT2T2 = (Chipset.IORam[TIMER2_CTRL]&INTR) != 0 && (Chipset.t2 & 0x80000000) != 0; - // 24.10.99 cg, end of initialisation // set timer resolution to 1 ms, if failed don't use "Accurate timer" bAccurateTimer = (timeBeginPeriod(1) == TIMERR_NOERROR); timeGetDevCaps(&tc,sizeof(tc)); // get timer resolution diff --git a/sources/Emu48/TYPES.H b/sources/Emu48/TYPES.H index 0caaa1b..456c1aa 100644 --- a/sources/Emu48/TYPES.H +++ b/sources/Emu48/TYPES.H @@ -7,15 +7,21 @@ * */ -#define SWORD SHORT // 09.12.99 cg, new, signed 16 Bit variable -#define QWORD ULONGLONG // 22.11.99 cg, new, unsigned 64 Bit variable +// HST bits +#define XM 1 +#define SB 2 +#define SR 4 +#define MP 8 + +#define SWORD SHORT // signed 16 Bit variable +#define QWORD ULONGLONG // unsigned 64 Bit variable #define CHIPSET Chipset_t typedef struct { - SWORD nPosX; // 09.12.99 cg, bugfix, is a signed number - SWORD nPosY; // 09.12.99 cg, bugfix, is a signed number - BYTE type; + SWORD nPosX; // position of window + SWORD nPosY; + BYTE type; // calculator type DWORD Port0Size; // real size of module in KB DWORD Port1Size; // real size of module in KB @@ -53,14 +59,17 @@ typedef struct WORD crc; WORD wPort2Crc; // fingerprint of port2 - WORD wRomCrc; // 20.11.99 cg, new, fingerprint of ROM -// QWORD cycles; // 22.11.99 cg, changed, oscillator cycles - DWORD cycles; // 22.11.99 cg, moved, oscillator cycles - DWORD cycles_reserved; // 22.11.99 cg, reserved for MSB of oscillator cycles - DWORD dwKdnCycles; // 22.11.99 cg, moved, cpu cycles at start of 1ms key handler + WORD wRomCrc; // fingerprint of ROM +#if defined _USRDLL // DLL version + QWORD cycles; // oscillator cycles +#else // EXE version + DWORD cycles; // oscillator cycles + DWORD cycles_reserved; // reserved for MSB of oscillator cycles +#endif + DWORD dwKdnCycles; // cpu cycles at start of 1ms key handler UINT Bank_FF; // save state of HP48GX port2 or state of HP49G ROM FF - UINT Port2_NBanks; // not used + UINT FlashRomState; // WSM state of flash memory (unused) BYTE cards_status; BYTE IORam[64]; // I/O hardware register UINT IOBase; // address of I/O modules page @@ -74,7 +83,7 @@ typedef struct BYTE t1; DWORD t2; - BOOL bShutdnWake; // 20.11.99 cg, moved, flag for wake up from SHUTDN mode + BOOL bShutdnWake; // flag for wake up from SHUTDN mode BYTE Keyboard_Row[9]; WORD IR15X;