######## ################## ###### ###### ##### ##### #### #### ## ##### #### #### #### #### #### ##### ##### ## ## #### ## ## ## ### ## #### ## ## ## ##### ######## ## ## ## ##### ## ## ## ## ## ##### ## ## ######## ## ## ## ### ## ## #### ## ## ##### #### #### #### #### ##### #### #### #### #### #### ###### ##### ## ###### ###### Issue #18 ################## July 3, 1999 ######## (v1.1) ............................................................................... Time flies like the wind; fruit flies like a banana. "Yet if the only form of tradition, of handing down, consisted in following the ways of the immediate generation before us in a blind or timid adherence to its successes, 'tradition' should be positively discouraged. We have seen many such simple currents lost in the sand; and novelty is better than repetition. [Tradition] cannot be inherited, and if you want it you must obtain it by great labor." T. S. Eliot, "Tradition and the Individual Talent" ............................................................................... BSOUT Tradition. C=Hacking is a magazine of considerable tradition, a tradition that is not merely imitating the successes of previous generations. C=Hacking has a tradition of true excellence, and once again, great people have contributed their time and talents towards the 64 community, and another fine issue of C=Hacking. And once again, innovative ideas have grown and borne delectable fruits for those seated at the C=Hacking table. And once again, in keeping with one of C=Hacking's most hallowed traditions, the issue is really, really late. A new job, a new move, a new house, a new puppy -- what can I say? This real world stuff takes some getting used to, and so this issue is even later than usual. On the plus side, I think you'll find it worth the wait (at least, I hope so!). A lot has happened in the C= world since the last issue of C=Hacking. At the personal level, all my moves and uphevals have led to a few changes. Now that I'm fabulously wealthy (compared to my old graduate student salary, I am fabulously wealthy), I splurged on a custom domain name. The Fridge is now located at http://www.ffd2.com, and thus the Official Unofficial C=Hacking Homepage is now located at http://www.ffd2.com/fridge/chacking/ And in the "well, it's news to me" department: - SuperCPU acitivity seems to be picking up steam, at long last. The giga.or.at scpu mail list recently revived itself, and several new projects and web pages have appeared. Moreover, 65816 assemblers have finally started to appear. El Cheapo Assembler (CheapAss), for SuperCPU-equipped 64s, is available in the Fridge. A new cross-assembler, ACME, is being worked on. And a *third* project, Virtualass816, is said to be underway. In addition to the assemblers, there is an 816 backend to LCC, some new operating system projects (e.g. JOS), rumors of games, game fixes (including a Flight Simulator patch by Maurice Randall), and more. http://come.to/supercpu is a good place to learn more about new SCPU developments. - There will be an English version of GO64! magazine, starting with the August issue, available by subscription. For more information, visit http://www.go64.c64.org - The annual Chicago Expo will take place in -- amazingly enough -- Chicago, on September 25. Last year was a hoot and hopefully our group will be there again this year. For more details, http://members.aol.com/~rgharris/swrap.html will surely contain info as the date draws near. - Justin Beck, a dj at station KDVS, in Davis, CA, runs a SID radio show every tuesday night at 8PM Pacific time. You can tune in at 90.3 in the Davis/Sacramento area, or the webcast at http://www.kdvs.org. For more information, write jrbeck@ucdavis.edu; actually, write him anyways, to tell him how cool the show is! - CBM FIDO echos are available at several places on the web these days -- http://cereal.mv.com is a pretty popular source. - Craig Bruce has been placing old issues of the Transactor online at http://www.pobox.com/~csbruce/commodore/transactor/ Well worth a visit. - Another unzip program has appeared, by Pasi Ojala. Visit his homepage http://www.cs.tut.fi/~albert/ for more details. - Richard Atkinson has drawn up schematics of the Commodore Sound Expander, a Yamaha-chip-based cartridge. For more info, email Richard! (And maybe look in a future issue of C=Hacking??? :) This weekend, here in the states, we are celebrating our independence (I think the Canadians tried to invade us once, or something...). I can't help but reflect that the Commodore 8-bits also represent a kind of independence. As the above (and the below) amply demonstrates, that independent spirit is alive and thriving, a fact that is not only remarkable, but even perhaps worth celebrating. Enjoy the issue! -S ....... .... .. . C=H 18 ::::::::::::::::::::::::::::::::::: Contents :::::::::::::::::::::::::::::::::: BSOUT o Voluminous ruminations from your unfettered editor. Jiffies o Maybe next time! The C=Hallenge o Yet again, no C=Hallenge, sigh... Side Hacking o Data Structures 101: Linked Lists DS101 is a new article series. The idea is to review different data structures, with examples of their use in 64 programming. This installment covers linked lists, and how to use them to make a zippy insertion sort with almost zero overhead. o Counting Sort, by Pasi Ojala And, since we're on the subject of sorting algorithms, Pasi wrote up the counting sort. What's a counting sort? Well, read the article! Main Articles o "VIC-20 Kernel ROM Disassembly Project", by Richard Cini This installment covers interrupts -- IRQ and NMI sources, the kernal handler code. o "A Diehard Programmer's Introduction to GEOS, and geoWrite Disassembly Notes", by Todd S. Elliott As part of his effort to learn about GEOS programming, Todd disassembled geoWrite 128, and patched it to be more accepting of new devices and such. This article summarizes that adventure -- the results and the lessons learned. o Masters Class: "NTSC/PAL fixing: FLI", by Russell Reed , Robin Harbon , and S. Judd. This time around the subject is FLI and IFLI graphics, and a tutorial on fixing them. Several pictures are provided for the reader to fix, in the included .zip file. o "Obj3d: The 3D object library", by S. Judd Obj3d is a set of routines for creating, manipulating, and displaying 3D worlds on the 64. It consists of routines to add and remove objects, move and rotate objects, render the display, and so on, and hence vastly simplifies the creation of 3D programs. This article actually consists of three articles. The first article describes the library, and walks through a simple example program. The second article is the "programmer's guide", containing memory maps and a list of all the routines. The third article discusses "stroids", a more advanced example program in which a space ship can fly around a randomly drifting asteroid field, to demonstrate that sophisticated programs can be written with only a few hundred lines of code. The program is purposely left incomplete -- it's up to you to finish it up! We had hoped to include a "3D Object Editor", written by Mark Seelye, but it wasn't quite done yet -- next issue! Source code and binaries are included at the end of the issue. .................................. Credits ................................... Editor, The Big Kahuna, The Car'a'carn..... Stephen L. Judd C=Hacking logo by.......................... Mark Lawrence For information on the mailing list, ftp and web sites, send some email to chacking-info@jbrain.com. Legal disclaimer: 1) If you screw it up it's your own fault! 2) If you use someone's stuff without permission you're a serious dork! About the authors: Todd Elliot is a 30 year old lawyer working as a Community Client Advisor for the Center On Deafness - Inland Empire, working with deaf people in the local communities. He got his first 64 in December 1983, using it for games, BBS activities, and dabbling in programming. Nowadays Todd focuses on ML programming, and his latest project is an AVI movie player for the SuperCPU. Todd is a huge fan of sports and the Miami Dolphins in particular, and enjoys writing articles and watching movies. At the 1998 Chicago Expo Todd enjoyed meeting all those people who were previously a name and an email address; according to Todd, "The Chicago Expo truly embodies what is going on with today's CBM community." Richard Cini is a 31 year old vice president of Congress Financial Corporation, and first became involved with Commodore 8-bits in 1981, when his parents bought him a VIC-20 as a birthday present. Mostly he used it for general BASIC programming, with some ML later on, for projects such as controlling the lawn sprinkler system, and for a text-to-speech synthesizer. All his CBM stuff is packed up right now, along with his other "classic" computers, including a PDP11/34 and a KIM-1. In addition to collecting old computers Richard enjoys gardening, golf, and recently has gotten interested in robotics. As to the C= community, he feels that it is unique in being fiercely loyal without being evangelical, unlike some other communities, while being extremely creative in making the best use out of the 64. Pasi 'Albert' Ojala is a 29 year old software engineer, currently working at a VLSI design company on a RISC DSP core C compiler. Around 1984 a friend introduced him to the VIC-20, and a couple of years later he bought a 64+1541 to replace a broken Spectrum48K. He began writing his own BBS, using ML routines for speed, and later wrote a series of demos under the Pu-239 label. In addition to pucrunch and his many C=Hacking articles, Pasi's most recent project is an "unzip" program for the 64. Pasi is also a huge Babylon-5 fan, and has a B5 quote page at http://www.cs.tut.fi/~albert/Quotes/B5-quotes.html Robin Harbron is a 26 year old internet tech support at a local independent phone company. He first got involved with C= 8-bits in 1980, playing with school PETs, and in 1983 his parents convinced him to spend the extra money on a C64 instead of getting a VIC-20. Like most of us he played a lot of games, typed in games out of magazines, and tried to write his own games. Now he writes demos, dabbles with Internet stuff, writes C= magazine articles, and, yes, plays games. He is currently working on a few demos and a few games, as well as the "in-progress-but-sometimes-stalled-for-a-real-long-time- until-inspiration-hits-again Internet stuff". He is also working on raising a family, and enjoys music (particularly playing bass and guitar), church, ice hockey and cricket, and classic video games. .................................. Jiffies ................................... Blah. ............................... The C=Hallenge ............................... Bleah. ................................ Side Hacking ................................ Data Structures 101 -- Linked lists and a nifty sorting algorithm ------------------- by S. Judd and Pasi Ojala -- Every computer science major has taken a class in data structures. The 64 programming world, however, is full of non-CS majors. Data structures are such useful tools for programmers to have in their toolbox that I thought it would be a worthwhile thing to have various people periodically review different types of data structures, and common algorithms which make use of them, and their relevance to an 8-bit CBM. What is a data structure? Perhaps a reasonable definition is that it is an abstract method of storing and retrieving data. These abstractions may then be implemented on the computer, to solve different types of problems. Whereas no single data structure is ideal for all problems, often a given problem has a data structure ideally suited for it. The "optimal" data structure for a given problem depends heavily upon what kind of data is stored, and how it is stored, retrieved, or otherwise manipulated. (On a more humorous note, /dev/null is ideal for storing information provided you don't need to retrieve it, ever -- this probably doesn't count as a data structure, though). A simple example of a data structure is an array: abstractly, it stores and retrieves data by means of an index value, e.g. A$(I). On the computer it might be implemented in many different ways, depending on the type of data stored (integers, strings, floating point, custom records, etc.), the dimension of the array, the computer hardware, and even whether the index is e.g. an integer or a string. So it is worthwhile to distinguish the abstract concept of an "array" from its actual implementation. Another type of useful data structure is a linked list. Imagine attaching a pointer to some chunk of data (usually called a "node"). This pointer can then point, or link, to another chunk of data -- the next node in the list. This can be schematically illustrated as: +---+ +---+ +---+ +---+ +---+ | 3 |---->| 1 |---->| 4 |---->| 5 |---->| 9 |----> ... +---+ +---+ +---+ +---+ +---+ Here each node contains some data (a number) and a pointer to the next node. Starting from the first node -- called the "head" of the list -- a program can reach every other node by following the pointers. This is the basic idea of a linked list. Linked lists are used all over the place in the 64. BASIC programs are a type of linked list. Each line of a basic program is stored as a line link, then a line number and the data. The link points to the next line in the program. Having links speeds up BASIC programs (imagine finding and counting the ends of each line for every GOTO or GOSUB), and links are useful because each program line can be of different size (imagine storing a program in something like an array). The end of the list is specified with a line link of 00. A better example is the DOS. Every 256-byte sector on a disk drive starts with a 2-byte track and sector pointer, pointing to the next sector in the chain, and contains 254 bytes of data. This is exactly a linked list. The directory tells where the first sector of a program is -- the head of the list -- and the program is loaded by simply traversing the list -- following each link and reading the data. The end of the list is specified by using a special link value (track = $FF). The reason it is "better" is that whereas a BASIC program is stored sequentially in memory, a program stored on disk might be strewn all over the place. This is an important feature of linked lists: they can join together data that is spread all over memory. If you think about it for a moment, you'll quickly realize that elements may be added into (or removed from) the middle of a list simply by changing a pointer: +---+ +---+ +---+ +---+ +---+ | 3 |---->| 1 |---->| 4 |-+ +->| 5 |---->| 9 |----> ... +---+ +---+ +---+ | | +---+ +---+ | | | +---+ | +->| 1 |-+ +---+ (Well, okay, two pointers :). Inserting sectors into the middle of a program isn't exactly very useful for disk storage, but there are plenty of applications where it is extremely useful; one such application, discussed below, is sorting data. Both BASIC and the disk drive are examples of a forward- or singly- linked list. But imagine attaching a second pointer to each disk sector, pointing to the _previous_ track and sector: +---+ +---+ +---+ +---+ +---+ | |---->| |---->| |---->| |---->| |----> ... | | | | | | | | | | | |<----| |<----| |<----| |<----| |<---- ... +---+ +---+ +---+ +---+ +---+ This would be a "doubly-linked list". The downside would be a loss of two bytes per sector; the upside would be that if your directory track were destroyed, you could recover all programs on a disk -- in a singly-linked list like C= DOS, you have to know where the start of the list, i.e. the first track and sector, is. Moreover, with a doubly-linked list you can _delete_ a node without even knowing where the node is in the list; with a singly-linked list, you have to search through the list to find the pointer to the node. Of course, you could add even more pointers to a list, which is done for certain types of trees, for example. (Trees will perhaps be covered some other time). A fast sorting algorithm ------------------------ Let's say you had a list of numbers that you wanted to sort from largest to smallest. For example, the obj3d library, discussed later in this issue, needs to depth-sort objects, so that far-away objects don't overlap nearby objects when drawn on the screen. This amounts to sorting a list of 16-bit numbers. These numbers are stored in a simple list -- not a linked list, but an array, like: lobytes lo0 lo1 lo2 ... hibytes hi0 hi1 hi2 A typical sorting algorithm would have to spend a lot of time swapping numbers, moving stuff around, etc. Even with 16-bits this is a fair amount of overhead, and with even larger numbers it gets very time- consuming. But, as mentioned earlier, a linked list is tailor-made for rearranging data. That is, if we start with a list of sorted numbers, then inserting a new number into the list amounts to finding the right spot in the list, +---+ +---+ +---+ | 1 |---->| 2 |---->| 5 |--- +---+ +---+ +---+ changing the first part of the list to point to the new number, +---+ +---+ +---+ | 1 |---->| 2 |---->| 5 |-+ +---+ +---+ +---+ | | | +---+ +->| 6 |--- +---+ and having that new number point to the rest of the list. +---+ +---+ +---+ +---+ +---+ | 1 |---->| 2 |---->| 5 |-+ +->| 8 |---->| 9 |----> ... +---+ +---+ +---+ | | +---+ +---+ | | | +---+ | +->| 6 |-+ +---+ So sorting a list of numbers amounts to inserting these numbers into the right spot in the list, one at a time. Amazingly enough, this type of sort is called an "insertion sort". Your first thought might be "doesn't that mean a whole lot of overhead in copying data and changing pointers?!" It might, if we were lame PC programmers; but, of course, we are 64 coders, which means we are handsome, witty, superior, humble -- and most of all, sneaky. All we _really_ need is a) an index into the list of numbers b) a pointer to the next number The trick is simply to combine the two, by storing the linked list just like the list of numbers, as a sequential array of *8-bit* links: list link0 link1 link2 ... Now each link not only points to the next list element -- it exactly points to the next number as well. For example, let's say that the earlier list of numbers was 23,16,513 lobyte 23 01 16 hibyte 00 02 00 Sorting from largest to smallest should give 1-0-2 -- that is, element 1 is the largest, followed by element 0, followed by element 2. So the linked list could have the form head = 1 list 2 0 $80 To see how this works, start with the head of the list, which points to list element 1. LDX head To get the next element in the list is easy: LDA list,X Now .X is the current element, and .A is a link to the next element. To traverse the entire list, then we simply need to TAX and loop: LDX head :loop LDA list,X TAX BPL :loop This will traverse the list, until the $80 is reached. The thing to realize is that .X is *also* a list into the list of numbers, so for example you could print out the numbers, in order, with some code like LDX head :loop LDA lobyte,X LDY hibyte,X JSR PrintAY LDA list,X TAX BPL :loop Now all we have to do is construct the list! get next coordinate traverse linked list compare to coordinates in linked list, to find correct spot split linked list in half make bottom half of list point to the new element make the new element point to the rest of the list So far it is just your basic insertion-sort; if you are familiar with AmigaOS, it is similar to Enqueue(), which inserts nodes by priority. The beauty here is that the index registers make all this really, really easy; here's the sort code from obj3d, to sort positive numbers, stored in CZ and HCZ = lo and hi bytes, from largest to smallest. It starts at the head of the list (at list+$80), traverses the list to find the right spot, and inserts the "node": :loop [copy number to be inserted to TEMP, TEMP+1 for a little speed] LDY #$80 ;Head of list :l1 LDA VISOBJS,Y ;Linked list of objects BMI :link STY TEMPY TAY ;Next object LDA CZ,Y ;If farther, then CMP TEMP ;move down list LDA HCZ,Y SBC TEMP+1 BCS :l1 ;Nearest objects last in list TYA LDY TEMPY ;Insert into list :link STA VISOBJS,X ;X -> rest of list TXA STA VISOBJS,Y ;beginning of list -> X DEX BPL :loop :rts RTS Here, VISOBJS is the linked list of sorted coordinates. Personally, I think that's awfully nifty -- an insertion sort with almost zero overhead. In summary, a linked list is simply an abstract method of storing and retrieving data. For certain kinds of problems it is extremely useful, even optimal, and for certain problems it can be implemented in a very efficient and convenient way. And as pointed out in the beginning of this article, that is the essence of a data structure. I suppose that about wraps things up. So, who wants to volunteer the next data structures article? :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Counting sort ------------- by Pasi Ojala Because we got ourselves tangled up with sorting algorithms, we may as well take a look into another one called counting sort. The idea is to sort a list of symbols according not to the symbols themselves, but rather according to a "key" associated with the symbols. This will become clear shortly. This sorting algorithm can be used with integer-valued data when the range of sorting key values (the values by which the order is decided) is small enough. The foundation for counting sort is to make one pass through the data and determine the sorted order of the data, then make another pass and perform the actual shuffling of data. Now, what kind of a data structure is used for counting sort? Surprise, we only need a counter for every possible sorting key. This is why a limited range of values is required or the memory consumption would simply be too large. And we need an array of input data and an array for the sorted output data, as the sorting can't be performed in place. An example sorts the following data, which in fact represents a Huffman tree. The sorting keys are the code lengths, and the associated data is the corresponding symbol. The sorting of symbols is required in some of the Huffman tree creation routines. key: 4 3 1 5 3 6 3 6 sym: A B C D E F G H The sorting keys can have value from 1 to 6, so we need 6 counters. Their initial values are set to zero: for(i=1;i<7;i++) count[i-1] = 0; count: 0 0 0 0 0 0 Then we perform the counting stage by going through the data and incrementing the counter corresponding to the sorting key value. In the end the counters contain the number of each code length found in the data. for(i=0;i<#ofvalues;i++) { count[sort_key[i]-1] = count[sort_key[i]-1] + 1; } count: 1 0 3 1 1 2 Then cumulative counts are calculated for this array. for(i=1;i<6;i++) { count[i+1] = count[i+1] + count[i]; } count: 1 1 4 5 6 8 If you take a close look into the meaning of the values now in the count array, you might notice that the last count value gives us from which element downward to stick the data with sorting key 6, the previous one where to stuff data with key 5 and so on. So, next we simply copy each element in the data into its final place in the output array using the cumulative counts calculated previously. Note that in C the indexing starts from 0, but in the table from 1 for the reader's convenience. for(i=#ofvalues-1;i>=0;i--) { count[key[i]-1] = count[key[i]-1] - 1; outputkey[count[key[i]-1]] = key[i]; outputsym[count[key[i]-1]] = sym[i]; } 1 2 3 4 5 6 1 2 3 4 5 6 7 8 --------------------------------- 6 1 1 4 5 6 8 x x x x x x x 6 H 7 X X X X X x X H 3 1 1 4 5 6 7 x x x 3 x x x 6 G 3 X X X G X x X H 6 1 1 3 5 6 7 x x x 3 x x 6 6 F 6 X X X G X x F H 3 1 1 3 5 6 6 x x 3 3 x x 6 6 E 2 X X E G X x F H 5 1 1 2 5 6 6 x x 3 3 x 5 6 6 D 5 X X E G X D F H 1 1 1 2 5 6 6 1 x 3 3 x 5 6 6 C 0 C X E G X D F H 3 0 1 2 5 6 6 1 3 3 3 x 5 6 6 B 1 C B E G X D F H 4 0 1 1 5 6 6 1 3 3 3 4 5 6 6 A 4 C B E G A D F H So, after the loop we have sorted the symbols according to the code lengths. We haven't talked about the O() notation, but the counting sort is an O(n) process. Counting sort only needs three fixed-sized loops, thus if the amount of data increases linearly, the sorting time also increases linearly. With insertion sort the sorting time increases geometrically because searching for the right spot to add a node takes longer and longer when the number of already sorted nodes increases. One extra bonus for counting sort is that it preserves the so-called inside order. The order of elements with identical keys is preserved in the end result. This is very important in many algorithms, like the Huffman tree creation GZip uses (and which gunzip.c64 used before the algorithm was changed to not require sorting). On the minus side is the necessity of the output buffer. Other sorting algorithms can sort the data in-place, although that also means that the data must be copied a lot of times. The preservation of inside-order also allows the sorting of data by multiple keys: first sort by secondary keys, then by primary keys. After this the data is sorted by the primary keys and all elements having identical keys are sorted by the secondary keys. And that's it! ....... .... .. . C=H #18 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Main Articles ------------- VIC KERNAL Disassembly Project - Part II Richard Cini May 21, 1999 Introduction ============ Last issue, we began by examining the VIC's start-up sequence. We began with the processor jump at power-on to location $FFFC upon power-up and ended at the Kernal’s jump into the BASIC ROM. This issue, we will look at the two other hard-coded processor vectors, the IRQ and NMI vectors. Just like the RES* pin on the 6502 processor, the IRQ* and NMI* pins (the hardware portion of the interrupt) are hard-coded to two memory locations, $FFFE and $FFFA, respectively. When the processor senses a negative-going pulse on these pins of at least 20uS in duration, the processor will jump to the respective vector locations (the software portion of the interrupt, also called an "interrupt handler"): FFFA ;=============================== FFFA ; - Power-on Vectors FFFA ; FFFA A9 FE .dw NMI ;$FEA9 FFFC 22 FD .dw RESET ;$FD22 FFFE 72 FF .dw IRQ ;$FF72 Both the IRQ* and NMI* lines are available at the expansion connector at the back of the VIC. They are also connected to the IRQ* output lines on the VIC's two 6522 VIA chips (just like the 64); the IRQ* is connected to VIA2 and the NMI* to VIA1. Hardware interrupts on the VIC-20 may be triggered by any one of several events which is recognized by either one of the VIC's two VIA chips, or by an external event which triggers the IRQ* or NMI* pins on the VIC's expansion connector. The software IRQ routine (at $FF72) is also entered by the execution of the BRK processor instruction. The VIA chips are capable of generating a hardware interrupt based on the events as outlined in register $911D, as described in the section titled "VIA Registers Used in Following Code." In general, interrupt events result from the VIA's internal timers reaching 0 and active transitions in the logic level of the CA1/CB1/CA2/CB2 handshaking pins. Some of the timers and handshaking pins are programmed or connected to hardware within the VIC. For example, timer 1 on VIA2 is initialized to provide a periodic interrupt every 0.015 seconds, or an approximate rate of 65 Hz. This interrupt is used to update the time-of-day clock and to provide timing pulses for cassette tape operations. This timer sharing is the main reason behind the jiffy clock losing time during tape operations. Similarly, the processor NMI* line is triggered by the IRQ* output on VIA1. In summary, by default, the interrupt handler code is executed as a result of the following: For the IRQ code: the 60Hz periodic interrupt from Timer 1 on VIA2 and the processor BRK instruction. For the NMI code: pressing of the RESTORE or Run/Stop-RESTORE key combinations and RS232 operations. The IRQ and NMI software routines could also be triggered if the programmer makes use of some of the other VIA pins for a home-brewed project. For example, if a home-brew alarm system is configured to trigger the CA2 pin when there is motion in the back yard. The user's program could be set up to watch for an interrupt generated by the CA2 pin, to sound a siren. VIA Registers Used in the Following Code ======================================== The 6522 VIA has 16 byte-wide registers which control the functioning of the chip or enable the receipt or transmission of data on the I/O pins. For the sake of space, we'll only discuss the registers directly applicable to this issue's code. $9111 is the Port A output register, an 8-bit I/O port which automatically uses the CA1 and CA2 pins for handshaking. Port A is duplicated at $911F, but changes to the register do not affect CA1 and CA2 (so, no handshaking). In the above code, only BIT7 and BIT6 are relevant. The bitfields are: BIT7 IEEE ATN out BIT6 Cassette sense switch BIT5 Joystick fire button BIT4 Joystick left BIT3 Joystick down BIT2 Joystick up BIT1 IEEE Serial Data In BIT0 IEEE Serial Clock In $911D is the register (the Interrupt Flag Register, "IFR") that indicates which event triggered the interrupt. BIT3, BIT2, and BIT0 are left unprogrammed in the above code, but are shown below for completeness. BIT4 manages the RS232 receive function, and BIT1 is connected to the RESTORE key. SET BY CLEARED BY BIT7 NMI status (set when any of the lower bits are set) BIT6 Timer 1 time-out read T1 LW and wrt T1 HW latch BIT5 Timer 2 time-out read T2 LW and wrt T1 HW latch BIT4 CB1 transition R/W Port B BIT3 CB2 transition R/W Port B BIT2 Completion of 8 shifts R/W shift register BIT1 CA1 transition R/W Port A ($9111 only) BIT0 CA2 transition R/W Port A ($9111 only) According to the 6522 data sheets, the shift register interrupt is an allowed interrupt, but there is a bug in early 6522s that prevented the shift register from working properly. In the VIC, the shift registers are disabled by default and remain unused in the KERNAL. It is possible for a user program to enable the shift registers and use them, but the quality of the results would be lacking because of the bug. $911E (the Interrupt Enable Register, "IER") is the register that enables or prevents the lower six bits in the IFR from triggering an interrupt. Writing a 0 to BIT7 clears the bits in the IER according to the bit pattern in the lower six bits. Writing a 1 to BIT7 sets the IER according to the bit pattern. For example, if one wanted to enable all of the above as sources of interrupts, one would write %11111111 to the IER. Disabling all of the above as interrupt sources would be accomplished by a write of %01111111 to the IER. $9110 is the I/O register for Port B of VIA1. This 8-bit port is connected to pins PB0-PB7 of the VIC User Port. When the RS232 interface module is connected to the User Port, Port B doubles as the RS232 port, with the following bitmap: PB7 Data Set Ready (DSR) in PB6 Clear To Send (CTS) in PB5 [no connection] PB4 Data Carrier Detect (DCD) in PB3 Ring Indicator (RI) in PB2 Data Terminal Ready (DTR) out PB1 Request To Send (RTS) out PB0 Receive Data CB1 acts as the interrupt source for the NMI receive routine and is physically connected (externally) to PB0 so that a received bit of data triggers an interrupt. CB2 acts as the RS232 transmit line. Register $9114 is an 8-bit register which holds the least- significant byte of Timer 1's countdown value. Registers $9118 and $9119 are the least-significant and most- significant bytes of Timer 2's countdown value. Together, they provide a 16-bit countdown capability for use as the baud rate clock. Register $911C is called the Peripheral Control Register, the "PCR". The PCR controls how the four handshaking lines (CA1/2 and CB1/2) act. On VIA1, CB2 is connected to the RS232 transmit line, and CA2 is connected to the cassette tape motor control circuitry. Both CA2 and CB2 have eight possible modes that can be manual or automatic, positively or negatively triggered, input or output oriented. Input modes set flags in the IFR based on programmed transitions. When programmed as outputs, CA2/CB2 lines are triggered based on writing to Port B. NMI Processing ============== Let's look at the NMI routine first. The NMI is triggered through the use of the RESTORE key connected to the CA1 line, the CB1 RS232 receive data line, and the expiration of Timer 1 and Timer 2, which manages framing for RS232 transmit and receive operations, respectively. The NMI code begins with some stub code that enables the redirection of the NMI processing if so programmed by a user. The indirect jump at $FFEA is similar to chainable interrupt processing on MS-DOS based computer systems -- save the old pointer, redirect pointer to your own code, and then chain to the system NMI processor. Several other Kernal vectors are handled in the same way: IRQ, Break, Open, Close, Load, Save, Set Input, Set Output, Input, Output, Get Character, and Clear I/O Channel. FEA9 ;============================================== FEA9 ; NMI - NMI transfer entry FEA9 ;============================================== FEA9 NMI FEA9 78 SEI ; disable interrupts FEAA 6C 18 03 JMP (NMIVP) ;$FEAD allows redirection of NMI The actual NMI processing code follows the redirect stub. The NMI routine is broken into three parts: RESTORE processing, RS232 transmit management and RS232 receive management. FEAD ;============================================== FEAD ; LNKNMI - Link to actual NMI code. The first FEAD ; part manages the R/S-R keys FEAD LNKNMI FEAD 48 PHA ; save registers .A FEAE 8A TXA FEAF 48 PHA ; .X FEB0 98 TYA FEB1 48 PHA ; .Y FEB2 AD 1D 91 LDA D1IFR ;check VIA1 interrupt flag register FEB5 10 48 BPL WARMEOI ;no flag present, then issue EOI FEB7 2D 1E 91 AND D1IER ;see if found interrupt is allowed ; based on IER mask FEBA AA TAX ;save bitmask of enabled and active ; interrupts FEBB 29 02 AND #%00000010 ;VIA1/CA1 RESTORE key? FEBD F0 1F BEQ WARM1 ;not RESTORE, so move on to RS232 ;Got here, so NMI must be RESTORE ; key FEBF 20 3F FD JSR SCNROM ;scan for A000 ROM (covered last ; issue) FEC2 D0 03 BNE LNKNMI1 ;no ROM at $A0, so skip ROM NMI ; routine FEC4 6C 02 A0 JMP (A0BASE+2) ;jump to ROM NMI routine FEC7 LNKNMI1 ;continue NMI processing. FEC7 2C 11 91 BIT D1ORA ;test ATN IN(7)/cass switch (6) ; bits FECA 20 34 F7 JSR IUDTIM ;update TOD clock FECD 20 E1 FF JSR STOP ;check for STOP key Z=1 if STOP ; pressed FED0 D0 2D BNE WARMEOI ;no stop key, so skip vector ; restore, VIC and I/O ; initialization. Go EOI. We reach the following code block if Run/Stop is pressed along with the RESTORE key. The code results in a soft reset: it restores the default system vectors, initializes the I/O chips to their default, and initializes the screen editor. The routine then jumps into BASIC, bypassing BASIC's normal initialization routines, leaving program RAM undisturbed. FED2 ;============================================== FED2 ; WARMST - Default USER vector FED2 ; Also get here from BRK instruction (through IRQ FED2 ; vector) or RUN/STOP-RESTORE key combination FED2 WARMST FED2 20 52 FD JSR IRESTR ;restore default vectors (covered ; last issue) FED5 20 F9 FD JSR IOINIT ;initialize I/O (covered last ; issue) FED8 20 18 E5 JSR CINT1 ;initialize screen editor (covered ; last issue) FEDB 6C 02 C0 JMP (BENTER+2) ;jump to BASIC NMI routine If program execution gets here without passing through the WARMST code, then the NMI resulted from an RS232 event, such as when the transmit or receive timers time-out (signaling that the VIC is done transmitting or receiving a character of information). For now, we'll skip the actual RS232 transmit/receive code; it'll be covered in a later issue. Since the Kernal programmers could not reliably use the shift registers in the VIAs, it appears that they synthesized a shift register in the NMI routine. This emulation enables the RS232 code to work properly. The code continues with the NMI part of the RS232 transmit and receive character processing routine: FEDE WARM1 FEDE AD 1E 91 LDA D1IER ;get IER bitmap FEE1 09 80 ORA #%10000000 ;set mask for enabling interrupts ; according to existing bitmap FEE3 48 PHA ;save "enable" bitmap FEE4 A9 7F LDA #%01111111 ;mask-disable all interrupts on ; VIA1 FEE6 8D 1E 91 STA D1IER ;go do it FEE9 8A TXA ;restore mask for active interrupts FEEA 29 40 AND #%01000000 ;IFR bit6 =TIMER1 time-out (RS232 ; clk) FEEC F0 14 BEQ WARM2 ;T1 done, go to RS232/RX chr FEEE A9 CE LDA #%11001110 ;set/reset bit5 to xmit char FEF0 05 B5 ORA NXTBIT ;RS232 transmit - next bit to send; FEF2 8D 1C 91 STA D1PCR ;CB2 manual L/H; CB1 neg trans for ; IRQ; CA2 manual H; CA1 neg trans ; for IRQ; CB2=TX, CA2=cass motor ; control FEF5 AD 14 91 LDA D1TM1L ;get VIA1/T1 count low byte FEF8 68 PLA ;restore IER bitmap... FEF9 8D 1E 91 STA D1IER ; ...and save it FEFC 20 A3 EF JSR SSEND ;send RS232 char FEFF FEFF WARMEOI FEFF 4C 56 FF JMP EOI ;end of interrupt FF02 WARM2 ;RS232 receive NMI routine FF02 8A TXA ;restore IFR mask from above FF03 29 20 AND #%00100000 ;VIA1/T2 time-out (done receiving ; character from RS232 channel)? FF05 F0 25 BEQ WARM3 ;yes, so move byte to buffer ;collect bits... FF07 AD 10 91 LDA D1ORB ;get user port bitmap FF0A 29 01 AND #%00000001 ;bit0=RS232/RX FF0C 85 A7 STA INBIT ;save received bit FF0E AD 18 91 LDA D1TM2L ;get VIA1/T2L count FF11 E9 16 SBC #$16 ; subtract 22d FF13 6D 99 02 ADC BAUDOF ; add low word of bit transmit time FF16 8D 18 91 STA D1TM2L ; save it FF19 AD 19 91 LDA D1TM2L+1 ;get VIA1/T2H count FF1C 6D 9A 02 ADC BAUDOF+1 ; add high word of bit xmit time FF1F 8D 19 91 STA D1TM2L+1 ; save it FF22 68 PLA ;restore old IFR bitmap... FF23 8D 1E 91 STA D1IER ; ...and save it FF26 20 36 F0 JSR SERRX ;signal RS232 receive routine FF29 4C 56 FF JMP EOI ;end of interrupt FF2C WARM3 ;received new char, so buffer it FF2C 8A TXA FF2D 29 10 AND #%00010000 ;CB1 interrupt (RX data bit ; transition) FF2F F0 25 BEQ EOI ;no bit, exit One interesting fact about the VIC is that originally it was supposed to include a 6551 ACIA (RS-232) communications chip as standard equipment. However, when MOS could not supply an adequate number of working chips to support the anticipated VIC production volume, the VIC engineers decided to emulate the 6551 in software. The VIC Programmer's Reference Guide makes mention of the 6551 registers, but the VIC clearly does not contain a 6551. See Cameron Kaiser's Commodore Knowledge Base for more details. The URL is http://calvin.ptloma.edu/~spectre/ckb/ The 6551 pseudo-Control Register contains the stop bits, word length, and baud rate parameters. The pseudo-Command Register contains the parity, duplex, and handshaking parameters. The pseudo-Status Register contains a result code bitmap. After setting the baud rate divisor, the routine updates the number of bits to transmit and exits. FF31 AD 93 02 LDA M51CTR ;pseudo 6551 control register FF34 29 0F AND #%00001111 ;pass the baud rate parameter only FF36 D0 00 BNE $+2 ;I/O delay FF38 0A ASL A ;shift left FF39 AA TAX ;save shifted baud rate bitmask and ; use as an index into a data table ; with the receive timer values FF3A BD 5A FF LDA R232TB-2,X ;index into baud rate divisor table FF3D 8D 18 91 STA D1TM2L ; and save the divisor into the VIA FF40 BD 5B FF LDA R232TB-1,X ; timer count register FF43 8D 19 91 STA D1TM2L+1 FF46 AD 10 91 LDA D1ORB ;read RS232 output register FF49 68 PLA ;restore IFR bitmap FF4A 09 20 ORA #%00100000 ;T2 interrupt flag FF4C 29 EF AND #%11101111 ;pass T2 int. flag but not CB1 FF4E 8D 1E 91 STA D1IER ;save new interrupt bitmap FF51 AE 98 02 LDX BITNUM ;get total number of bits to TX/RX FF54 86 A8 STX BITCI ;save as receiver bit count FF56 ; FF56 ; EOI - End of Interrupt FF56 ; FF56 EOI FF56 68 PLA ;restore registers FF57 A8 TAY FF58 68 PLA FF59 AA TAX FF5A 68 PLA FF5B 40 RTI ;return from interrupt IRQ Processing ============== The IRQ routine is another very important routine for the VIC, as various housekeeping chores are performed during the interrupt. The processor BRK instruction also points to the IRQ vector, so there is some testing early in the routine to handle the BRK instruction. The entry code is similar to the NMI entry code, and similarly, allows function chaining. For example, one could write a small IRQ routine to provide keyboard "click" feedback, with the code activated during IRQ processing. FF72 ;============================================== FF72 ; IRQ - IRQ transfer point FF72 ;============================================== FF72 IRQ FF72 48 PHA ; save .A FF73 8A TXA FF74 48 PHA ; save .X FF75 98 TYA FF76 48 PHA ; save .Y FF77 BA TSX ; get stack pointer FF78 BD 04 01 LDA FBUFFR+4,X ;look in stack for PSW BRK flag FF7B 29 10 AND #%00010000 ;bit4 of PSW; breakpoint or IRQ? FF7D F0 03 BEQ BRKSKIP ;IRQ, branch FF7F FF7F 6C 16 03 JMP (BRKVP) ;jump to breakpoint processor, ; which is the WARMST location. FF82 BRKSKIP FF82 6C 14 03 JMP (IRQVP) ;jump to normal IRQ routine at ; $EABF When the code is finished determining if the interrupt was triggered by a timer tick interrupt or through a BRK instruction, the IRQ code continues at $EABF. If it is a BRK instruction, code execution continues at the WARMST location. The rest of the IRQ code calls the clock update routine, handles blinking the cursor, tape motor control, and scanning the keyboard -- all functions that could be considered "user interface" functions. EABF ;===================================================== EABF ; IRQVEC - IRQ Vector EABF ; EABF IRQVEC EABF 20 EA FF JSR UDTIM ;update system clock FFEA=>F734 EAC2 A5 CC LDA BLNSW ;cursor enable (0=enable) EAC4 D0 29 BNE IRQVEC2 ;non-zero, so skip blink code EAC6 EAC6 C6 CD DEC BLNCT ;decrement blink count EAC8 D0 25 BNE IRQVEC2 ;not reached 0, so move on to ; cassette timing stuff EACA A9 14 LDA #$14 ;reset blink timer to 20d (ms) EACC 85 CD STA BLNCT ;save new count EACE A4 D3 LDY CSRIDX ;get cursor column EAD0 46 CF LSR BLNON ;get blink phase into C flag EAD2 AE 87 02 LDX CSRCLR ;get color under cursor EAD5 B1 D1 LDA (LINPTR),Y ;get character code of current char EAD7 B0 11 BCS IRQVEC1 ;blink already on, so continue EAD9 EAD9 E6 CF INC BLNON ;increment blink on flag EADB 85 CE STA GDBLN ;save char under cursor EADD 20 B2 EA JSR CCOLRAM ;get pointer to color RAM EAE0 B1 F3 LDA (COLRPT),Y ;get color code for cursor location EAE2 8D 87 02 STA CSRCLR ;save it EAE5 AE 86 02 LDX CLCODE ;get color under cursor EAE8 A5 CE LDA GDBLN ;get char again EAEA EAEA IRQVEC1 EAEA 49 80 EOR #%10000000 ;update cursor with blink phase EAEC 20 AA EA JSR PRNSCR1 ;set char and color EAEF EAEF IRQVEC2 ;scan for tape switch pressed EAEF AD 1F 91 LDA D1ORAH ;read I/O bitmap EAF2 29 40 AND #%01000000 ;cassette switch pressed? EAF4 F0 0B BEQ IRQVEC3 ;no, turn motor off EAF6 EAF6 A0 00 LDY #$00 ;set motor "on" EAF8 84 C0 STY CAS1 ;clear motor interlock flag EAFA AD 1C 91 LDA D1PCR ;get PCR bitmap EAFD 09 02 ORA #%00000010 ;set motor control bit to "on" EAFF D0 09 BNE IRQVEC4 ;go to timer1 test EB01 EB01 IRQVEC3 ;set motor to "off" EB01 A5 C0 LDA CAS1 ;get cassette interlock flag EB03 D0 0D BNE IRQVEC5 ;is flag 1, then exit-motor is off EB05 EB05 AD 1C 91 LDA D1PCR ;get PCR bitmap EB08 29 FD AND #%11111101 ;set motor control bits to "off" EB0A EB0A IRQVEC4 EB0A 2C 1E 91 BIT D1IER ;IER bit7=IERs/c bit6=T1 EB0D 70 03 BVS IRQVEC5 ;is timer 1 still enabled? Yes, ; skip update EB0F EB0F 8D 1C 91 STA D1PCR ;set motor status EB12 EB12 IRQVEC5 EB12 20 1E EB JSR ISCNKY ;scan keyboard EB15 2C 24 91 BIT D2TM1L ;D2T1 latch LSB bits 7-6 EB18 68 PLA ;restore registers and return from EB19 A8 TAY ; interrupt EB1A 68 PLA EB1B AA TAX EB1C 68 PLA EB1D 40 RTI Conclusion ========== The VIC's NMI and IRQ routines are important to the smooth operation of the VIC, handling a few critical functions that make an impact on usability. The NMI handler deals with soft-reset processing and RS232 communications timing, and the IRQ handler deals with the cursor, the keyboard, and cassette deck timing. The division of labor between the two routines is a sensible one. The routines are relatively compact, but allow expansion through chaining. Next time, we'll examine more routines in the VIC's KERNAL, including some of the routines called from NMI and IRQ. ....... .... .. . C=H #18 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: geoWrite Disassembly Notes By Todd S. Elliott - Eyethian@juno.com Introduction ============ As part of an effort to learn GEOS programming and to improve an application I was working on, I disassembled geoWrite 128, and along the way, I made several modifications to make it work with modern day GEOS systems. This article contains the fruits of my efforts and some of the lessons I learned. The first part of the article describes largely the territory that comes with GEOS programming. The second part discusses my particular disassembly procedure, and the last part contains the actual disassembly notes to geoWrite 128 v2.2. Background ========== When GEOS came out in mid-1980's, I tried it and it was cumbersome and slow. I took an immediate dislike to it and sparingly used it, as if it were a demo. But, CMD arrived with their powerful peripherals and I began to enjoy using GEOS. Little did I realize it, GEOS insidously has made me a convert and a believer in the fact that a c64/128 can do a GUI OS. But, I was still a user all of this time, and I'll admit that I didn't think about programming for it and was a little bit intimidated. But, the WWW craze hit, and there were browsers popping up everywhere. All of a sudden, scarce GEOS information was made immediately available, including programming docs. I thought to myself, why not try GEOS programming? All I needed was an idea. Naively, I thought I would try to create a graphical (mono) browser that could read in HTML files off a local system, and thought that GEOS 128 was a natural fit. Thus, 'Constellation' was born, nutured and died an untimely death, like so many of my other projects before and after then, and they shall remain nameless. :( First off, I had to use geoProgrammer and it was not an easy package to use and master, and its manual was the heaviest in the business. Secondly, GEOS uses an event driven nature of programming and that goes against my beliefs in programming for the CBM 8-bit machines for years. Third, there were a lot of system calls at my disposal and I really didn't know how to use them effectively, or used wrong calls, and Berkeley Softwork's own programming docs had inaccuracies and incompleteness. Fourth, GEOS 128, while a marvel and a technological breakthrough, it was too limited, in my view, for a serious application such as a mono graphical browser. My main obstacle, as one would strangely call it, to GEOS programming is the Berkeley Softwork's (BSW) own programming suite, geoProgrammer. One main feature that set it apart from other assemblers was that it had a linker. This was a new concept for me and took a while to warm up to the idea. (BTW, Merlin is the only other assembler that I know of which uses a linker.) Next, Berkeley Softworks added a host of features, directives, psuedo-ops and even included a very powerful symbolic debugger in this programming suite. This made programming for GEOS a complex task and I really had to keep tabs on my projects and track my GEOS development. This complexity was only exacerbated by several bugs that made GEOS programming an exactingly task. But if used correctly, geoProgrammer does a very good job in creating GEOS programs easily and quickly. Fortunately, Concept, Maurice Randall's integrated development environment for Wheels, comes to my rescue by fixing most of the problems plaguing geoProgrammer and boosting productivity in programming for GEOS. Despite Concept's ease of use and its integrated development environment, one still has to know geoProgrammer in order to use it effectively. This is because Concept has an integrated development environment in which it acts as a desktop and allows the programmer to select geoWrite, geoAssembler or geoLinker and work on source code files. Secondly, Concept does not work in GEOS and is specific to the Wheels upgrade. For more information, download Concept at: http://people.delphi.com/arca93/files/concept.wr3 There are other development packages available for GEOS programming, and I won't go into them as I never have used them but will name them for the sake of completeness. There was a program called geoCOPE, which was reviewed in the Transactor and was billed as a simple alternative to geoProgrammer for novices who desire to learn a little bit about GEOS programming. Then there's the MegaAssembler, a german GEOS programming suite which is supposedly powerful and complex as geoProgrammer. I do not know if it is in the public domain or if it has an English version. Once I got past the intricacies of geoProgrammer, I was stymied at first by the event-driven nature underlying GEOS. I was conditioned to create a main loop checking for user input and do various activities as necessary. The opposite is used in GEOS. I had to GIVE up control of the application to the GEOS main loop and let it do its job. That meant I had to set up the icons, the menus, the screen interface, etc. and do an RTS. The GEOS main loop then takes over and if an icon was clicked upon, for example, my application finally takes control again and acts upon the icon click. As I got used to it, I began to appreciate the event-driven nature of GEOS and how it has made my job programming for GEOS that much easier. The event-driven nature of GEOS is backed up by a phalanx of GEOS system calls, of which most can be accessed by the programmer without the need to ever use the KERNAL at all. Some system calls can be quite complex and can basically take over the entire computer or can be very destructive if used the wrong way. To make matters worse in trying to understand this entirely new graphical OS, BSW's own programming docs were incomplete and had inaccuracies. Despite that, both programming docs, the Official GEOS Programmer's Reference Guide and the Hitchhiker's Guide to GEOS contains a lot of useful information and when taken together, is truly the 'bible' on GEOS programming. Anyone wishing to do serious GEOS programming needs both books. Still, I managed to overcome these obstacles to GEOS programming and actually created a user interface for Constellation. But I ran into a serious limitation of GEOS 128; it had no memory management routines. I required that Constellation be able to handle HTML files ranging from 254 bytes to 2Mb monsters with equal aplomb. GEOS 128 has system calls for using REU memory, but I was not very familiar with using them and there was no way of telling how GEOS 128 used expansion memory. Secondly, I wasn't too sure on how to display text and graphics with ease and whatever methods that I looked at, it simply looked too cumbersome and difficult. However, there was a 'browser' already in the GEOS environment; users simply do not view it as that way. It's geoWrite, and it reads and mixes in text and graphics with ease. Secondly, it is a full-blown BSW application that works seamlessly with GEOS as opposed to Constellation's often mysterious crashes. At that point, I decided to scuttle Constellation and open a dissection into the inner workings of geoWrite 128 and use that knowledge to revive Constellation. And as a bonus, I would learn a lot more about GEOS programming far better than what BSW's own docs could provide. Only that it exactly hasn't turned out that way so far... As I progressed into disassembling geoWrite 128 v2.1, I came across several sections of code and said to myself, 'I could change this or that'. There were code that mostly dealt with file handling, and this stuff was obsolete by the time high powered GEOS systems came into fruiton. For example, BSW put in code that merely 'toggled' the data device, and at that time, it was sensible as two drive systems were possible. But today's GEOS systems can support up to four drives and this code is an annoyance and limits geoWrite 128's usability in modern day GEOS systems. So, I disassembled geoWrite 128 with the full intention of porting such GEOS knowledge to Constellation and wound up improving geoWrite 128 instead. Here's what I improved so far in geoWrite 128 v2.2: * Complete four drive support in its file requestors. * Capable of booting up from any drive (A-D) when a datafile is clicked upon. Improved booting up sequence. * Loads completely into expansion memory in Wheels equipped systems. In this case, geoWrite 128 no longer needs to access the disk device to retrieve its own modules, and simply fetches them from the faster RAM expansion. A very useful feature for users who only have 41's, 71's, 81's or FD's. This feature does not work in GEOS 128 because there is no reliable way of knowing how it manages RAM expansion. * Semi-intelligent - Does not prompt the user to insert a new disk if non-removable media is used. * Displays the DISK icon when a Native ramdisk is used. Previously, geoWrite 128 would not display a DISK icon (used to change disks or partitions) when a ramdisk is accessed. But native ramdisks have their own subdirectories and the DISK icon is activated accordingly. * Also displays the DISK icon if a ramlink 1581 partition is used. * The DISK icon is also displayed in the disk device from which geoWrite 128 was loaded from, because geoWrite 128 is now 100% ram resident. * Autodetects whether it is running in GEOS 128 v2.0 or Wheels 128 systems. The rest of this article mainly focuses on how geoWrite 128 v2.2 is constructed, how to disassemble a GEOS program, and mostly tries to offer inspiration for others to undertake GEOS programming endeavors. There is so much that needs to be done and can be done in GEOS. Without further ado... Onward! Disassembly For Dummies. :) =========================== Before we begin with the disassembly notes, let's describe how I conducted the disassembly of geoWrite 128 v2.1. In fact, a program called geoSourcer (64/128 versions) was recently released into the public domain while I was busy disassembling geoWrite 128 v2.1. Since I haven't disassembled seven other VLIR modules, I plan to use geoSourcer to do the job and will post a review, tutorial or critique of sorts either here in a future issue of C=Hacking or on comp.sys.cbm. First, I personally use the DarkStar suite of disk utilities (ZipCode, etc.) and use its excellent disk editor. The reason is that I need to 'link' or 'de-link' the individual VLIR modules from the rest of the program for later disassembly or reassembly. One must have knowledge of how VLIR files are structured at this stage of disassembly. There are two formats under GEOS; the sequential format (not to be confused with SEQ files) and the VLIR format. The sequential format simply consists of a file running in a contigious fashion and is loaded in at once. The VLIR format is similar to CBM REL files in which there are 127 individual records, and each record can basically be of any length. VLIR files are broken into records, or if in program files, 'modules'. With respect to programs and not datafiles, VLIR #0 will be the main module which is loaded and executed first in GEOS. In turn, this module will load in other modules in its VLIR file structure whenever necessary. This allows for much larger GEOS programs to run and still have room to spare. For example, geoWrite 128 is 35K, and if it were a sequential file, there would be no room left over for the actual datafile to coexist. What about geoPublish at 99K? The VLIR file format is the only way geoPublish can run in GEOS and on the CBM 8-bit platform. In essence, the VLIR format allows GEOS to use the disk device as virtual memory device by loading in modules as needed. geoWrite 128 v2.1 consists of eight VLIR files. VLIR #0 loads in at $0400, and contains the main 'meat' of the program, with icons, menus, dialog box info, etc. VLIR #1 contains mostly screen setup routines, the geos kernal ID check, checking the printer driver and other initialization routines. VLIRs #2 through #4 are unknown at this point, while VLIR #5 contains the main routines for datafile handling. VLIR #6 is unknown, while VLIRs #7 and #8 contain the printing code. VLIRs #1 through #7 all load in at $3244, and VLIR #8 loads in at $0680. I focused on VLIR #0 and #5. To disassemble the VLIRs, I located the record on the disk and created a separate directory entry pointing to the record as a PRG (non-GEOS) file. I looked at the first two bytes of the file. They are actual pieces of code, but outside of GEOS, they are mistaken for as load addresses. I wrote down these two bytes somewhere so I could later retrieve them. At this point, I have to know its true load address. Let's say that it's $3244. So I replaced these first two bytes with the true load address plus two, in a low/high byte order, as in $46 $32. I'm done with the disk editor at this point and used a disassembler program. The disassembler read in the load address -- my user supplied value of $3246 -- and went from there, disassembling that particular VLIR record. I used a symbolic disassembler modified for LADS 128 by James Mudgett and it works in 128 mode. Theoretically, I could supply a symbol file containing GEOS equates and it will produce some GEOS compliant source code. I haven't done this yet. Next, I used the Make-ASCII program by the authors of EBUD to convert the PRG disassembly to a SEQ petascii file. I used EBUD to modify the source code file, add GEOS equates, fix some minor addressing problems in the ML code, add comments, etc. In short, I followed the program flow and logic and adding comments here and there, fixing up stuff, etc. This is the true guts and grits of disassembling and takes up quite a bit of time, reference to documentation, etc. At times, this process was enlightening and at times, it was quite dull and boring. (Ever try to translate codes into ASCII text that GEOS uses?) My best friend during this disassembly process was dialog boxes, menus, icons and prompts. Why? They shed light on the calling routines. Some routines were so obscure at an initial glance that I couldn't figure them out. But when it called a dialog box, presto! I understood now what the routine was trying to do in the first place. This disassembly process goes on and on. Maurice Randall also was very patient in answering my email correspondence as I progressed through the disassembly. In case if I haven't said it, I'm saying it now... THANKS! :) Anyway, the reason why I decided to use non-GEOS tools is because I wanted to produce an exact copy of the original code from my disassembled code. I couldn't do that with geoProgrammer as it doesn't always produce an exact copy. Maurice has fixed many problems in geoProgrammer with the release of Concept and can be downloaded from his website. I've been using Concept to assemble the patcher program that patches a user's copy of geoWrite 128 v2.1 and have full confidence in that programming package. Upon assembling the source code, I ran a compare of the resulting code against the original PRG file that I extracted earlier in a disk editor. I used my Action Replay v6 ML monitor to do the comparisons. If the files did not match, then I went back into the disassembly and figured out why, etc., and fixed them. Once I did have a match, then I knew my disassembly was perfect and that I could add any changes to it I wanted, etc., and is what I've done with geoWrite 128 v2.2. Having a perfect copy of the VLIR #5 to work with, I went back into my disk editor to patch it back into the VLIR application on a 5 1/4 disk. I did this by modifying the VLIR table as to include that PRG file as a VLIR record and remove the directory entry pointing to it as a PRG file. Remember the modified load address of $3246? I removed it and added the original two bytes of code, so the GEOS application would run fine within the GEOS environment. As for serialization and the GEOS Kernal ID check, I don't want to get into this area, but suffice it to say that I used the Action Replay v6 ML monitor to remove these. You may have to remove these if the code you want to disassemble is somehow blocked by serialization or serial number checks. This is also an another reason why I decided to use non-GEOS tools. GEOS can't remove the serialization by itself, obviously, and Action Replay v6 does not run in 128 mode and may crash the computer when running in 64 mode running GEOS. The GEOS Kernal ID check is a form of copy protection and is used often by major BSW applications. This is largely the process I've been using to patch geoWrite 128 v2.1 to a v2.2 by modifying VLIR #0 and #5. I haven't touched other modules yet and there are 7 more modules to go. :( Hopefully I haven't missed anything and that it has been helpful and instructive. I plan to finish disassembly of geoWrite 128 v2.1 and will certainly post more disassembly notes in the future, either in C=Hacking or wherever appropriate. Disassembly Nota Bene ===================== The rest of the article will mainly focus on routines that I've either disassembled or revamped in the geoWrite 128 v2.2 upgrade. The labels as used are largely from BSW itself, in the Official GEOS Programmer's Reference Guide and the Hitchhiker's Guide to GEOS. The format for the article is as follows: * The routine name is displayed, along with its actual address and VLIR module number and then the category it is under. For illustration of the admittedly unorthodox addressing scheme, let's give an example: $3a41x5. The $3a41 is the actual address, and the x5 is the VLIR module number of where it is located. (VLIR modules #0 through 8) * A brief description of the routine follows in the function field. * Parameters, if any, are described. * Variables or routines that the routine will use are listed. * Listing of any variables or flags that are set or accessed when the routine is done. * Posting of any psuedo-registers or registers that are used by the routine and not preserved. * The description of the routine, with a sequence of events or branches. * Finishes up with any proposed modifications that would be suitable for future work. The disassembly comments pertaining to VLIR #5 of geoWrite 128 are only applicable to the v2.2 version as patched, and not its original v2.1 version. Traces of the original v2.1 version still remain, notably the lbxxxx labels throughout the disassembly. The numbers following the 'lb' refers to the actual locations in the v2.1 version. Any absolute addresses that do not contain a VLIR number is presumed to be addresses for VLIR #0. Also, this is not 100% complete, there are still several gaps of which I have not been able to figure out and they are duly noted, or have been guesstimated as such. The patcher program is now available and is being marketed by yours truly, a multinational corporation. :) Ok, so I'm no corporation nor paper tiger, but email me for more details at eyethian@juno.com about acquiring the patcher program. There is a geos programming emailing list maintained at cbm.videocam.net.au. One need not join, but can read past archives covering various topics of geos programming. The URL for the geoProgramming:The Millennium (GTM) emailing list is: http://cbm.videocam.net.au/gtm/ That all said, there are plenty of GEOS programs, especially those BSW applications, that are ripe for disassembly for further exploration and knowledge of GEOS programming. As a bonus, these programs really do need modifications as to make them work all but seamlessly on modern day GEOS systems with hulking CMD power. I hope that the following disassembly notes will be instructive to people wishing to get into GEOS programming or to upgrade other existing BSW applications. At any rate, enjoy. setProgDevice & - $0daax0 - Disk Routines setDataDevice - $0daex0 - Function: Sets the disk device from which the application was loaded from. Sets the disk device for which the datafile activities take place. Parameters: None. Uses: progDrive dataDrive toggleZP SetDevice Returns: None. Destroys: .A Description: Depending on the entry point, it checks either progDrive or dataDrive and issues a SetDevice call to change the active disk device. Proposal: This needs to be overhauled, as the disk device driven model is obsolete in modern day GEOS systems. This needs to be changed to a directory driven model, where directories rule and not the disk device. Users can change directories, subdirectories, partitions, etc. and the program needs to keep track of all of this activity and the current routines fall short. VLIROps - $0dddx0 - Disk Routines Function: Loads in geoWrite 128's own individual VLIR modules. Parameters: .A containing the VLIR number for the geoWrite 128 module to load. Uses: PVLIROps CVLIRNum - Current geoWrite 128 VLIR module in memory setProgDevice RVLIRB2F - restores a geoWrite VLIR module from BackRAM $3172 - Address location of which starting tracks and sectors for the nine individual VLIR modules can be found and is used as an index. $2798 - ReadFile SVLIRF2B - saves a geoWrite VLIR module to BackRAM GotoFirstMenu lb29dc - Error message lb233a - Canned DB to display error message Returns: None. Destroys: r1, r2, r7, r10, .A, .X and .Y. Description: First, it loads r7 with $3244 for the loading address for the VLIR modules. Next, it copies r7 into r10 for the error handler. Next, it checks the value passed in .A against CVLIRNum and if they match, then the VLIR module in question is already in memory and there is no need to load it in from the disk device, and it simply RTS's without any further action. If the numbers do not match, then CVLIRNum will take on the number passed in .A and opens the disk device housing the application through setProgDevice. Next, it calls RVLIRB2F, and sees if a copy of the VLIR module is already located in BackRAM. If so, the routine simply restores the VLIR module by fetching it from BackRAM, and doesn't need to access the disk device at all. If the VLIR module is not located in BackRAM, it then checks the index located at $3172 and retrieves the starting track and sector of the VLIR module and issues a ReadFile to load in the VLIR module. The maximum VLIR module size is only 4,000 bytes. Then it calls SVLIRF2B, to determine if the newly loaded in VLIR module should also be saved to BackRAM. If there is an error, a DB is generated and the VLIR module in question tries to get accessed again. Note: There is an alternative entry point, PVLIROps, which is for the VLIR #8, the printing code. The reason is that this module loads in at $0680 as opposed to $3244 for VLIR modules #1 through 7. The entry point is located at address $0de5, or just eight bytes beyond VLIROps, and requires passing r7 the $0680 address. Proposal: This routine can be modified as to allow retrieval of VLIR modules from expansion ram in Wheels or MP3 operating systems. Already implemented in the patcher program for Wheels users. StashGW128 - $2bdbx0 - Setup Function: Stashes all eight VLIR modules of geoWrite 128 v2.2 into expansion memory, and enables the ram-based VLIR code. Parameters: a7L (Wheels flag) and $d4 (LoadOptFlag) Uses: RAMOps - Core ram-based VLIR code setProgDevice obtainRec - loads in an individual VLIR module from disk $3172 - Address location of which starting tracks and sectors for the nine individual VLIR modules can be found and is used as an index. $2798 - ReadFile standard - loads r7 with $3244 deviate - loads r7 with $0680 checkDisk i_MoveData VLIRAMOps - Replacement routine for the following routine: VLIROps - the routine being replaced by the previous routine. CVLIRNum - Current geoWrite 128 VLIR module in memory toggleZP DoRAMOp EnterDeskTop lb29dc - Error message lb233a - Canned DB to display error message Returns: None. Destroys: r0, r1, r2, r3L, r7, r15L, .A, .X and .Y. Description: First, it checks a7L and determines if it is running under a GEOS 128 or Wheels 128 system. If it is GEOS 128, then the routine simply jumps to $3244 of geoWrite's VLIR #1 and goes from there. Secondly, it checks LoadOptFlag to determine if the datafile was passed through the printer icon for printing. If that is the case, then the routine would jump to $3244 of geoWrite's VLIR #1. In either event, no RAM activities take place. If the routine gets past the checks, then it stashes VLIR #1 into expansion memory because it is already sitting there in FrontRAM memory. Next, it calls setProgDevice and proceeds to stash VLIRs #2 through 7 into expansion memory. Lastly, it stashes the memory region from $0680 to $0b80 into expansion memory. Then it loads in VLIR #8, the printing code, into that region beginning at $0680. Next, it performs a SWAP of memory located at $0680 and that in expansion memory, putting the printing code into expansion memory and restoring the original code located at $0680. Next, it fetches VLIR #5 from expansion memory and modifies the checkDisk code as to allow the DISK icon to be placed in the file requestor, even if it is displaying the disk device from which geoWrite 128 originally loaded from. Next, once the code is modified, VLIR #5 is stashed into expansion memory. Lastly, VLIR #1 is fetched from expansion memory. Next, it replaces the code as found in VLIROps with the ram-based VLIR code located at VLIRAMOps. Finally, it jumps to $3244 in geoWrite's VLIR #1. Proposal: None. Code may change to support ram expansion capabilities of MP3 128 systems. VLIRAMOps - $0dddx0 - RAM Routines Function: Loads in geoWrite 128's own individual VLIR modules via expansion RAM. Parameters: .A containing the VLIR number for the geoWrite 128 module to load. Uses: PVLIROps RAMOps CVLIRNum - Current geoWrite 128 VLIR module in memory toggleZP DoRAMOp lb29dc - Error message lb233a - Canned DB to display error message EnterDeskTop Returns: None. Destroys: r0, r1, r2, r3L, r7, .A, .X and .Y. Description: First, it loads r7 with $3244 for the loading address for the VLIR modules. Next, it checks the value passed in .A against CVLIRNum and if they match, then the VLIR module in question is already in memory and there is no need to load it in from RAM expansion, and it simply RTS's without any further action. If the numbers do not match, then CVLIRNum will take on the number passed in .A and checks to see if it is VLIR #8, the printing module. If that is the case, then only 1,280 bytes gets fetched from expansion memory. Otherwise, it's 4,000 bytes. Next, it calculates the RAM expansion address offset of which the correct VLIR module can be found. Lastly, when all registers are prepped, DoRAMOp does the job of fetching the VLIR module into the correct memory location. If there is an error, a DB is generated and the application aborts to deskTop. Note: There is an alternative entry point, PVLIROps, which is for the VLIR #8, the printing code. The reason is that this module loads in at $0680 as opposed to $3244 for VLIR modules #1 through 7. The entry point is located at address $0de5, or just eight bytes beyond VLIRAMOps, and requires passing r7 the $0680 address. Proposal: This routine replaces the disk-based VLIR routines as found in geoWrite 128 v2.1. This routine may need to be changed to support the MP3 128 platform. checkDrives - $2b0dx0 - Disk Routines Function: Checks available drives on a user's system. Parameters: r0L via deskTop with LoadOptFlag r2 via deskTop pointing to diskname of the disk containing the datafile. r3 via deskTop pointing to datafile's filename. Uses: DrACurDkNm DrBCurDkNm DrCCurDkNm DrDCurDkNm setDataDevice setProgDevice $27b0 - FindFile curDrive numDrives toggleZP prepDlgBox enterDeskTop Returns: progDrive - The drive geoWrite was loaded from. dataDrive - The disk device that houses the datafile. Destroys: r6, .A, .X and .Y Description: First, it checks curDrive and stores the value there into progDrive. Next, it stores the same value into dataDrive. It checks then the LoadOptFlag to determine if a datafile was clicked on. If so, it checks the name of disk A and compares it against the name of the disk that has the datafile. If there is a match, then the datafile is on drive A or C. Next, a value of eight is stored into dataDrive. If they are different, then the routine checks for additional drives. If there are no additional drives, then a dialog box is fetched, warning the user that a datafile and geoWrite must be on the same disk on single drive systems. After that dialog box is over, geoWrite quits to deskTop. If there is an additional drive, then geoWrite will put a value of nine into dataDrive. Proposal: This code can be modified to search drives A-D in sequence. This way, a user can click on a datafile anywhere in his/her system and geoWrite 128 can boot up correctly. Secondly, it should use FindFile to nearly pinpoint the proper disk containing the datafile because disknames can be identical. Already implemented in the patcher program. checkVer - $2b75x0 - Misc. Routines Function: Checks the current GEOS version. Parameters: None. Uses: Version c128Flag DoDlgBox enterDesktop Returns: None. Destroys: r0 and .A Description: First checks to see if it is running in GEOS v2.0 or higher systems. Next, it checks to see if it is running on the 128 version of GEOS. If both conditions are not met, then a dialog box is printed to that effect and geoWrite 128 aborts to deskTop. Otherwise, the routine RTS's w/o further action. Proposal: The routine can be modified to check for Wheels or MP3 systems and designate a variable for later routines to rely upon. Already implemented in the patcher program, where it designates a7L as the Wheels flag. toggleZP - $251bx0 - Housekeeping Function: Toggles the state of all zp variables, thereby preserving two zp spaces, one for the actual application usage and the other for stock GEOS/Kernal usage. Parameters: None. Uses: None. Returns: None. Destroys: None. All registers are preserved via the stack. Description: It's a toggle routine. When called, it performs a swap of zero page space located at $80 to $fb towards a buffer located in $2e22, and the buffer contents are similarly swapped back. This way, geoWrite 128 can use that zp region freely, and swap it out whenever calling GEOS Kernal routines that rely on this area, and when these routines are done, the toggle routine is called again to swap back in those values for use in the application. This is the most heavily used routine in geoWrite 128. Proposal: While this routine is nice as it allows the application more zp space, this is unnecessary routine and should be eliminated. It slows down the application somewhat, as this must be called twice whenever a GEOS Kernal call is used. prepDlgBox - $2314x0 - Dialog Boxes Function: Preps the pointers for the actual DoDlgBox function call. Parameters: .A has low byte and .Y has high byte pointing to the dialog box text. Uses: DoDlgBox $2538 - Does something to the VDC Returns: Carry flag is set. Destroys: r5, r14, .A, .X and .Y Description: The routine uses a `canned' dialog box with preset icons, placement and size. The only thing that is controllable is the text. The pointer passed on in .A and .Y registers points to the text. Commonly used to create error dialog boxes with an OK icon. Proposal: None. layoutScreen - $32cex1 - Appearance Function: Draws up the main menu bar and the overall screen layout for geoWrite 128. Parameters: None. Uses: DoMenu i_GraphicsString Returns: Carry flag is set. Destroys: r0, .A Description: The routine draws the screen layout using various GraphicsString parameters. Additionally, it builds the main menu bar at the top, with its table located at $0bbcx0. Proposal: This may need changing, especially if new menu items are added or old ones deleted and maybe the screen layout should be changed or left as is. InitGW128 - $3c70x1 - Startup Function: Initializes geoWrite 128 with variables, flags and vectors. Parameters: None. Uses: toggleZP closeRecordFile dispBufferOn RecoverVector checkSerNbr Returns: None. Destroys: .A Description: The routine sets the following locations to zero: $41e4-e5, $0200, $021a, $2dfa, $db, $de, $e1, and $f7. It also closes geoWrite 128's own VLIR record, and as well as activates the software mouse as sprite zero. It sets the dispBufferOn flag as to allow only writes to foreground and sets the RecoverVector to point at $2199. It sets $f1 to have a value of $c0 and sets $023a & $023c to have a value of $ff. Proposal: This may need changing, when new variables or flags need be set. CheckPtrDrv - $33e7x1 - Startup Function: Checks the status of the printer driver and loads it in, if necessary. Parameters: None. Uses: loadPtrDrv getDimensions setProgDevice maxPtrHgt ($2dfb) Returns: None. Destroys: a9L, .A, .X Description: The routine sets the maximum printer height ($02f0) in card rows to maxPtrHgt. Then it loads in the printer driver, and if there is an error, no harm is done as the maximum printer height is already established. But, if a printer driver is successfully loaded in, then a call to getDimensions will place the maximum printer height in card rows at maxPtrHgt. Proposal: Why not just call getDimensions when the printer driver is always loaded into memory in GEOS 128 configuration? InitGW128 - $325cx5 - Dialog Boxes Function: Prints the copyright message and calls the infamous Create, Open or Quit DB. Parameters: None. Uses: $1baf - Turns off text cursor. DrawStripes i_GraphicsString printCopyrightMsg $2538 - issues a dialog box createOpenQuitDB - DB table Returns: None. Destroys: all registers. Description: It turns off the text cursor, draws the striped pattern you see in the upper right corner of the screen, clears the screen with a default pattern, prints out the copyright message, and issues the infamous create/open/quit DB. The routine will then branch to routines that handle creation of datafiles, opening of datafiles or quitting the application. Proposal: None I can think of right now. Maybe abolish this dialog box in favor of allowing the user to boot geoWrite 128 to a blank screen and have him/her to select from a menu an appropriate action to undertake. printCopyrightMsg - $3375x5 - Text Function: Prints the copyright message. Parameters: None, but can only be used once. Uses: crflag $1f05 - calls system font currentMode i_GraphicsString i_PutString Returns: None. Destroys: r1, .A and possibly others. Description: Sets the crflag as to reflect that the copyright message has been printed. That's why this routine can only be used once. It calls the system font, and then uses inline routines to draw and print the copyright message. Proposal: No change. Unless someone patching this copy wants to add their own message. createDocRoutine - $33ddx5 - Disk Routines Function: Creates a new geoWrite v2.1 datafile. Parameters: none. Uses: queryFilename nameBuffer $27b0 - FindFile lb3f67 - File exists text prompt $2314 - issues a DB createNewFile $de - currently unknown $d3 - currently unknown currentMode TitlePage HeaderHeight FooterHeight FirstPage PageHeight CPageHeight PageWidth gPFlag $2393 - Something to do with fonts SetScrollSprite printDataName lb404a - creating file error text string $233a - issues a DB InitGW128 Returns: None. Destroys: r6, .A, .X and possibly others. Description: First turns on the icons related to drive activity and then calls the DB that prompts the user to enter the filename. If the user cancels or no filename was inputted, the routine then goes back to the main DB in InitGW128. r6 is then loaded with the inputted filename and the FindFile function is called. If a file exists on disk, the routine informs the user with a DB and prompts the user for a new filename. It then calls createNewFile and sets up variables relating to page length and width, margins, headers and footers, etc. Next, it sets up the fonts and the scrolling sprite. Last, it will print the datafile's filename in the upper right corner of the screen. Proposal: One thing could be changed; instead of requiring the user to make a new filename upon notice that a file already exists, the user could override it and use the same filename, but get rid of the old one. This could be a `TEMP' file concept. If a new version of geoWrite is written with a new version of a datafile, then this routine would need be revamped accordingly. Setup4DB - $3449x5 - Disk Routines Function: Preps the dialog boxes with the appropriate drive icons and the DISK icon. Parameters: None. Uses: nameBuffer setDiskIcon checkDisk setDataDevice readDiskName iconLTable iconHTable driveType curDrive onLTable onHTable offLTable offHTable Returns: None. Destroys: r1, r7H, r0, r5, .A, .X & .Y Description: First, it delimits the nameBuffer. It then checks for a ramdisk and application disk in order to turn on/off the DISK icon. It then reads in the disk name so that it will appear in the requestor DB. Next, it checks all available drives and activates the drive icons accordingly. Last, it loads in r5 with nameBuffer. Proposal: No change. fileRequestor - $34b2x5 - Dialog Boxes Function: Calls up the file requestor box w/ 4 drive support. Parameters: None. Uses: setup4DB permName - (Write Image) restoreDBTable dBTable - DB table w/ DBGETFILES $2538 - Issues a DB InitGW128 curType lb3f45 - pointer to text (Insert New Disk) $2314 - Issues a DB $27ad - OpenDisk openServiceRoutine dataDrive setDataDevice toggleZP Returns: None. Destroys: r7L, r10, r0L, .A, .Y Description: When called, the routine first preps the drives via the setup4DB routine. Since the dBTable would be shared by other requestors, some modifying code was used. The routine modifies the dBTable w/ restoreDBTable data. It sets up DBGETFILES to search for only APPL_DATA files containing the permanent name string of `Write Image'. Finally, the DB is issued, complete w/ 4 drive support. If the user cancelled, then it aborts back to InitGW128. If the user opened a file, then openServiceRoutine is run. If the user clicked upon the disk selection, it checks to see if non-removable media is present. If it is removable media, then an another DB is issued to prompt the user to enter the new disk. Otherwise, it displays the file requestor again w/ new contents of the newly selected partition. If a drive icon is clicked, it will issue a setDataDevice command and OpenDisk the new drive and repeats the file requestor w/ new contents of the newly selected drive. Proposal: No change. openServiceRoutine - $364dx5 - Disk Routines Function: Opens a geoWrite v2.1 datafile. Parameters: None. Uses: setDataDevice nameBuffer InitGW128 $27b0 - FindFile TSDFHdr dirEntryBuf lb35fc - pointer to DB data (File Write Protected) $2538 - Issues a DB PageWidth CheckDFVer lb3f86 - text pointer (version higher than v2.1) $2314 - Issues a DB convertDataFile lb356e - extracts global variables stored in a datafile's file header $260c - r0 points to filename OpenRecordFile toggleZP AdjPageWidths windowBottom $d3 - currently unknown $de - currently unknown SetScrollSprite printDataName lb403d - text pointer (opening file error) $233a - Issues a DB Returns: None. Destroys: r5, r6, r0L, .A, .X & .Y Description: First, it calls setDataDevice, and checks nameBuffer to see if a filename was selected. If no name is selected, it quits to InitGW128, otherwise, it will find it by FindFile. It then stores the T/S for the datafile's fileheader, and checks to see if it is write-protected. Next, it stores in a max width of 639 into PageWidth. Next, it checks the datafile's version and if necessary, converts it to a v2.1 format. Next, it gets global variables stored in the datafile's fileheader and stashes it into zero page. Last, it finally opens the datafile with OpenRecordFile. It will also read in geoPublish data and adjust page widths. It stores a $00 in $d3 and $de and as well as a $c7 (scanline 199) in windowBottom, Last, it will set the scrolling sprite and prints the datafile's filename on the upper right corner of the screen. Proposal: Changes may be necessary if new formats are to be supported in addition to regular geoWrite v2.1 datafiles. checkDisk - $371bx5 - Disk Routines Function: Checks to see if the data drive is a ramdisk or holds the application and sets the DISK icon accordingly. Parameters: None. Uses: a7L dataDrive progDrive driveType cableType Returns: Carry clear indicating the existence of a ramdisk or that the application is on that same disk as designated as a data device. Carry set means that the DISK icon can be placed in a requestor DB. Destroys: .A, .Y Description: First, it checks to see if the application is on the same disk as being designated as a data device housing the datafile. If that is the case, then the DISK icon cannot be placed. Next, it checks the appropriate driveType entry to check the existence of a ramdisk. If a 41/71 ramdisk is being used, then the carry flag is cleared as to prevent the DISK icon. If a 81 ramdisk is being used, then it checks the Wheels flag at a7L to determine if it's a 81 RL ramdisk as shown in cableType. If that is the case, then the carry flag is set as to allow the DISK icon, otherwise it is cleared as to prevent the DISK icon because it's just a regular 81 ramdisk. The carry flag is set if a native ramdisk is being used, as to allow the DISK icon to appear. Proposal: If geoWrite 128 can fully load itself into RAM, then the DISK icon can be placed in the requestor DB even when the disk device houses both the application and the datafiles. As it stands, the DISK icon must be removed because the user might select a different disk, partition or subdirectory and the application then can't find its own VLIR modules. Instant crash. *UPDATE* I have added ram routines and geoWrite 128 is now 100% RAM resident, and therefore, the DISK icon can be displayed in this case. But there is one major caveat, among others, in using this approach. The fonts are keyed to the disk device from which geoWrite 128 was loaded from. If the user changes the disks or partitions or subdirectories from that same disk device, then geoWrite 128 would not be able to find the font data and may lead to unpredictable results. The same goes for Text Scraps and other features that are tied to the disk device from which geoWrite 128 originally loaded from. In the future, I would have to work on font routines and other routines as necessary as to eliminate this dependence on the original disk device. readDiskName - $3739x5 - Disk Routines Function: Reads in a disk name housing the current datafile. Parameters: None. Uses: DrACurDkNm DrBCurDkNm DrCCurDkNm DrDCurDkNm diskName dataDrive Returns: None. Destroys: r0, .A, .Y Description: Depending on the value of dataDrive, r0 is loaded with the appropriate current diskname and its contents are transferred to diskName. This way, the file requestor can display the disk name along with other info. Proposal: No change. recoverFile - $3781x5 - Disk Routines Function: It recovers a file. Parameters: None. Uses: GotoFirstMenu $dd - some kind of unidentified flag i_MoveData CNameBuffer NameBuffer openServiceRoutine lb4005 - text pointer (Cannot recover) $2314 - Issues a DB Returns: None. Destroys: .A, .Y Description: Calls GotoFirstMenu to close its menu selection, then tries to recover the file by copying the current filename into the filename buffer and calling openServiceRoutine. It does check the flag at $dd before determining whether a file could be recovered. If it can't be recovered, a DB is issued to that effect. Proposal: No change. RenamFile - $379bx5 - Disk Routines Function: It renames a file. Parameters: None. Uses: GotoFirstMenu queryFilename CNameBuffer NameBuffer $27b0 - FindFile toggleZP RenameFile printDataName SetScrollSprite lb40a7 - text pointer (File Exists error) $2314 - Issues a DB $260c - r0 points to NameBuffer Returns: None. Destroys: r6, r0, .A, .X, .Y Description: First calls GotoFirstMenu before calling queryFilename. The new name is then put into CNameBuffer and calls the RenameFile routine. Of course, if a file exists, then the DB pops up, reporting the error. Last, it prints the new filename onto the upper right corner of the screen and sets the scrolling sprite. Proposal: No change. queryFilename - $37dbx5 - Dialog Boxes Function: Prompts the user for a filename for a v2.1 datafile. Parameters: .A must pass either a zero or a $12 to turn off/on the drive icons and the system DISK icon from the DB. Uses: renTable setup4DB NameBuffer lb3f45 - text pointer (Insert New Disk) $2314 - Issues a DB replaceDBTable dBTable qDBTable - DB table data $2538 - Issues a DB dataDrive setDataDevice $27ad - OpenDisk curType Returns: .A containing the value of r0L. Destroys: r0L, .A, .X & .Y Description: First, it shuts off/on the drive icons and the DISK icon before calling setup4DB to prep the DB with appropriate icons. Next, it modifies the DB table to replace the DBGETFILES with DBGETSTRING, as this DB table is also shared by the file requestor. If a drive icon was clicked upon, the drive gets accessed and the DB is reissued with the updated icon data. If a DISK icon was selected, it will prompt the user to insert a new disk, or skips that process if it's on non-removable media. Last, upon exiting, it loads .A with r0L so that the calling routine will know what the user selected. Proposal: No change. createNewFile - $3839x5 - Disk Routines Function: Creates a new geoWrite v2.1 datafile. Parameters: None. Uses: CPageHeight NameBuffer lb38c4 - (word) Page Height stored in a datafile's fileheader dFileHeader toggleZP SaveFile $27b0 - FindFile CheckDFVer dirEntryBuf TSDFHdr $260c - r0 points to filename OpenRecordFile $27a7 - AppendRecord $27aa - UpdateRecord PointRecord Returns: None. Destroys: r10L, r9, r6, .A, .X & .Y Description: First, it stores the current page height into the global variables as hidden in the datafile's fileheader. Next, it points the first two bytes of the datafile's header (dFileHeader) to the filename and calls SaveFile. Next, it calls FindFile to load in the newly created datafile's fileheader into memory and preserves its t/s pointers. It also checks its version identifier. Next, it opens the datafile and creates 127 blank VLIR records and updates it and then finally points to VLIR #0 for further handling. Proposal: Changes may be necessary to support a newer format or support other file formats. convertDataFile - $393ex5 - Data Handling Function: Converts a datafile to a v2.1 format. Parameters: .Y contains $31 or higher to correspond with ascii values of 1 thru 9. .A contains $32 or lower to correspond to ascii values of 2 through 0, i.e., .A is the `2' in `v2.1' and the .Y is the `1' in the `v2.1'. Uses: lb3fbf - points to the version string of `v2.x' where x = 1 or higher VerFlag convertFileDBTable $2538 - Issues a DB toggleZP sysDBData fileHeader lb38bd - global variables controlling document lb356e - extracts these global variables from the file header TSDFHdr $27b6 - PutBlock $260c - r0 points to filename OpenRecordFile PointRecord lb3978 - modifies a VLIR record of the datafile NextRecord $279e - Closes the VLIR datafile lb3fc1 - text pointer (Converting File Error) $233a - Issues a DB Returns: .A to indicate error status ($00 = no error) Destroys: r4, r1, .A, .X & .Y Description: First, it modifies the file header of the datafile as to make it to show v2.1. Depending on the values passed, it will set VerFlag as to control further file conversion to a v2.1 format. Next, it issues a DB informing the user that it's converting the datafile to a v2.1 and asks permission. Then it modifies the fileheader of the datafile as to incorporate new global variables. With the fileheader modified, a PutBlock call is issued. Finally, it will read in all used VLIR records of the datafile and convert them to a v2.1 format. Next, it will read in the header and footer VLIR records and modify them as well. Last, it will then close the datafile. Proposal: Changes may be necessary to support a newer format or support other file formats. lb3978 - $39f0x5 - Data Handling Function: Converts an individual VLIR record of a datafile to a v2.1 format. Parameters: The VLIR record must have been already opened. Uses: fileData VerFlag toggleZP ReadRecord lb39da - fixes v1.x ruler escapes to conform to the v2.1 standard lb3fbf - the V2.X identifier string where X is accessed lb3a09 - fixes the width of a v2.0 page to a v2.1 page WriteRecord Returns: None. Destroys: r2, r7, .A, .X & .Y Description: It will first read in a VLIR record of a datafile, and depending on its original version (set by VerFlag), it will modify ruler escapes as to make the current record conform to v2.1 specifications. When all of these modifications are done, the record is written back w/ WriteRecord. Proposal: Changes may be necessary to support a newer format or support other file formats. lb39da - $3a52x5 - Data Handling Function: Converts an individual VLIR record of a datafile from a v1.x to a v2.1 format. Parameters: The VLIR record must have been already opened and read w/ ReadRecord. Uses: fileData Returns: None. Destroys: .A & .Y Description: It merely moves the first 20 bytes of the first ruler escape on a VLIR record back by seven bytes and zeroes out some parts of the ruler escape. Proposal: Changes may be necessary to support a newer format or support other file formats. lb3a09 - $3a81x5 - Data Handling Function: Converts an individual VLIR record of a datafile from a v2.0 to a v2.1 format. Parameters: The VLIR record must have been already opened and read w/ ReadRecord. Uses: $d0 - word pointer to last byte of individual VLIR record of a datafile $25a1 - points r15 to start of fileData toggleZP $26d7 - compares r15 against $d0 $25e1 - increments r15 $25db - increments r15 three times $25f7 - skips ruler escapes pointed to by r15 Returns: None. Destroys: r7, r15, r1, .A & .Y Description: The v2.0 page has a width of 1.2 to 7.2 inches, and the v2.1 page has a width of 0.2 to 8.2 inches, and this routine searches for every ruler escape and converts them to a v2.1 format. Proposal: Changes may be necessary to support a newer format or support other file formats. printDataName - $3af1x5 - Appearance Function: Prints the datafile's filename in the upper right corner of the screen. Parameters: nameBuffer must have a filename. Uses: nameBuffer CNameBuffer i_MoveData $1fba - use system font currentMode DrawStripes PutChar PutString rightMargin GetCharWidth Returns: None. Destroys: r0, r1, r11, r13L, .A & .Y, r2L is specially preserved. Description: It moves the contents at nameBuffer to CNameBuffer and then calculates the width of the filename, ensuring that it does not exceed its maximum width. Next, it will print a trailing space, then the filename, and then a leading space, in that little striped box in the upper right corner of the screen. Proposal: No change. runDA - $3bb1x5 - Desk Accessories Function: Allows a DA to be run. Parameters: .A passes the index number of the DA in an internal table. Uses: GotoFirstMenu $d3 - I think this variable holds the current VLIR # of a datafile lb406f - text pointer (Cannot run DA) $2314 - Issues a DB $07c5 - Inverts a rectangle and preserves zp space $0d61 - Closes the current datafile saveTScrap $21ba - saves the portion of the screen $01 - I/O port $d017 - sprite horizontal expand register $4c95 - Currently unknown word variable setProgDevice toggleZP GetFile $21bf - restores the portion of the screen i_MoveData lb3ecd - text pointer (Not enough disk space) lb4058 - text pointer (Running DA error) $233a - Issues a DB $22e3 - Sets up the text cursor loadTScrap $0d6c - reloads the VLIR datafile and points to the last accessed record $851e - default screen color VIDEO_MATRIX FillRam $2555 - Not sure yet what this routine does $1512 - Displays the characters onscreen $2575 - The opposite of $2555 SetScrollSprite $1bbb - turns on the text prompt and draws a couple of rectangles Returns: None. Destroys: r6, r15L, r0, r2L, r1, r10L, .A, .X & .Y, and since a DA is run, pretty much everything else has been hosed, except that geoWrite 128 tries to preserve as much as possible. Description: After calling GotoFirstMenu, it checks to see if it's in header/footer mode and if so, the DA can't be run. Next, it preserves much of zero page and inverts a rectangle twice onscreen. It then closes the current VLIR datafile, saves any Text Scraps, sprite data/expansion registers and the first 24 scanlines of the screen. Finally, it calls and runs the DA. When the DA is done, geoWrite then will restore sprite data/expansion registers, the first 24 scan lines of the screen, color and video data and much of the zero page area. Then it sets up the text cursor, loads in any Text Scrap, and reloads the VLIR datafile and points to its current VLIR record. Next, it will begin displaying the contents of the datafile, enable the text cursor and draws a couple of rectangles, and returns control to MainLoop. Proposal: The I/O calls seem redundant for I/O is always banked in and color data is being restored as well, which is puzzling as geoWrite 128 does not support color. Other than that, no changes. saveTScrap - $3c97x5 - Disk Routines Function: Saves a text scrap. Parameters: None. Uses: TSFlag TSiZe lb3dff - load address of text scrap lb3e01 - size of text scrap setProgDevice lb40c1 - pointer to filename (Text Scrap) toggleZP DeleteFile lb3db8 - pointer to filename for the SaveFile call and also doubles as its fileheader SaveFile Returns: TSFlag holds a zero value to indicate that a Text Scrap is saved, otherwise $ff if something's wrong. Destroys: r0, r9, r10L, .A & .X Description: It checks TSFlag and TSiZe to determine existence of a text scrap, and if so, then attempts to save it. It modifies the fileheader for the text scrap as to include an appropriate load address ($41e4), the ending address, etc. Next, it saves them to the same disk/partition as where the geoWrite 128 application resides. It will delete a prior text scrap if one existed. Proposal: The area, $41e4-$4310, seems too small of a buffer for a text scrap. Maybe this buffer should be enlarged to accommodate large text scraps. Also, this may need to be changed to accommodate future versions or simply accommodate a text album file. quitGeoWrite - $3cb1x5 - Disk Routines Function: Quits the geoWrite 128 application and returns to deskTop. Parameters: None. Uses: SaveTScrap setProgDevice toggleZP EnterDeskTop Returns: None. Destroys: None; the deskTop now takes control. Description: First, it saves the current Text Scrap. Then it returns to the disk/partition that it was originally booted from, restored zero page space and then quit, resuming control to the deskTop. Proposal: No change. SetScrollSprite - $3cbdx5 - User Interface Function: Enables the scrolling sprite that one uses to scroll the document onscreen, in the little box in the middle top of the screen. Parameters: None. Uses: $01 - i/o port $d02d - Sprite 6 color register i_FillRam i_MoveData lb3c73 - sprite data $d01d - expand sprite 6 horizontally Returns: None. Destroys: .A and $01 is preserved. Description: It enables I/O, colors sprite #6 black and expands it horizontally, and defines its bitmap. Proposal: No change. But is the I/O activity redundant because I/O is mapped in already in a GEOS 128 configuration? AdjPageWidths - $3cf6x5 - Data Handling Function: Reads in imprinted geoPublish data and adjusts page widths. Parameters: None. Uses: gPFlag PointRecord toggleZP ReadRecord $2c1d - Page Width High Byte $2be0 - Page Width Low Byte pageWidth Returns: None. Destroys: r2, r7, .A, .X & .Y Description: It will check VLIR #63 of a geoWrite v2.1 datafile and determine whether geoPublish has stashed its internal data there. If so, then gPFlag is set, and geoWrite 128 will read in page widths and compensate page widths. Proposal: Maybe give the user a DB giving an option to remove geoPublish data or leaving it alone. If a user wants to remove geoPublish data, then the document would have to be reformatted. printInfoBox - $3d4dx5 - Dialog Boxes Function: Prints the copyright message and info box. Parameters: None. Uses: GotoFirstMenu lb3cdf - DB table pointer infoBoxText - text pointer to copyright message, etc. $2538 - Issues a DB Returns: value in r0L. Destroys: .A & .X Description: It issues a DB stating the copyright message, its authors, etc. Proposal: Maybe add in the patcher info here. loadTScrap - $3d5fx5 - Disk Routines Function: Loads in a text scrap. Parameters: None. Uses: setProgDevice lb40c1 - points to filename $27b0 - FindFile CheckDFVer lb40cd - text pointer (text scrap beyond v2.1) $2314 - Issues a DB $2625 - r4 points to $8000 $27b3 - GetBlock lb40e7 - text pointer (reading Text Scrap error) $233a - Issues a DB TSiZe TSFlag Returns: TSiZe and TSFlag will have values reflecting the presence of a Text Scrap. Destroys: r1, r5, r6, .A, .Y & .X Description: It will load in a text scrap from the same disk/partition that geoWrite 128 was launched from. Next, it will check its version, and then gets info from the first datablock of the text scrap and copies down its size, sets up the flags as appropriate, where TSiZe and TSFlag will have non-zero values if a text scrap exists. It doesn't actually load in a text scrap yet, save for its first datablock. Proposal: Changes may be necessary to support a new format or to support text albums. CheckDFVer - $3eb9x5 - Disk Routines Function: Checks a datafile's version against a v2.1 string identifier. Parameters: r5 having the direntry of the datafile. Uses: toggleZP GetFHdrInfo fileHeader Returns: N and Z flags are set on whether the datafile equals v2.1 Destroys: r5, r9, .A, .Y & .X Description: It simply gets the datafile's fileheader and compares its version string against the standard, v2.1, and sets flags as appropriate. Proposal: No change. ....... .... .. . C=H #18 :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Masters Class: xFLI Fixing ------------- by Russell Reed , Robin Harbon , S. Judd Last issue we covered the issues involved in NTSC/PAL fixing -- 63 cycles/line versus 65 cycles/line, total cycles per frame, etc. -- and fixed up some raster routines in a demo. One common raster routine that is usually quite easy to fix is FLI, and in this article we'll cover fixing FLI and IFLI routines. This article is more "interactive" than the previous article. The .zip file included in this issue contains an FLI picture (debris.panic), an IFLI picture (underwater-pal and underwater-ntsc), and some FLI display code. The FLI display code was downloaded from funet; "Underwater World", by Katon/Lepsi De/Arise, is an entry from the SymMek99 graphics competition; I'm not quite sure which competition the Debris picture is from. Try loading and running one of the pictures, and observe what happens. That's typical of a lot, though not all, FLI-type routines. Background ---------- FLI is discussed in detail in C=Hacking issue #4, among other places. Briefly, FLI is a software graphics mode that gives greater flexibiliby in color selection than standard multicolor mode. FLI works by changing VIC registers to load new color information on every scan line. It uses exactly 23 cycles/line in PAL mode, and 25 cycles/line on an NTSC machine. IFLI is similar, but "interlaces" two FLI pictures, to gain even more apparent resolution and colors, at the cost of a flickering display. Full screen FLIs are pretty standard. They have color data from $4000 to about $4FE8, graphics data from $6000 to $7F40, and more color data at either $3C00-$3FE8 or $8000-$83E8. IFLIs are not nearly so standard; you'll generally have to examine a routine to see where the graphics data and the $D800 color nybbles are stored. IFLIs can add a further wrinkle over FLI by updating the $D800 color RAM on every frame, in the vertical borders, which may require more cycles than an NTSC machine has to spare. An FLI display routine is really quite straightforward, and looks something like initial delay loop: LDA xxxx STA $D011 LDA xxxx STA $D018 INX CPX #xx BNE :loop in PAL (this article will focus on fixing PAL code, but going the other way is of course straightforward). The STA $D011 forces a badline; the STA $D018 selects a new line of color data. Sometimes this loop will be unrolled, and sometimes the initial delay can be very strange. It is this code that needs to be either modified or replaced. Displaying a picture -------------------- The easiest way to fix a picture is to simply replace the display code. The FLI picutre "Debris" has the color data at $3C00, which is more common. Now, since FLI format is pretty standard, if you have some NTSC FLI code, it'll work on this pic. Escape the picture by either pressing the stop/restore combination, or a reset button if you have one. In the zip you'll also find "FLIview NTSC". Load and run this now, and you'll see the picture as it's supposed to look (almost -- this code isn't quite perfect; we'll get it better when we fix the code with the picture). The next step is to save the picture in a way that can be distributed and make you famous as an 3li+3 c0de f1x3r. While the picture is still in memory, fire up a machine language monitor (most any will do, the FLI data is an an area rarely used by monitors). The easiest thing to do is to save memory from $0801 to $7F40, and compress it using ABCrunch, pucrunch, etc. -- although there are a lot of unused bytes between the display code at $0810 and the start of FLI data at $3B00, these get compressed down to just a few bytes. Another option is to use a linker. Save memory from $3B00 to $7F40 and name it something that reminds you it's an FLI pic -- you can load this file with most any of the FLI viewers out there, and you can load it into most of the FLI editors. Copy both "FLIview" and the saved-off FLI data to a work disk. Then boot up Sledgehammer, available at most FTP sites. Sledgehammer is a compacting linker (it actually uses run-length encoding, a.k.a. char-packing). It takes a number of program and data files and "links" them together into a single file that you can load and run. Once Sledgehammer is loaded, run it, put in your work disk, and press F1 to start. You'll then see a list of the files on the disk. Use the cursor keys to move and the return key to select "FLIview" and the FLI data. Then press 'C'. Sledgehammer then asks if you need to change load addresses. You can ignore this for now, so push 'N'. Next we see a screen asking for three pieces of information. For the name, "DEBRIS.SRF" (for self-running FLI) works. If you listed "FLIview" you saw that it did an SYS 2064 to start. 2064 is equal to $0810 in hex, so put 0810 for start, and then 37 for the $01 value. From there Slegehammer takes over, loading the files and compacting. Press space when it prompts to save. Once it's done, it resets. You can now load and run "DEBRIS.SRF", and you should see the picture. If not, read back through the article and check your steps carefully. Okay, we've successfully made this picture work on NTSC computers, but this technique won't work in most cases (i.e. stuff besides standalone pictures). Next we're going to go back to the picture and see how to change the PAL code so it works on our computers. Fixing the PAL code ------------------- The first thing we need to do before we fix the code is find out where it is in memory and how to start it. Also, most demos, self-running pictures, etc. are compressed, and we can't work with them until they're uncompressed. How you approach this depends on whether or not you have a freezer cartridge. If you don't own a freezer, then fire up a monitor that sits fairly high in memory, e.g. $C000; we're going to look at the decompression code to see how to start the FLI code up. If you list the program, you'll see it does a SYS 2061, which is hex $080D. Start disassembling at $080D. You'll notice it sets border and background to black (stores color code 0 in $D020 and $D021), then calls $E544 which clears the screen. Next we have a loop to transfer a message to the top screen line - $0400. Then another loop to transfer the decompression code from $0858 to $04FF. Finally, a third loop to transfer the compressed program to high memory and a jump to $050B. This is pretty typical of most decompression code; some may skip the text/message line and/or transferring the program to high memory. I usually replace the clear screen call with three 'NOP' instructions. You may also want to replace the store to $0286, which changes the working character color to black. Now into the decompression code we go. Start disassembling at $0858. There will be a few garbage instructions at the start. Scroll down a page or so until you see the sequence: LDA #$37 STA $01 CLI JMP $1000 This is where the decompression routine transfers control to the program. The instructions may vary a little on other files, but will usually be pretty similar. From this, we can tell that the program will start at $1000. We can also regain control here after decompression. Replace JMP $1000 with JMP $A7AE. Sometimes an RTS will work, and if you have a cartridge based monitor you can use a BRK. Now return to BASIC and type "PRINT 3". Some monitors disturb some floating point work areas; that line will clear this up. It may print something other than 3, but if you try it again you'll notice it's back to normal. Now run the program. After the usual depack effects, you'll notice you have a READY prompt. Reload the monitor, as the decompression probably overwrote it. Note: sometimes you won't be able to get back to BASIC, but since you know the start address, you can load the program and once it's running, use a reset button, then load your monitor. If you have a freezer, then you can simply load and run the picture, and freeze it after it finishes decompressing. Enter your monitor again and disassemble at $1000 -- let's study this code to see what's involved in displaying an FLI picture. The first thing we want to look at is down at $1035. The interrupt vector at $0314 is changed so that interrupts execute the code at $1043; sometimes the $FFFE vector is used instead. Now look at $1043; the part we're interested in is at $1060. Here is the main FLI display loop, as described above, that changes the vertical scroll register ($D011) and the screen display pointer ($D018), the two registers involved in FLI. If you add the cycles the instructions use, the loop takes 23 cycles. We need to extend it to 25. The easiest way here is to add a NOP at $1071. Change that part to be: INX CPX #... NOP BNE $1062 RTS Now jump to $1000 to re-run the program. You'll notice you can see the picture, but it doesn't quite look right; the problem is in the initial delay, before the main display loop: LDY #xx DEY BNE *-2 Go back in and change the value at $105C until the picture looks right. You can hit stop/restore to exit the picture, then restart your monitor. Changing a value like that is a dirty way to fix the picture, but is also usually the easiest. (By the way, the value at $105C which works is $0B). Now save your code off -- it starts at $1000 and ends at $12E0; there's tables at $1100 and $1200 as you'll notice in the FLI loop -- and crunch it down. That's all there is to it. Now it's your turn, to fix "Underwater". This is an IFLI picture, but the process is not much different than outlined above. You need to locate the interrupt routine, and fix up the main display loop and the initial delay; unlike some IFLIs this one is pretty easy to fix, and you can always peek at the fixed code if you really need a hint. Things that can go wrong ------------------------ Load up "debris" again. This time, instead of adding a NOP to the end of the loop, add it to the _beginning_ of the loop (move the loop down one byte and add a NOP), and change the BNE to branch to this NOP. Now fix up the initial delay, and -- i