magazines:chacking18
no way to compare when less than two revisions
Differences
This shows you the differences between two versions of the page.
— | magazines:chacking18 [2015-04-17 04:34] (current) – created - external edit 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 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, ' | ||
+ | positively discouraged. | ||
+ | lost in the sand; and novelty is better than repetition. | ||
+ | cannot be inherited, and if you want it you must obtain it by great | ||
+ | labor." | ||
+ | T. S. Eliot, " | ||
+ | |||
+ | ............................................................................... | ||
+ | |||
+ | BSOUT | ||
+ | |||
+ | Tradition. | ||
+ | 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. | ||
+ | borne delectable fruits for those seated at the C=Hacking table. | ||
+ | again, in keeping with one of C=Hacking' | ||
+ | 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. | ||
+ | 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:// | ||
+ | C=Hacking Homepage is now located at | ||
+ | |||
+ | http:// | ||
+ | |||
+ | And in the "well, it's news to me" department: | ||
+ | |||
+ | - SuperCPU acitivty 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. | ||
+ | being worked on. And a *third* project, Virtualass816, | ||
+ | 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:// | ||
+ | developments. | ||
+ | |||
+ | - There will be an English version of GO64! magazine, starting with the | ||
+ | August issue, available by subscription. | ||
+ | http:// | ||
+ | |||
+ | - 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:// | ||
+ | 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/ | ||
+ | http:// | ||
+ | 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:// | ||
+ | |||
+ | - Craig Bruce has been placing old issues of the Transactor online at | ||
+ | |||
+ | http:// | ||
+ | |||
+ | Well worth a visit. | ||
+ | |||
+ | - Another unzip program has appeared, by Pasi Ojala. | ||
+ | |||
+ | http:// | ||
+ | |||
+ | for more details. | ||
+ | |||
+ | - Richard Atkinson < | ||
+ | of the Commodore Sound Expander, a Yamaha-chip-based cartridge. | ||
+ | For more info, email Richard! | ||
+ | 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...). | ||
+ | help but reflect that the Commodore 8-bits also represent a kind of | ||
+ | independence. | ||
+ | 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 | ||
+ | |||
+ | ::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | 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. | ||
+ | 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 " | ||
+ | < | ||
+ | |||
+ | This installment covers interrupts -- IRQ and NMI sources, | ||
+ | the kernal handler code. | ||
+ | |||
+ | o "A Diehard Programmer' | ||
+ | | ||
+ | |||
+ | As part of his effort to learn about GEOS programming, | ||
+ | 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: " | ||
+ | < | ||
+ | |||
+ | 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 is a set of routines for creating, manipulating, | ||
+ | 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. | ||
+ | describes the library, and walks through a simple example program. | ||
+ | |||
+ | The second article is the " | ||
+ | maps and a list of all the routines. | ||
+ | |||
+ | The third article discusses " | ||
+ | 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", | ||
+ | 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' | ||
+ | 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' | ||
+ | |||
+ | About the authors: | ||
+ | |||
+ | Todd Elliot is 30 years old, working as a Community Client Advisor | ||
+ | for the Center On Deafness - Inland Empire, working with deaf people in | ||
+ | the local communities. | ||
+ | for games, BBS activities, and dabbling in programming. | ||
+ | focuses on ML programming, | ||
+ | for the SuperCPU. | ||
+ | particular, and enjoys writing articles and watching movies. | ||
+ | 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' | ||
+ | |||
+ | Richard Cini is a 31 year old vice president of Congress Financial | ||
+ | Corporation, | ||
+ | his parents bought him a VIC-20 as a birthday present. | ||
+ | for general BASIC programming, | ||
+ | controlling the lawn sprinkler system, and for a text-to-speech synthesyzer. | ||
+ | All his CBM stuff is packed up right now, along with his other " | ||
+ | computers, including a PDP11/34 and a KIM-1. | ||
+ | old computers Richard enjoys gardening, golf, and recently has gotten | ||
+ | interested in robotics. | ||
+ | is unique in being fiercely loyal without being evangelical, | ||
+ | some other communities, | ||
+ | best use out of the 64. | ||
+ | |||
+ | Pasi ' | ||
+ | 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. | ||
+ | to pucrunch and his many C=Hacking articles, Pasi's most recent project | ||
+ | is an " | ||
+ | has a B5 quote page at http:// | ||
+ | |||
+ | Robin Harbron is a 26 year old internet tech support at a local | ||
+ | independent phone company. | ||
+ | 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. | ||
+ | dabbles with Internet stuff, writes C= magazine articles, and, yes, | ||
+ | plays games. | ||
+ | as well as the " | ||
+ | until-inspiration-hits-again Internet stuff" | ||
+ | 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. | ||
+ | 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? | ||
+ | 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 " | ||
+ | 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' | ||
+ | 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). | ||
+ | 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. | ||
+ | abstract concept of an " | ||
+ | |||
+ | Another type of useful data structure is a linked list. Imagine | ||
+ | attaching a pointer to some chunk of data (usually called a " | ||
+ | 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 " | ||
+ | can reach every other node by following the pointers. | ||
+ | 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. | ||
+ | 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). | ||
+ | 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 " | ||
+ | stored sequentially in memory, a program stored on disk might be strewn all | ||
+ | over the place. | ||
+ | together data that is spread all over memory. | ||
+ | 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, | ||
+ | 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 " | ||
+ | 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. | ||
+ | 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. | ||
+ | in this issue, needs to depth-sort objects, so that far-away objects | ||
+ | don't overlap nearby objects when drawn on the screen. | ||
+ | sorting a list of 16-bit numbers. | ||
+ | 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 " | ||
+ | |||
+ | Your first thought might be " | ||
+ | overhead in copying data and changing pointers? | ||
+ | lame PC programmers; | ||
+ | are handsome, witty, superior, humble -- and most of all, sneaky. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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; | ||
+ | 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. | ||
+ | at the head of the list (at list+$80), traverses the list to find the | ||
+ | right spot, and inserts the " | ||
+ | |||
+ | : | ||
+ | |||
+ | LDY #$80 ;Head of list | ||
+ | :l1 LDA VISOBJS, | ||
+ | BMI :link | ||
+ | STY TEMPY | ||
+ | | ||
+ | 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, | ||
+ | TXA | ||
+ | STA VISOBJS, | ||
+ | DEX | ||
+ | BPL :loop | ||
+ | :rts RTS | ||
+ | |||
+ | Here, VISOBJS is the linked list of sorted coordinates. | ||
+ | |||
+ | Personally, | ||
+ | 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 " | ||
+ | shortly. | ||
+ | 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. | ||
+ | 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. | ||
+ | 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; | ||
+ | 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. | ||
+ | end the counters contain the number of each code length found in the data. | ||
+ | |||
+ | for(i=0; | ||
+ | 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; | ||
+ | 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. | ||
+ | from 1 for the reader' | ||
+ | |||
+ | for(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 | ||
+ | |||
+ | 3 1 1 4 5 6 7 x x x 3 x x x 6 | ||
+ | G | ||
+ | |||
+ | 6 1 1 3 5 6 7 x x x 3 x x 6 6 | ||
+ | F | ||
+ | |||
+ | 3 1 1 3 5 6 6 x x 3 3 x x 6 6 | ||
+ | E | ||
+ | |||
+ | 5 1 1 2 5 6 6 x x 3 3 x 5 6 6 | ||
+ | D | ||
+ | |||
+ | 1 1 1 2 5 6 6 1 x 3 3 x 5 6 6 | ||
+ | C | ||
+ | |||
+ | 3 0 1 2 5 6 6 1 3 3 3 x 5 6 6 | ||
+ | B | ||
+ | |||
+ | 4 0 1 1 5 6 6 1 3 3 3 4 5 6 6 | ||
+ | A | ||
+ | |||
+ | |||
+ | So, after the loop we have sorted the symbols according to the code | ||
+ | lengths. | ||
+ | is an O(n) process. | ||
+ | if the amount of data increases linearly, the sorting time also increases | ||
+ | linearly. | ||
+ | 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. | ||
+ | preserved in the end result. | ||
+ | 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 " | ||
+ | handler" | ||
+ | |||
+ | |||
+ | |||
+ | FFFA ; | ||
+ | FFFA ; - Power-on Vectors | ||
+ | FFFA ; | ||
+ | FFFA A9 FE .dw NMI ; | ||
+ | FFFC 22 FD .dw RESET ; | ||
+ | FFFE 72 FF .dw IRQ ; | ||
+ | |||
+ | 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/ | ||
+ | |||
+ | 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/ | ||
+ | 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' | ||
+ | |||
+ | $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, " | ||
+ | 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/ | ||
+ | BIT3 CB2 transition R/ | ||
+ | BIT2 Completion of 8 shifts R/W shift register | ||
+ | BIT1 CA1 transition R/ | ||
+ | BIT0 CA2 transition R/ | ||
+ | |||
+ | 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, " | ||
+ | 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 | ||
+ | " | ||
+ | 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) ; | ||
+ | |||
+ | 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 ; | ||
+ | FEAD | ||
+ | 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 ; | ||
+ | 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 # | ||
+ | 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 ; | ||
+ | ; issue) | ||
+ | FEC2 D0 03 BNE LNKNMI1 ;no ROM at $A0, so skip ROM NMI | ||
+ | ; routine | ||
+ | |||
+ | FEC4 6C 02 A0 JMP (A0BASE+2) ; | ||
+ | |||
+ | FEC7 | ||
+ | FEC7 2C 11 91 BIT D1ORA ;test ATN IN(7)/cass switch (6) | ||
+ | ; bits | ||
+ | FECA 20 34 F7 JSR IUDTIM ; | ||
+ | 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' | ||
+ | normal initialization routines, leaving program RAM undisturbed. | ||
+ | |||
+ | FED2 ; | ||
+ | FED2 ; WARMST - Default USER vector | ||
+ | FED2 ; | ||
+ | FED2 ; vector) or RUN/ | ||
+ | FED2 | ||
+ | FED2 20 52 FD JSR IRESTR ; | ||
+ | ; last issue) | ||
+ | FED5 20 F9 FD JSR IOINIT ; | ||
+ | ; issue) | ||
+ | FED8 20 18 E5 JSR CINT1 ; | ||
+ | ; last issue) | ||
+ | FEDB 6C 02 C0 JMP (BENTER+2) ; | ||
+ | |||
+ | 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/ | ||
+ | |||
+ | 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 | ||
+ | FEDE AD 1E 91 LDA D1IER ;get IER bitmap | ||
+ | FEE1 09 80 ORA # | ||
+ | ; according to existing bitmap | ||
+ | FEE3 48 PHA ;save " | ||
+ | FEE4 A9 7F LDA # | ||
+ | ; VIA1 | ||
+ | FEE6 8D 1E 91 STA D1IER ;go do it | ||
+ | |||
+ | FEE9 8A TXA ; | ||
+ | FEEA 29 40 AND # | ||
+ | ; clk) | ||
+ | FEEC F0 14 BEQ WARM2 ;T1 done, go to RS232/RX chr | ||
+ | |||
+ | FEEE A9 CE LDA # | ||
+ | FEF0 05 B5 ORA NXTBIT ; | ||
+ | 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 ; | ||
+ | FEF9 8D 1E 91 STA D1IER ; ...and save it | ||
+ | FEFC 20 A3 EF JSR SSEND ;send RS232 char | ||
+ | FEFF | ||
+ | FEFF | ||
+ | FEFF 4C 56 FF JMP EOI ;end of interrupt | ||
+ | |||
+ | FF02 | ||
+ | FF02 8A TXA ; | ||
+ | FF03 29 20 AND # | ||
+ | ; character from RS232 channel)? | ||
+ | FF05 F0 25 BEQ WARM3 ;yes, so move byte to buffer | ||
+ | |||
+ | ; | ||
+ | FF07 AD 10 91 LDA D1ORB ;get user port bitmap | ||
+ | FF0A 29 01 AND # | ||
+ | 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 ; | ||
+ | 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 ; | ||
+ | FF23 8D 1E 91 STA D1IER ; ...and save it | ||
+ | FF26 20 36 F0 JSR SERRX ; | ||
+ | FF29 4C 56 FF JMP EOI ;end of interrupt | ||
+ | |||
+ | FF2C | ||
+ | FF2C 8A TXA | ||
+ | FF2D 29 10 AND # | ||
+ | ; 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' | ||
+ | Guide makes mention of the 6551 registers, but the VIC clearly does not | ||
+ | contain a 6551. See Cameron Kaiser' | ||
+ | details. The URL is http:// | ||
+ | |||
+ | 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 ; | ||
+ | FF34 29 0F AND # | ||
+ | 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, | ||
+ | FF3D 8D 18 91 STA D1TM2L ; and save the divisor into the VIA | ||
+ | |||
+ | FF40 BD 5B FF LDA R232TB-1, | ||
+ | FF43 8D 19 91 STA D1TM2L+1 | ||
+ | |||
+ | FF46 AD 10 91 LDA D1ORB ;read RS232 output register | ||
+ | FF49 68 PLA ; | ||
+ | FF4A 09 20 ORA # | ||
+ | FF4C 29 EF AND # | ||
+ | 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 | ||
+ | FF56 68 PLA ; | ||
+ | FF57 A8 TAY | ||
+ | FF58 68 PLA | ||
+ | FF59 AA TAX | ||
+ | FF5A 68 PLA | ||
+ | FF5B 40 RTI ; | ||
+ | |||
+ | |||
+ | 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 " | ||
+ | processing. | ||
+ | |||
+ | FF72 ; | ||
+ | FF72 ; IRQ - IRQ transfer point | ||
+ | FF72 ; | ||
+ | FF72 | ||
+ | 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, | ||
+ | FF7B 29 10 AND # | ||
+ | FF7D F0 03 BEQ BRKSKIP ; | ||
+ | FF7F | ||
+ | FF7F 6C 16 03 JMP (BRKVP) ; | ||
+ | ; which is the WARMST location. | ||
+ | FF82 | ||
+ | FF82 6C 14 03 JMP (IRQVP) ; | ||
+ | ; $EABF | ||
+ | |||
+ | When the code is finished determining if the interrupt was | ||
+ | triggered by a timer tick interrupt or through a BRK instruction, | ||
+ | code continues at $EABF. If it is a BRK instruction, | ||
+ | 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 " | ||
+ | interface" | ||
+ | |||
+ | EABF ; | ||
+ | EABF ; IRQVEC - IRQ Vector | ||
+ | EABF ; | ||
+ | EABF | ||
+ | EABF 20 EA FF JSR UDTIM ; | ||
+ | EAC2 A5 CC LDA BLNSW ; | ||
+ | EAC4 D0 29 BNE IRQVEC2 ; | ||
+ | EAC6 | ||
+ | EAC6 C6 CD DEC BLNCT ; | ||
+ | EAC8 D0 25 BNE IRQVEC2 ; | ||
+ | ; 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), | ||
+ | EAD7 B0 11 BCS IRQVEC1 ; | ||
+ | EAD9 | ||
+ | EAD9 E6 CF INC BLNON ; | ||
+ | EADB 85 CE STA GDBLN ;save char under cursor | ||
+ | EADD 20 B2 EA JSR CCOLRAM ; | ||
+ | EAE0 B1 F3 LDA (COLRPT), | ||
+ | EAE2 8D 87 02 STA CSRCLR ; | ||
+ | EAE5 AE 86 02 LDX CLCODE ;get color under cursor | ||
+ | EAE8 A5 CE LDA GDBLN ;get char again | ||
+ | EAEA | ||
+ | EAEA | ||
+ | EAEA 49 80 EOR # | ||
+ | EAEC 20 AA EA JSR PRNSCR1 ; | ||
+ | EAEF | ||
+ | EAEF | ||
+ | EAEF AD 1F 91 LDA D1ORAH ; | ||
+ | EAF2 29 40 AND # | ||
+ | EAF4 F0 0B BEQ IRQVEC3 ; | ||
+ | EAF6 | ||
+ | EAF6 A0 00 LDY #$00 ;set motor " | ||
+ | EAF8 84 C0 STY CAS1 ;clear motor interlock flag | ||
+ | EAFA AD 1C 91 LDA D1PCR ;get PCR bitmap | ||
+ | EAFD 09 02 ORA # | ||
+ | EAFF D0 09 BNE IRQVEC4 ;go to timer1 test | ||
+ | EB01 | ||
+ | EB01 | ||
+ | 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 # | ||
+ | EB0A | ||
+ | EB0A | ||
+ | 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 | ||
+ | EB12 20 1E EB JSR ISCNKY ; | ||
+ | EB15 2C 24 91 BIT D2TM1L ; | ||
+ | |||
+ | EB18 68 PLA ; | ||
+ | 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' | ||
+ | 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? | ||
+ | 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, ' | ||
+ | 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, | ||
+ | docs had inaccuracies and incompleteness. Fourth, GEOS 128, while a marvel | ||
+ | and a technological breakthrough, | ||
+ | serious application such as a mono graphical browser. | ||
+ | |||
+ | My main obstacle, as one would strangely call it, to GEOS programming is the | ||
+ | Berkeley Softwork' | ||
+ | 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, | ||
+ | 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' | ||
+ | environment, | ||
+ | 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, | ||
+ | |||
+ | http:// | ||
+ | |||
+ | There are other development packages available for GEOS programming, | ||
+ | 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' | ||
+ | the MegaAssembler, | ||
+ | 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, | ||
+ | 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' | ||
+ | lot of useful information and when taken together, is truly the ' | ||
+ | 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 ' | ||
+ | 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' | ||
+ | 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 ' | ||
+ | sensible as two drive systems were possible. But today' | ||
+ | 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, | ||
+ | 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' | ||
+ | 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 ' | ||
+ | or ' | ||
+ | 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. | ||
+ | records, or if in program files, ' | ||
+ | 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. | ||
+ | and contains the main ' | ||
+ | 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. | ||
+ | |||
+ | 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, | ||
+ | and it will produce some GEOS compliant source code. I haven' | ||
+ | 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, | ||
+ | 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' | ||
+ | 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' | ||
+ | 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' | ||
+ | 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' | ||
+ | there are 7 more modules to go. :( Hopefully I haven' | ||
+ | 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' | ||
+ | Guide and the Hitchhiker' | ||
+ | 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 ' | ||
+ | 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: | ||
+ | list is: http:// | ||
+ | |||
+ | That all said, there are plenty of GEOS programs, especially those BSW | ||
+ | applications, | ||
+ | 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: | ||
+ | from. Sets the disk device for which the datafile activities take place. | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | dataDrive | ||
+ | toggleZP | ||
+ | SetDevice | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | dataDrive and issues a SetDevice call to change the active disk device. | ||
+ | |||
+ | Proposal: | ||
+ | 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, | ||
+ | needs to keep track of all of this activity and the current routines fall | ||
+ | short. | ||
+ | |||
+ | VLIROps - $0dddx0 - Disk Routines | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | load. | ||
+ | |||
+ | Uses: | ||
+ | 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: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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' | ||
+ | |||
+ | 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: | ||
+ | modules from expansion ram in Wheels or MP3 operating systems. Already | ||
+ | implemented in the patcher program for Wheels users. | ||
+ | |||
+ | StashGW128 - $2bdbx0 - Setup | ||
+ | |||
+ | Function: | ||
+ | expansion memory, and enables the ram-based VLIR code. | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | 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: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | GEOS 128 or Wheels 128 system. If it is GEOS 128, then the routine simply | ||
+ | jumps to $3244 of geoWrite' | ||
+ | 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' | ||
+ | |||
+ | 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' | ||
+ | |||
+ | Proposal: | ||
+ | of MP3 128 systems. | ||
+ | |||
+ | VLIRAMOps - $0dddx0 - RAM Routines | ||
+ | |||
+ | Function: | ||
+ | expansion RAM. | ||
+ | |||
+ | Parameters: | ||
+ | load. | ||
+ | |||
+ | Uses: | ||
+ | RAMOps | ||
+ | CVLIRNum - Current geoWrite 128 VLIR module in memory | ||
+ | toggleZP | ||
+ | DoRAMOp | ||
+ | lb29dc - Error message | ||
+ | lb233a - Canned DB to display error message | ||
+ | EnterDeskTop | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | in geoWrite 128 v2.1. This routine may need to be changed to support the MP3 | ||
+ | 128 platform. | ||
+ | |||
+ | checkDrives - $2b0dx0 - Disk Routines | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | r2 via deskTop pointing to diskname of the disk containing | ||
+ | the datafile. | ||
+ | r3 via deskTop pointing to datafile' | ||
+ | |||
+ | Uses: | ||
+ | DrBCurDkNm | ||
+ | DrCCurDkNm | ||
+ | DrDCurDkNm | ||
+ | setDataDevice | ||
+ | setProgDevice | ||
+ | $27b0 - FindFile | ||
+ | curDrive | ||
+ | numDrives | ||
+ | toggleZP | ||
+ | prepDlgBox | ||
+ | enterDeskTop | ||
+ | |||
+ | Returns: | ||
+ | dataDrive - The disk device that houses the datafile. | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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 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: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | c128Flag | ||
+ | DoDlgBox | ||
+ | enterDesktop | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | 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: | ||
+ | zp spaces, one for the actual application usage and the other for stock | ||
+ | GEOS/Kernal usage. | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | 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: | ||
+ | |||
+ | Parameters: | ||
+ | box text. | ||
+ | |||
+ | Uses: | ||
+ | $2538 - Does something to the VDC | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | |||
+ | layoutScreen - $32cex1 - Appearance | ||
+ | |||
+ | Function: | ||
+ | geoWrite 128. | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | i_GraphicsString | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | GraphicsString parameters. Additionally, | ||
+ | top, with its table located at $0bbcx0. | ||
+ | |||
+ | Proposal: | ||
+ | added or old ones deleted and maybe the screen layout should be changed or | ||
+ | left as is. | ||
+ | |||
+ | InitGW128 - $3c70x1 - Startup | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | closeRecordFile | ||
+ | dispBufferOn | ||
+ | RecoverVector | ||
+ | checkSerNbr | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | $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: | ||
+ | set. | ||
+ | |||
+ | CheckPtrDrv - $33e7x1 - Startup | ||
+ | |||
+ | Function: | ||
+ | necessary. | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | getDimensions | ||
+ | setProgDevice | ||
+ | maxPtrHgt ($2dfb) | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | always loaded into memory in GEOS 128 configuration? | ||
+ | |||
+ | InitGW128 - $325cx5 - Dialog Boxes | ||
+ | |||
+ | Function: | ||
+ | Open or Quit DB. | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: $1baf - Turns off text cursor. | ||
+ | DrawStripes | ||
+ | i_GraphicsString | ||
+ | printCopyrightMsg | ||
+ | $2538 - issues a dialog box | ||
+ | createOpenQuitDB - DB table | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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/ | ||
+ | creation of datafiles, opening of datafiles or quitting the application. | ||
+ | |||
+ | Proposal: | ||
+ | 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: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | $1f05 - calls system font | ||
+ | currentMode | ||
+ | i_GraphicsString | ||
+ | i_PutString | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | their own message. | ||
+ | |||
+ | createDocRoutine - $33ddx5 - Disk Routines | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | 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: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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' | ||
+ | right corner of the screen. | ||
+ | |||
+ | Proposal: | ||
+ | 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: | ||
+ | the DISK icon. | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | setDiskIcon | ||
+ | checkDisk | ||
+ | setDataDevice | ||
+ | readDiskName | ||
+ | iconLTable | ||
+ | iconHTable | ||
+ | driveType | ||
+ | curDrive | ||
+ | onLTable | ||
+ | onHTable | ||
+ | offLTable | ||
+ | offHTable | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | |||
+ | fileRequestor - $34b2x5 - Dialog Boxes | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | 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: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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' | ||
+ | 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: | ||
+ | |||
+ | openServiceRoutine - $364dx5 - Disk Routines | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | 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' | ||
+ | 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: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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' | ||
+ | stores in a max width of 639 into PageWidth. Next, it checks the datafile' | ||
+ | version and if necessary, converts it to a v2.1 format. Next, it gets global | ||
+ | variables stored in the datafile' | ||
+ | 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, | ||
+ | sprite and prints the datafile' | ||
+ | screen. | ||
+ | |||
+ | Proposal: | ||
+ | in addition to regular geoWrite v2.1 datafiles. | ||
+ | |||
+ | checkDisk - $371bx5 - Disk Routines | ||
+ | |||
+ | Function: | ||
+ | application and sets the DISK icon accordingly. | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: a7L | ||
+ | dataDrive | ||
+ | progDrive | ||
+ | driveType | ||
+ | cableType | ||
+ | |||
+ | Returns: | ||
+ | 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: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | 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: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | DrBCurDkNm | ||
+ | DrCCurDkNm | ||
+ | DrDCurDkNm | ||
+ | diskName | ||
+ | dataDrive | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | |||
+ | recoverFile - $3781x5 - Disk Routines | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | $dd - some kind of unidentified flag | ||
+ | i_MoveData | ||
+ | CNameBuffer | ||
+ | NameBuffer | ||
+ | openServiceRoutine | ||
+ | lb4005 - text pointer (Cannot recover) | ||
+ | $2314 - Issues a DB | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | |||
+ | RenamFile - $379bx5 - Disk Routines | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | 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: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | |||
+ | queryFilename - $37dbx5 - Dialog Boxes | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | icons and the system DISK icon from the DB. | ||
+ | |||
+ | Uses: | ||
+ | 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: | ||
+ | |||
+ | Description: | ||
+ | before calling setup4DB to prep the DB with appropriate icons. Next, it | ||
+ | modifies the DB table to replace the DBGETFILES with DBGETSTRING, | ||
+ | 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: | ||
+ | |||
+ | createNewFile - $3839x5 - Disk Routines | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | NameBuffer | ||
+ | lb38c4 - (word) Page Height stored in a datafile' | ||
+ | dFileHeader | ||
+ | toggleZP | ||
+ | SaveFile | ||
+ | $27b0 - FindFile | ||
+ | CheckDFVer | ||
+ | dirEntryBuf | ||
+ | TSDFHdr | ||
+ | $260c - r0 points to filename | ||
+ | OpenRecordFile | ||
+ | $27a7 - AppendRecord | ||
+ | $27aa - UpdateRecord | ||
+ | PointRecord | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | variables as hidden in the datafile' | ||
+ | two bytes of the datafile' | ||
+ | SaveFile. Next, it calls FindFile to load in the newly created datafile' | ||
+ | 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: | ||
+ | other file formats. | ||
+ | |||
+ | convertDataFile - $393ex5 - Data Handling | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | 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: | ||
+ | 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: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | other file formats. | ||
+ | |||
+ | lb3978 - $39f0x5 - Data Handling | ||
+ | |||
+ | Function: | ||
+ | format. | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | 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: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | other file formats. | ||
+ | |||
+ | lb39da - $3a52x5 - Data Handling | ||
+ | |||
+ | Function: | ||
+ | to a v2.1 format. | ||
+ | |||
+ | Parameters: | ||
+ | ReadRecord. | ||
+ | |||
+ | Uses: | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | on a VLIR record back by seven bytes and zeroes out some parts of the ruler | ||
+ | escape. | ||
+ | |||
+ | Proposal: | ||
+ | other file formats. | ||
+ | |||
+ | lb3a09 - $3a81x5 - Data Handling | ||
+ | |||
+ | Function: | ||
+ | to a v2.1 format. | ||
+ | |||
+ | Parameters: | ||
+ | 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: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | other file formats. | ||
+ | |||
+ | printDataName - $3af1x5 - Appearance | ||
+ | |||
+ | Function: | ||
+ | the screen. | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | CNameBuffer | ||
+ | i_MoveData | ||
+ | $1fba - use system font | ||
+ | currentMode | ||
+ | DrawStripes | ||
+ | PutChar | ||
+ | PutString | ||
+ | rightMargin | ||
+ | GetCharWidth | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | |||
+ | runDA - $3bb1x5 - Desk Accessories | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | $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: | ||
+ | |||
+ | Destroys: | ||
+ | run, pretty much everything else has been hosed, except that geoWrite 128 | ||
+ | tries to preserve as much as possible. | ||
+ | |||
+ | Description: | ||
+ | header/ | ||
+ | zero page and inverts a rectangle twice onscreen. It then closes the current | ||
+ | VLIR datafile, saves any Text Scraps, sprite data/ | ||
+ | first 24 scanlines of the screen. Finally, it calls and runs the DA. When the | ||
+ | DA is done, geoWrite then will restore sprite data/ | ||
+ | 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: | ||
+ | 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: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | 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: | ||
+ | saved, otherwise $ff if something' | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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/ | ||
+ | geoWrite 128 application resides. It will delete a prior text scrap if one | ||
+ | existed. | ||
+ | |||
+ | Proposal: | ||
+ | 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: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | setProgDevice | ||
+ | toggleZP | ||
+ | EnterDeskTop | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | the disk/ | ||
+ | space and then quit, resuming control to the deskTop. | ||
+ | |||
+ | Proposal: | ||
+ | |||
+ | SetScrollSprite - $3cbdx5 - User Interface | ||
+ | |||
+ | Function: | ||
+ | document onscreen, in the little box in the middle top of the screen. | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: $01 - i/o port | ||
+ | $d02d - Sprite 6 color register | ||
+ | i_FillRam | ||
+ | i_MoveData | ||
+ | lb3c73 - sprite data | ||
+ | $d01d - expand sprite 6 horizontally | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | horizontally, | ||
+ | |||
+ | Proposal: | ||
+ | mapped in already in a GEOS 128 configuration? | ||
+ | |||
+ | AdjPageWidths - $3cf6x5 - Data Handling | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | PointRecord | ||
+ | toggleZP | ||
+ | ReadRecord | ||
+ | $2c1d - Page Width High Byte | ||
+ | $2be0 - Page Width Low Byte | ||
+ | pageWidth | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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: | ||
+ | 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: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | lb3cdf - DB table pointer | ||
+ | infoBoxText - text pointer to copyright message, etc. | ||
+ | $2538 - Issues a DB | ||
+ | |||
+ | Returns: | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | etc. | ||
+ | |||
+ | Proposal: | ||
+ | |||
+ | loadTScrap - $3d5fx5 - Disk Routines | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | 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: | ||
+ | a Text Scrap. | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | 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, | ||
+ | non-zero values if a text scrap exists. It doesn' | ||
+ | scrap yet, save for its first datablock. | ||
+ | |||
+ | Proposal: | ||
+ | support text albums. | ||
+ | |||
+ | CheckDFVer - $3eb9x5 - Disk Routines | ||
+ | |||
+ | Function: | ||
+ | |||
+ | Parameters: | ||
+ | |||
+ | Uses: | ||
+ | GetFHdrInfo | ||
+ | fileHeader | ||
+ | |||
+ | Returns: N and Z flags are set on whether the datafile equals v2.1 | ||
+ | |||
+ | Destroys: | ||
+ | |||
+ | Description: | ||
+ | version string against the standard, v2.1, and sets flags as appropriate. | ||
+ | |||
+ | Proposal: | ||
+ | ....... | ||
+ | .... | ||
+ | .. | ||
+ | . C=H #18 | ||
+ | |||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | Masters Class: xFLI Fixing | ||
+ | ------------- | ||
+ | by Russell Reed < | ||
+ | Robin Harbron < | ||
+ | S. Judd < | ||
+ | |||
+ | Last issue we covered the issues involved in NTSC/PAL fixing -- 63 cycles/line | ||
+ | versus 65 cycles/ | ||
+ | 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 " | ||
+ | included in this issue contains an FLI picture (debris.panic), | ||
+ | picture (underwater-pal and underwater-ntsc), | ||
+ | The FLI display code was downloaded from funet; " | ||
+ | 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. | ||
+ | typical of a lot, though not all, FLI-type routines. | ||
+ | |||
+ | Background | ||
+ | ---------- | ||
+ | |||
+ | FLI is discussed in detail in C=Hacking issue #4, among other places. | ||
+ | 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. | ||
+ | similar, but " | ||
+ | resolution and colors, at the cost of a flickering display. | ||
+ | |||
+ | Full screen FLIs are pretty standard. | ||
+ | about $4FE8, graphics data from $6000 to $7F40, and more color data at either | ||
+ | $3C00-$3FE8 or $8000-$83E8. | ||
+ | generally have to examine a routine to see where the graphics data and | ||
+ | the $D800 color nybbles are stored. | ||
+ | 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, | ||
+ | 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). | ||
+ | selects a new line of color data. Sometimes this loop will be unrolled, and | ||
+ | sometimes the initial delay can be very strange. | ||
+ | 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 " | ||
+ | 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/ | ||
+ | you'll also find " | ||
+ | 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. | ||
+ | fire up a machine language monitor (most any will do, the FLI data is an an | ||
+ | area rarely used by monitors). | ||
+ | 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. | ||
+ | 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. | ||
+ | Then boot up Sledgehammer, | ||
+ | compacting linker (it actually uses run-length encoding, a.k.a. char-packing). | ||
+ | It takes a number of program and data files and " | ||
+ | single file that you can load and run. Once Sledgehammer is loaded, run it, | ||
+ | put in your work disk, and press F1 to start. | ||
+ | files on the disk. Use the cursor keys to move and the return key to select | ||
+ | " | ||
+ | need to change load addresses. | ||
+ | Next we see a screen asking for three pieces of information. | ||
+ | " | ||
+ | that it did an SYS 2064 to start. | ||
+ | for start, and then 37 for the $01 value. | ||
+ | loading the files and compacting. | ||
+ | it's done, it resets. | ||
+ | see the picture. | ||
+ | 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). | ||
+ | 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' | ||
+ | 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. | ||
+ | notice it sets border and background to black (stores color code 0 in $D020 | ||
+ | and $D021), then calls $E544 which clears the screen. | ||
+ | transfer a message to the top screen line - $0400. | ||
+ | transfer the decompression code from $0858 to $04FF. | ||
+ | transfer the compressed program to high memory and a jump to $050B. | ||
+ | pretty typical of most decompression code; some may skip the text/ | ||
+ | and/or transferring the program to high memory. | ||
+ | screen call with three ' | ||
+ | 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. | ||
+ | so until you see the sequence: | ||
+ | |||
+ | LDA #$37 | ||
+ | STA $01 | ||
+ | CLI | ||
+ | JMP $1000 | ||
+ | |||
+ | This is where the decompression routine transfers control to the program. | ||
+ | instructions may vary a little on other files, but will usually be pretty | ||
+ | similar. | ||
+ | also regain control here after decompression. | ||
+ | $A7AE. | ||
+ | you can use a BRK. Now return to BASIC and type "PRINT 3" | ||
+ | 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. | ||
+ | you have a READY prompt. | ||
+ | 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. | ||
+ | look at is down at $1035. | ||
+ | interrupts execute the code at $1043; sometimes the $FFFE vector is used | ||
+ | instead. | ||
+ | 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. | ||
+ | to add a NOP at $1071. | ||
+ | |||
+ | INX | ||
+ | CPX #... | ||
+ | NOP | ||
+ | BNE $1062 | ||
+ | RTS | ||
+ | |||
+ | Now jump to $1000 to re-run the program. | ||
+ | picture, but it doesn' | ||
+ | before the main display loop: | ||
+ | |||
+ | LDY #xx | ||
+ | DEY | ||
+ | BNE *-2 | ||
+ | |||
+ | Go back in and change the value at $105C until the picture looks right. | ||
+ | can hit stop/ | ||
+ | a value like that is a dirty way to fix the picture, but is also usually the | ||
+ | easiest. | ||
+ | |||
+ | Now save your code off -- it starts at $1000 and ends at $12E0; there' | ||
+ | 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 " | ||
+ | process is not much different than outlined above. | ||
+ | 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 " | ||
+ | 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 -- it doesn' | ||
+ | what value you use in the LDY #xx. This highlights the fact that FLI | ||
+ | involves somewhat delicate timing; the problem here is that DEY BNE *-2 | ||
+ | takes five cycles, which is too " | ||
+ | which means a more involved code rewrite -- and all because the two cycles | ||
+ | were added to the display loop at the beginning instead of the end. | ||
+ | |||
+ | IFLIs bring on problems of their own. Sometimes the display loop is | ||
+ | unrolled, and looks like | ||
+ | |||
+ | LDA #xx | ||
+ | STA $D011 | ||
+ | LDA #xx | ||
+ | STA $D018 | ||
+ | few cycles delay | ||
+ | LDA #xx | ||
+ | STA $D011 | ||
+ | ... | ||
+ | |||
+ | Sometimes the loop is only unrolled over eight iterations (to cover a full | ||
+ | character row); sometimes, however, it is unrolled over the entire picture, | ||
+ | taking up thousands of bytes. | ||
+ | the loop (LDA #xx STA $D021) too. Fortunately, | ||
+ | written using a code generator, as part of the initialization routine. | ||
+ | you can locate the generator, you only have to add two cycles in one place. | ||
+ | |||
+ | There are also different ways of generating the interrupt. | ||
+ | often used, the CIAs are also used -- if you set the CIA timer to $42C6 | ||
+ | (NTSC -- PAL is $4CC7) it will count exactly one frame. | ||
+ | takes a variable number of cycles, since the CPU has to finish executing | ||
+ | the current instruction before the interrupt sequence takes place. | ||
+ | reading the low byte of the timer in the interrupt routine, the code can | ||
+ | deduce exactly how many cycles took place since the interrupt, and hence | ||
+ | exactly where it is on the raster line. Fixing this type of routine | ||
+ | involves setting the timers correctly, as well as fixing up the display | ||
+ | loop and initial delay. | ||
+ | |||
+ | Conclusion | ||
+ | ---------- | ||
+ | |||
+ | Nevertheless, | ||
+ | about any routine can be fixed up, and as usual becomes easier with practice. | ||
+ | As an application of the techniques outlined in this article, Robin and Steve | ||
+ | fixed up the graphics entries from the 1999 Mekka Symposium; you can find | ||
+ | them in the Fridge. | ||
+ | So good luck in your fixing endeavors, and see you next time! | ||
+ | |||
+ | :::::::: | ||
+ | |||
+ | begin 644 flifix.zip | ||
+ | M4$L# | ||
+ | M+G!R9U58# | ||
+ | M@/ | ||
+ | M> | ||
+ | MU?/ | ||
+ | MBC4.Z4P: | ||
+ | M3> | ||
+ | M*# | ||
+ | M!> | ||
+ | MG`]D`%71/ | ||
+ | M@^*%Y`-42L3A& | ||
+ | MN(BO; | ||
+ | M%^#/< | ||
+ | M8CE" | ||
+ | MO3SP\M# | ||
+ | M+2%[0@*^; | ||
+ | M@-?' | ||
+ | M4^7[ZU)[*4Q!_JUQ)7? | ||
+ | M$`!D96)R: | ||
+ | MW71MDFV%619.)`MI]M: | ||
+ | M< | ||
+ | M^S_GDGIQ)!?# | ||
+ | M2Y]=O_2W=M_7" | ||
+ | M,; | ||
+ | MB: | ||
+ | M%VS? | ||
+ | MDR\? | ||
+ | MVK[WJ^N3O_: | ||
+ | M< | ||
+ | M7^K^UFMO_' | ||
+ | M& | ||
+ | M*G, | ||
+ | M& | ||
+ | M> | ||
+ | MT7(: | ||
+ | M!F;; | ||
+ | M(RCVAUW=H+4R=.`VFXUY[62TQ)`9FKYM& | ||
+ | MHP/ | ||
+ | MF^& | ||
+ | MA\37-)T80? | ||
+ | MP).O& | ||
+ | M]I3WN=TQ; | ||
+ | MM]G0QQ@?&; | ||
+ | M=; | ||
+ | MU7*9RS4N.# | ||
+ | M^OSITYPWXXAU_LS; | ||
+ | M5[@LU\: | ||
+ | MG< | ||
+ | M# | ||
+ | MZ)G5?/ | ||
+ | MS_$-]# | ||
+ | M^> | ||
+ | M\2P+; | ||
+ | MA)W3E4=@(G/# | ||
+ | MX/ | ||
+ | MPB_< | ||
+ | MY? | ||
+ | MTJU[Z_]> | ||
+ | MD!> | ||
+ | M' | ||
+ | M)Y@TW[E!0[D& | ||
+ | M& | ||
+ | MPX>' | ||
+ | M(1`< | ||
+ | M? | ||
+ | M)WUZU*2JL9> | ||
+ | MN%U4PAO\`Z%/' | ||
+ | MASPQ*X\UN+U9M(F!O`K41Q; | ||
+ | M4? | ||
+ | MK)F\YO; | ||
+ | M33MC" | ||
+ | M4(" | ||
+ | MT.(' | ||
+ | M1[)> | ||
+ | M*_YX3; | ||
+ | M%G" | ||
+ | MR4*SIDFSGB' | ||
+ | MNGN> | ||
+ | MX.BQI, | ||
+ | MH& | ||
+ | MXU1@`C_> | ||
+ | ML@XW)6O]"< | ||
+ | MK7U_/ | ||
+ | M^MSCXSN58' | ||
+ | MM' | ||
+ | M.EEU2!T=$D[47# | ||
+ | MJ]: | ||
+ | MF&:: | ||
+ | MJC%!E9O; | ||
+ | M%U, | ||
+ | MOH@*J+Y]D%: | ||
+ | M`ZHJH+)N5& | ||
+ | M6O1J[0FF> | ||
+ | M]; | ||
+ | M93A[UL804F< | ||
+ | M[24O>; | ||
+ | M4^B6T6; | ||
+ | M(+JP%9Y\? | ||
+ | MF(L3WCZEX%7U< | ||
+ | M: | ||
+ | M(1A]7I# | ||
+ | MI%; | ||
+ | M+< | ||
+ | MRVXU43B; | ||
+ | M/ | ||
+ | M'' | ||
+ | M' | ||
+ | MFP> | ||
+ | MUW_5T$G< | ||
+ | M+.FB& | ||
+ | M1P19;& | ||
+ | M@.-6SYQ)G!%' | ||
+ | MWKR9& | ||
+ | M$;#'/ | ||
+ | M# | ||
+ | MC" | ||
+ | M-AJ>" | ||
+ | M^`O' | ||
+ | M8: | ||
+ | M: | ||
+ | M"< | ||
+ | MP/ | ||
+ | ML: | ||
+ | MTO? | ||
+ | M-F, | ||
+ | MH]U=_.1+> | ||
+ | MJ4ATL63> | ||
+ | M"& | ||
+ | M)2/ | ||
+ | MP2[Y+W;> | ||
+ | MS+-9V-PL%C> | ||
+ | MHZ$I0\W^O0A& | ||
+ | M!`)# | ||
+ | M7J8H\0\> | ||
+ | MK=Y)5Q8W]: | ||
+ | M, | ||
+ | ML13# | ||
+ | M# | ||
+ | M-: | ||
+ | M-# | ||
+ | M<? | ||
+ | MPP, | ||
+ | M)\!T-*2& | ||
+ | MF@F%IVA7< | ||
+ | M$3F;' | ||
+ | M16AG> | ||
+ | M)5^, | ||
+ | MD2$OS, | ||
+ | M6XM-BBNEBG> | ||
+ | M& | ||
+ | M!46U6-6U' | ||
+ | ML9^/ | ||
+ | M9^W6)_LX9[># | ||
+ | M5FO%2" | ||
+ | M> | ||
+ | M/: | ||
+ | MOO' | ||
+ | MD*W^O)A1%H1S4HMS43A' | ||
+ | MS`=04< | ||
+ | MRYO4>;> | ||
+ | M/ | ||
+ | M# | ||
+ | MK$!2FUS1UEOI)(K" | ||
+ | M-!.S_#, | ||
+ | M41%2JBFT*X@%$: | ||
+ | M, | ||
+ | MY1N." | ||
+ | M" | ||
+ | MBMH=F$' | ||
+ | M& | ||
+ | M_GR`7^`LA*)GFD+).(> | ||
+ | M```/ | ||
+ | M? | ||
+ | M^YR]UHYU!`@#& | ||
+ | M`0Q-7.9Y#' | ||
+ | M`XZX2OL*> | ||
+ | MKR!: | ||
+ | M_TO& | ||
+ | M5*Y4CCT$@R< | ||
+ | M' | ||
+ | M8>' | ||
+ | M;? | ||
+ | M0P/ | ||
+ | M& | ||
+ | MRY: | ||
+ | M+88XOIV]X$584C`B< | ||
+ | MPXS/ | ||
+ | MX: | ||
+ | M: | ||
+ | M3S> | ||
+ | M], | ||
+ | MLZ2N82KA7U; | ||
+ | M]%9: | ||
+ | M+? | ||
+ | M2S<? | ||
+ | MEJSM@?# | ||
+ | M6]^!MJUTK10!C)+0U`/ | ||
+ | M2`Y& | ||
+ | M> | ||
+ | M4+N7+KV5.4AD=# | ||
+ | M)& | ||
+ | M? | ||
+ | M9_<: | ||
+ | MVS0\=JQ>: | ||
+ | MM2_KGHO& | ||
+ | M*Q+5\[Y!AT' | ||
+ | M(P: | ||
+ | M< | ||
+ | MOG*L+%# | ||
+ | M2=!7U)2JE# | ||
+ | M> | ||
+ | M6[" | ||
+ | M9!U: | ||
+ | M> | ||
+ | M_> | ||
+ | M: | ||
+ | MX2W*W23`^.6LF@A, | ||
+ | M8_Y+W1V$L]J; | ||
+ | M: | ||
+ | M< | ||
+ | M6%P2^-.7K(*JYCX" | ||
+ | M_)6CV7; | ||
+ | M_6U' | ||
+ | M]+M43O& | ||
+ | MXZ=/ | ||
+ | M' | ||
+ | M:' | ||
+ | MC]!1/ | ||
+ | M/ | ||
+ | MA--MRS`MLGG: | ||
+ | MC30X# | ||
+ | M; | ||
+ | MJP*B" | ||
+ | M`? | ||
+ | MH" | ||
+ | M" | ||
+ | MD-5\E!X$%0# | ||
+ | M/ | ||
+ | M2Y3YOH^" | ||
+ | M]MW; | ||
+ | M/; | ||
+ | MW< | ||
+ | M2> | ||
+ | MORNTGF; | ||
+ | M^^LJ^> | ||
+ | M(0*P0H\QI2: | ||
+ | MC& | ||
+ | MIU(107@RF_U\E7? | ||
+ | MCN^" | ||
+ | M=H\O, | ||
+ | M!TD? | ||
+ | M*"? | ||
+ | M/ | ||
+ | M-4KEM# | ||
+ | ML2'<' | ||
+ | M\& | ||
+ | M> | ||
+ | MJRA51P1-+4T*? | ||
+ | M? | ||
+ | M*? | ||
+ | M2^7O# | ||
+ | M\]? | ||
+ | MS[M8:& | ||
+ | M;# | ||
+ | M[Q%P\R!K.& | ||
+ | M: | ||
+ | M)P7$!N[YA.OSO90V-D? | ||
+ | M8OP+`? | ||
+ | MIP)H2Z> | ||
+ | MZ6%<& | ||
+ | M369# | ||
+ | M" | ||
+ | MRU(OZU`X/# | ||
+ | MLM`U0? | ||
+ | M&#/ | ||
+ | M`7@PT`G@AF), | ||
+ | M!=3< | ||
+ | M; | ||
+ | MH&' | ||
+ | M0<< | ||
+ | M)#; | ||
+ | MWW^@X, | ||
+ | MV!" | ||
+ | M6:?' | ||
+ | MK' | ||
+ | M3_; | ||
+ | M[9@L> | ||
+ | M4*%G`[KP_UU2I: | ||
+ | M7`YQ> | ||
+ | M/ | ||
+ | M$/ | ||
+ | M; | ||
+ | M-J$PP*KL`V? | ||
+ | MY=AJ$# | ||
+ | MMIJ$' | ||
+ | M+X7# | ||
+ | M& | ||
+ | M& | ||
+ | MD$ZNQMZ]2/ | ||
+ | MP514@B_-Y/ | ||
+ | M5*J# | ||
+ | MH(< | ||
+ | M4_: | ||
+ | M6J4' | ||
+ | MS## | ||
+ | M-Y9((F< | ||
+ | ME+& | ||
+ | M' | ||
+ | M)QH" | ||
+ | MCG\; | ||
+ | MG/ | ||
+ | MLMO[H; | ||
+ | M_`^' | ||
+ | MZ.D\'< | ||
+ | M=^V-![2_UQ*AY3HHKYPDUWA?' | ||
+ | MNUQV$%G]7" | ||
+ | MHR@HM0[/ | ||
+ | MY`1? | ||
+ | M0# | ||
+ | MPJT& | ||
+ | M0: | ||
+ | MN54' | ||
+ | MBA&" | ||
+ | M23? | ||
+ | MY#; | ||
+ | M*F+0)F2& | ||
+ | M%< | ||
+ | M2P3EBR)RF< | ||
+ | M=G6!36Z' | ||
+ | M`-&? | ||
+ | M0.9UN; | ||
+ | M!A-M-$T%J%5]^S\: | ||
+ | M3=0ORXR=A; | ||
+ | M[> | ||
+ | M& | ||
+ | M0MC<" | ||
+ | MJHDP7GR).SR)> | ||
+ | MB*\6]O? | ||
+ | MU# | ||
+ | M9N2D< | ||
+ | MZY# | ||
+ | M?; | ||
+ | MF[@QWE*_5" | ||
+ | M" | ||
+ | MO]RO[V)M1Q*, | ||
+ | M=& | ||
+ | M[' | ||
+ | M=SG__" | ||
+ | M, | ||
+ | M? | ||
+ | M1U$0& | ||
+ | MA& | ||
+ | MNBO+-TZ^\B@.# | ||
+ | M)XKN$: | ||
+ | M; | ||
+ | MT'< | ||
+ | M`6I# | ||
+ | M$8CUI2$B3,> | ||
+ | M92U]+R_7`K.W1ECQ(%BUL83BD; | ||
+ | MH4UQ0B#' | ||
+ | M0S-7># | ||
+ | MV? | ||
+ | M54NV5J-PW@_)UQ' | ||
+ | M" | ||
+ | M?" | ||
+ | M=IC]I//" | ||
+ | M`_< | ||
+ | MDAT2KS(++)V: | ||
+ | ME; | ||
+ | M_^I54> | ||
+ | M:' | ||
+ | M& | ||
+ | M(Z",; | ||
+ | M9ATGJ$UHU02P0HI1J8-2E@; | ||
+ | M$R_-" | ||
+ | MHDJ/ | ||
+ | MGM; | ||
+ | M# | ||
+ | M10^' | ||
+ | M$L0CR: | ||
+ | MXS> | ||
+ | MWIP/ | ||
+ | MWDY7Z_\[$H7: | ||
+ | M^? | ||
+ | M0G# | ||
+ | MG^%0" | ||
+ | MT+5-0& | ||
+ | MFXS$Y# | ||
+ | M\G[S)/ | ||
+ | MZ/ | ||
+ | M)GU/ | ||
+ | M2; | ||
+ | M=ZT/ | ||
+ | M(\" | ||
+ | M76\D5/ | ||
+ | MJ2BA# | ||
+ | M/ | ||
+ | M5APM72R7:# | ||
+ | MVR.^# | ||
+ | M%,& | ||
+ | M2?# | ||
+ | MF]3$> | ||
+ | MM-T].CSJZER]O0H^.@HNSPD_O: | ||
+ | M/ | ||
+ | M*JE*I`, | ||
+ | M.`2`-H'/ | ||
+ | M;? | ||
+ | MZ, | ||
+ | MF2/ | ||
+ | M`ST_O< | ||
+ | M.CIGFM75/ | ||
+ | M6; | ||
+ | MMHF8\$ZJ9J=' | ||
+ | MA; | ||
+ | MZME/& | ||
+ | MC, | ||
+ | MJ`V& | ||
+ | MS>: | ||
+ | M%5ID9L!!, | ||
+ | MG6=MU555' | ||
+ | M^T: | ||
+ | M> | ||
+ | MSPM' | ||
+ | MS/ | ||
+ | MGO5G7CO[# | ||
+ | M& | ||
+ | MF+2, | ||
+ | MWTQ)W=VMKFYK*UIT[3@ZH, | ||
+ | M/ | ||
+ | MD]1A# | ||
+ | MMZQFEP*> | ||
+ | M_OBX^[ML[_' | ||
+ | MQCE&; | ||
+ | M8]GN; | ||
+ | M)53GV> | ||
+ | M\> | ||
+ | M=S< | ||
+ | MC^W@< | ||
+ | MLJ*ZG(> | ||
+ | M+0Q6: | ||
+ | MP@2-PV_VLY(Y5' | ||
+ | M61P+_VQ[A_& | ||
+ | MC%, | ||
+ | MV!]^2\W4UH' | ||
+ | M+^L< | ||
+ | M27M8> | ||
+ | M1W1TN53" | ||
+ | MADA$? | ||
+ | M8@/ | ||
+ | MUCC/ | ||
+ | MWYV& | ||
+ | MXV8\=53C; | ||
+ | MGS& | ||
+ | MRGP\*> | ||
+ | MX5.I5U%+: | ||
+ | MM< | ||
+ | MS8" | ||
+ | M_J?> | ||
+ | MY/< | ||
+ | MB>/ | ||
+ | MKQ1.' | ||
+ | MW? | ||
+ | M3!=: | ||
+ | MZC[# | ||
+ | MP3^Y=, | ||
+ | MV$%G)5`)U5/ | ||
+ | MSY-8A1H88OOOBHX/ | ||
+ | MJ7.R%E)F2ORRM6C3!N=' | ||
+ | M5, | ||
+ | M`O2MKH: | ||
+ | M> | ||
+ | M; | ||
+ | M# | ||
+ | MOX-=R6VY09I9YH' | ||
+ | MS# | ||
+ | MA=XI-M%I%!E; | ||
+ | M`4%Z> | ||
+ | M\C]=# | ||
+ | M]K^UPP!HD-2E@" | ||
+ | MNMPEVUQ_9F5M, | ||
+ | MWB1@7' | ||
+ | MU9'& | ||
+ | MS7Z2' | ||
+ | M7EB4E+S^4N@9, | ||
+ | MX4)O/? | ||
+ | M74X5!0' | ||
+ | M' | ||
+ | MK)FH' | ||
+ | M2& | ||
+ | MQ.35QFZS$1W< | ||
+ | MOZ> | ||
+ | MCK+*9QDRLD? | ||
+ | M_YKW+.P=O7WR[K+N[D$B+M5PQF[& | ||
+ | MLJ5BD@# | ||
+ | M< | ||
+ | M# | ||
+ | M^" | ||
+ | M' | ||
+ | M007Z^*ILP1OCL_.I" | ||
+ | M8%-D, | ||
+ | MAGF=N(W, | ||
+ | MF\-> | ||
+ | MI': | ||
+ | M& | ||
+ | M/& | ||
+ | M+6-$, | ||
+ | ME2GERVF)!G' | ||
+ | M1:' | ||
+ | MZH_OF& | ||
+ | MIV, | ||
+ | M' | ||
+ | M\].# | ||
+ | MF> | ||
+ | MG_1GO9IO< | ||
+ | MBLT+7; | ||
+ | M)2Y& | ||
+ | ML; | ||
+ | M& | ||
+ | M3' | ||
+ | MV" | ||
+ | MJFK-7EIV@$H^1V!H8Q&': | ||
+ | M_4LX3GO%$DW=[NEC; | ||
+ | MY? | ||
+ | MAC=J.FU$(D' | ||
+ | M+A2O_9:" | ||
+ | MZW10^4-96U5KV^YMV6ZT=-063T.7),?, | ||
+ | MV" | ||
+ | MB+!S0V2&# | ||
+ | M*=PHK$PV3DZ*X%DLW`F00(!? | ||
+ | MZE%# | ||
+ | M8$> | ||
+ | M? | ||
+ | M(6R*& | ||
+ | MBXNOJ< | ||
+ | M7^J3R]N^2-8; | ||
+ | MSSSJ& | ||
+ | MW\29L368!/ | ||
+ | M).ZQ,<< | ||
+ | M7HE? | ||
+ | MXN& | ||
+ | MEPG*)@0MO3!, | ||
+ | MSP[# | ||
+ | M]*? | ||
+ | MS([L1X_> | ||
+ | M# | ||
+ | MN]W# | ||
+ | M-RKNO< | ||
+ | M()(L$AP.L6Y6UA861JT^QU-$]]+[IV)[!& | ||
+ | M14KP+9FT, | ||
+ | M)8B<; | ||
+ | M< | ||
+ | MOF_(FK$8!' | ||
+ | M@" | ||
+ | MF-5XQ=S? | ||
+ | M]!).[/ | ||
+ | M"? | ||
+ | MV3; | ||
+ | M& | ||
+ | M%]S; | ||
+ | ME52" | ||
+ | M"> | ||
+ | MY9XZM[J^^' | ||
+ | M# | ||
+ | M==9R? | ||
+ | MRF^A+QBDED!5< | ||
+ | M_@L." | ||
+ | MKT[, | ||
+ | MXNN\R< | ||
+ | M' | ||
+ | M_8`^$W@%NBZ((; | ||
+ | M(7D^2Z,# | ||
+ | M_Y1W$, | ||
+ | MT7.K7< | ||
+ | M8XFBA: | ||
+ | M%A" | ||
+ | MU5Q< | ||
+ | M\1: | ||
+ | MT_&<#& | ||
+ | M<& | ||
+ | MT]NX13TO5^: | ||
+ | MX2, | ||
+ | MAV-SXK_TJ]TI3^=I& | ||
+ | M<? | ||
+ | M^PA)', | ||
+ | MO& | ||
+ | M!& | ||
+ | M< | ||
+ | M7> | ||
+ | M# | ||
+ | MU33MWW7=; | ||
+ | MB(88R> | ||
+ | MJ([\$+^F0< | ||
+ | MJJ3S@9&/ | ||
+ | M`+' | ||
+ | M0O\.*^< | ||
+ | M/ | ||
+ | M*BH^M' | ||
+ | ML613!" | ||
+ | M!P? | ||
+ | M%H, | ||
+ | MN`TH(+; | ||
+ | MY" | ||
+ | MFV, | ||
+ | M1R@48; | ||
+ | M16ZED%@5A@3_: | ||
+ | MKX80J*-*8!K8@OEI7P2" | ||
+ | M)X8/ | ||
+ | ML%E184Q># | ||
+ | MO37WYG^NQ89P4[; | ||
+ | M[: | ||
+ | M$T!5VDV? | ||
+ | MJ`" | ||
+ | M-*J3LF< | ||
+ | MI/ | ||
+ | MDL%7]I8(& | ||
+ | M=[/ | ||
+ | MF3KX/" | ||
+ | MWQ^[X6CVS7@=(5_XNV%C; | ||
+ | M>: | ||
+ | M_L> | ||
+ | M-`BC4E# | ||
+ | MA' | ||
+ | M\9.N8NFHXP#& | ||
+ | MR8M%.GL.< | ||
+ | M)B_OJB+< | ||
+ | MX%; | ||
+ | ML1< | ||
+ | MX=0' | ||
+ | MI_U^%368HQJ$QT4<> | ||
+ | MWH(P, | ||
+ | M$=M& | ||
+ | MD$+E3+< | ||
+ | M3D& | ||
+ | M%? | ||
+ | M`6M)BP+H-P0KYH; | ||
+ | M" | ||
+ | M> | ||
+ | M2]: | ||
+ | M]59# | ||
+ | MY$3< | ||
+ | M@H-&<& | ||
+ | M6" | ||
+ | MLV.)]? | ||
+ | MVG63"? | ||
+ | M_)_; | ||
+ | M`' | ||
+ | M1!*G]=GA%< | ||
+ | MB(, | ||
+ | MXGV.[5< | ||
+ | M]: | ||
+ | M< | ||
+ | M-V"" | ||
+ | MAW5/ | ||
+ | M$7!# | ||
+ | M_; | ||
+ | M+YX: | ||
+ | M@*YRLI; | ||
+ | M(BK: | ||
+ | MCQK!P(!3<' | ||
+ | MWO" | ||
+ | M/ | ||
+ | MP8HLD1C^SU!' | ||
+ | MOG=[^A)%)" | ||
+ | M$(%UC?? | ||
+ | M[: | ||
+ | M& | ||
+ | M<> | ||
+ | MOPO@VG`# | ||
+ | MKXT!-RT])# | ||
+ | M)Q76< | ||
+ | MZ!: | ||
+ | M1TL79? | ||
+ | MJ, | ||
+ | M^? | ||
+ | M; | ||
+ | M, | ||
+ | M(QK^FM-U> | ||
+ | MO+KZ:, | ||
+ | M\? | ||
+ | MBK][MQ# | ||
+ | M0\S4Q$# | ||
+ | MS72J_1<#,? | ||
+ | M0WRU& | ||
+ | M7CQI@77< | ||
+ | MWXAGHST" | ||
+ | MFM@; | ||
+ | M" | ||
+ | MQ&' | ||
+ | M$QM7=';" | ||
+ | MB(O\LMWA< | ||
+ | M)U> | ||
+ | M=).CZUO_RQ!9J\[> | ||
+ | MJ\$OA_=0G$1-GT]K!2X_WM00`-)(=> | ||
+ | MI.NDPA@(+$0HA@JB" | ||
+ | MD)-.? | ||
+ | MN< | ||
+ | MFRITBCIZO`0K: | ||
+ | MY_# | ||
+ | MM=; | ||
+ | M4PH8? | ||
+ | M3.Q86*K1`!T)%N%=/ | ||
+ | MO*G& | ||
+ | M> | ||
+ | M%99FKM+=S2(]?# | ||
+ | MGPV\(' | ||
+ | M& | ||
+ | M!`2_3`FH7J" | ||
+ | M_/ | ||
+ | M? | ||
+ | MU9ES%WTW^2(, | ||
+ | MD' | ||
+ | MGFN30& | ||
+ | M98? | ||
+ | MSM%HK5:' | ||
+ | M%SD0E!@)H1]6US8-=: | ||
+ | MV3C/ | ||
+ | M? | ||
+ | MD' | ||
+ | M:; | ||
+ | M/>< | ||
+ | MW? | ||
+ | M-; | ||
+ | MK(QH+> | ||
+ | M!25*:/;< | ||
+ | MW+Y?: | ||
+ | MNKKR5VAH]`STF]3RBC!)& | ||
+ | M1@CM" | ||
+ | M0P00+`U$Y+%_, | ||
+ | MK/ | ||
+ | M]-6FX" | ||
+ | M8[\[Z\L: | ||
+ | MV=' | ||
+ | M%# | ||
+ | M*PKWT< | ||
+ | MLJ1%3S\DI-131]AV\H^"> | ||
+ | M2ONX(ZJ*# | ||
+ | M,& | ||
+ | M]_? | ||
+ | M`> | ||
+ | M0YI/ | ||
+ | M6' | ||
+ | M=TR]\L1< | ||
+ | M9-T; | ||
+ | MXZ-AA; | ||
+ | M: | ||
+ | MU[YYPS\S=, | ||
+ | M-`Q+' | ||
+ | MG[& | ||
+ | MO+MK<?, | ||
+ | M<&, | ||
+ | MBC`2*`: | ||
+ | MGF0-# | ||
+ | M+OL/ | ||
+ | MT/ | ||
+ | MN9# | ||
+ | MU=A8; | ||
+ | M5, | ||
+ | MW"# | ||
+ | M6):< | ||
+ | MJKI\; | ||
+ | MW_IN: | ||
+ | M<#& | ||
+ | M3Q/ | ||
+ | MRP< | ||
+ | MTJQ, | ||
+ | M80_DL< | ||
+ | M8MO5V^7DF5<? | ||
+ | M5VX87X5]WA4\< | ||
+ | MEJ=(HW+\R\: | ||
+ | MDW< | ||
+ | M+8R-6" | ||
+ | M.E`.S3=`W034%D0V< | ||
+ | MIZ]FG36YU!V: | ||
+ | M]`; | ||
+ | MR& | ||
+ | M56' | ||
+ | MK> | ||
+ | MTK6UM5^C_Z51-CMU.G7Q<' | ||
+ | MRV_LZ# | ||
+ | MM4R1%>#& | ||
+ | ML(H.59N\NY%1/ | ||
+ | M> | ||
+ | M!F$^A(A7)*`)S580&' | ||
+ | M/ | ||
+ | M$A!J(I0J& | ||
+ | MO5!7C; | ||
+ | M79F.TT41K@E/ | ||
+ | MIXQ6(*/ | ||
+ | MC2VC^(Y)DLN.;:' | ||
+ | M<> | ||
+ | M0DETR%99$M[" | ||
+ | M44)A6" | ||
+ | M*%U(K; | ||
+ | M" | ||
+ | ML7+OX@TV-S[X' | ||
+ | MWZ*\*, | ||
+ | M; | ||
+ | M]%C(I<' | ||
+ | M> | ||
+ | M1`)F`Q1< | ||
+ | M" | ||
+ | M: | ||
+ | MF& | ||
+ | MED]T< | ||
+ | M12DIK1W7+0XOPOLN# | ||
+ | ML& | ||
+ | MZP=\O"" | ||
+ | M1.E-ZXUJ3=; | ||
+ | M2, | ||
+ | M_*8+[/& | ||
+ | M^& | ||
+ | MPRD2$B)" | ||
+ | M9%U=4V77#>;< | ||
+ | M+$E*PW3$' | ||
+ | M1_8MW3I5? | ||
+ | M9NR88/& | ||
+ | MP# | ||
+ | MB(-@-DIA/ | ||
+ | MC^4/ | ||
+ | M8$1_& | ||
+ | MFM? | ||
+ | MU(: | ||
+ | M? | ||
+ | M$" | ||
+ | M]'< | ||
+ | M3)CBZ2H> | ||
+ | M' | ||
+ | ML=8C0NTM7& | ||
+ | M" | ||
+ | MP3^33NG`# | ||
+ | M$`!U; | ||
+ | MD18O[E!**0[%?: | ||
+ | MF3GW)), | ||
+ | MDBERYFNT]$W: | ||
+ | M1%U? | ||
+ | M,#< | ||
+ | M=*'/ | ||
+ | M/ | ||
+ | MTX2%# | ||
+ | M? | ||
+ | MWX@V, | ||
+ | ML& | ||
+ | M0I`VS:? | ||
+ | M4!^@]VXS/ | ||
+ | MO9BWYG6\-I5' | ||
+ | M0GM< | ||
+ | MHH" | ||
+ | MOS5: | ||
+ | M+^[[AMJT)# | ||
+ | MH$*: | ||
+ | M^D^# | ||
+ | MUW3R[RN]EOA0RVK9=%*PU@K3\FFVEI=N_=L+" | ||
+ | M%M-$4%:# | ||
+ | M).=I@P: | ||
+ | M]I-4? | ||
+ | MT# | ||
+ | M? | ||
+ | MV" | ||
+ | M)60RE$3; | ||
+ | M_/ | ||
+ | M; | ||
+ | M2RCEY\8.-$QF)A4E+Q4+6P*I=8[< | ||
+ | MHX, | ||
+ | MNW;# | ||
+ | M2OTKZ8$$4@PM9? | ||
+ | MVF=B0I%AQ[Q_EY1!, | ||
+ | M+$7K?; | ||
+ | MR" | ||
+ | MB" | ||
+ | M1_!6YGO0[VWDS^9_=E!D0W: | ||
+ | MHV@UI.U" | ||
+ | MA, | ||
+ | M4TSB)QKOJMX0@M&& | ||
+ | MWK3KC6EK1RX-Z1? | ||
+ | MB/ | ||
+ | MCDV[[Y0=1^4/ | ||
+ | MG/ | ||
+ | M*ABDWZLO^]]> | ||
+ | M& | ||
+ | MD< | ||
+ | M4EUCT)T+.(9UW]3*`8[I" | ||
+ | MP,/ | ||
+ | MMA_WSYMXY$U$UX" | ||
+ | MA"; | ||
+ | M9@' | ||
+ | M12E& | ||
+ | M' | ||
+ | M%VRQ< | ||
+ | MH4< | ||
+ | MB, | ||
+ | MOX4%@X> | ||
+ | M[O;/ | ||
+ | M" | ||
+ | M?; | ||
+ | M6G9_8^F1P^CYPH' | ||
+ | M$H& | ||
+ | MMX88< | ||
+ | MAV6$HCF$`' | ||
+ | MXH3RFH,? | ||
+ | M.$$# | ||
+ | MS<< | ||
+ | MU4I#? | ||
+ | M, | ||
+ | M4(: | ||
+ | M%523X$E' | ||
+ | M" | ||
+ | MKZV34!JCIU(' | ||
+ | M)QCJ-92X!DG@R6JJG67$\*OOW8E)=^/ | ||
+ | MPA=); | ||
+ | M? | ||
+ | MM6A@-_BS`G/ | ||
+ | MU: | ||
+ | M^, | ||
+ | M$`-NW# | ||
+ | MTW\SXGK./; | ||
+ | M_LTGQK@; | ||
+ | M9H(2> | ||
+ | M71< | ||
+ | M0, | ||
+ | MCG, | ||
+ | MC3K7M6< | ||
+ | M56S(R, | ||
+ | M" | ||
+ | MRF'; | ||
+ | M# | ||
+ | MC[C8XKI=), | ||
+ | ML, | ||
+ | MLM/ | ||
+ | M-=-0H^$-L? | ||
+ | MH%N4LXAP_G[*Z[P$@`G3? | ||
+ | M8L%HDS; | ||
+ | M' | ||
+ | MN[L[C`N8ZU(7T\A*4OO=' | ||
+ | MNJFKCR]YF`1; | ||
+ | M8BY/ | ||
+ | M5FMZWKHX=M& | ||
+ | M? | ||
+ | M: | ||
+ | MM\' | ||
+ | MO((< | ||
+ | M.J5" | ||
+ | M%9D, | ||
+ | M].42S(@F`R9^(8G_Y?&# | ||
+ | MDF# | ||
+ | MUPDN1& | ||
+ | MG> | ||
+ | MA0[3LDTP;' | ||
+ | M: | ||
+ | MQ7[.L[J< | ||
+ | M=5> | ||
+ | MC/ | ||
+ | M*WUREV" | ||
+ | M72^& | ||
+ | M; | ||
+ | M)I)7!\.' | ||
+ | MHB.+N&, | ||
+ | MB5/ | ||
+ | M!3GRZ`E2I6_\: | ||
+ | M6$7G=`0ZX(4\Y[YXR\LU& | ||
+ | M; | ||
+ | MRKQ&& | ||
+ | M7R/ | ||
+ | MJY8]DS.< | ||
+ | M& | ||
+ | MTEII!(-M^S^*; | ||
+ | MUYD<' | ||
+ | MYZ" | ||
+ | MC8M4NH, | ||
+ | M-S5& | ||
+ | M]X</ | ||
+ | M`JAXB\0> | ||
+ | MKREQAD, | ||
+ | MG[EJ+6)*WEU_E2!V*7& | ||
+ | MB# | ||
+ | M$(G_%Z`TA=P^`" | ||
+ | MVNK7DKF=KW+OYER$$7^REELW7RH< | ||
+ | M(^45Q9J0P; | ||
+ | M[\R8N> | ||
+ | M& | ||
+ | MX+0M-)J@N6TPCW%24> | ||
+ | MYRC([: | ||
+ | MD?# | ||
+ | MS@NLDCX^4U/ | ||
+ | MNM)# | ||
+ | M? | ||
+ | M5NR<> | ||
+ | M? | ||
+ | M0?' | ||
+ | M$*)1.S1> | ||
+ | M_70' | ||
+ | M+\%Z,; | ||
+ | MG<" | ||
+ | MW!$NJP!JC[AN0$7@G: | ||
+ | MKB/ | ||
+ | MV%6)B7< | ||
+ | MYT6K> | ||
+ | MF/ | ||
+ | MTDP*H(J=< | ||
+ | MS57-9G.*!^3R5C6*!V^@E-%`_\0RTD[GR[; | ||
+ | M=5; | ||
+ | MFXN3K[? | ||
+ | M=\/ | ||
+ | M0? | ||
+ | M`O: | ||
+ | MY3TXT0^; | ||
+ | MCY8F/ | ||
+ | M7)_@A%!G3`4]3C: | ||
+ | MFO^`_*=\& | ||
+ | M/ | ||
+ | M1J3N%_1B=RF`D2< | ||
+ | M+=^, | ||
+ | MVZQ" | ||
+ | MQB!H/ | ||
+ | M)05[^R7" | ||
+ | MD5SIS[G*\IT>: | ||
+ | MMS' | ||
+ | MH2C4XVD(]CO& | ||
+ | M5S7B[U[OZHN[U<< | ||
+ | M9K]15APK)6M'&, | ||
+ | M,& | ||
+ | MP*%I95VT., | ||
+ | MS# | ||
+ | M1; | ||
+ | M\*S9AUN; | ||
+ | M3K[; | ||
+ | MH2/ | ||
+ | M5Q^, | ||
+ | M]0%^59[/ | ||
+ | MSVL)DD@G_Q\5' | ||
+ | M1.WB!1# | ||
+ | MT< | ||
+ | MW&? | ||
+ | MBO=-I/ | ||
+ | ME' | ||
+ | M: | ||
+ | MR14" | ||
+ | MP9# | ||
+ | M!GS%\? | ||
+ | M# | ||
+ | M-/ | ||
+ | MKE1; | ||
+ | M: | ||
+ | M< | ||
+ | M5O5@? | ||
+ | MOYZ[P, | ||
+ | MS$^(? | ||
+ | M-^^0B8/ | ||
+ | M5]:" | ||
+ | M: | ||
+ | MDUF`^(T%9,># | ||
+ | MC\/ | ||
+ | M2UO676Z< | ||
+ | M(]O1U# | ||
+ | MKH(INYDL: | ||
+ | MF76`V(QL\# | ||
+ | M2OH& | ||
+ | M_" | ||
+ | MGP[()YC; | ||
+ | M: | ||
+ | MF.& | ||
+ | M4" | ||
+ | M!"> | ||
+ | MN67; | ||
+ | MK^3\OM: | ||
+ | M1P6_U=67# | ||
+ | MJA`' | ||
+ | M*%Y6" | ||
+ | M&:# | ||
+ | M.2TYZ2\MF.< | ||
+ | M1*]*9> | ||
+ | MGK6Y^JSS]9: | ||
+ | MC, | ||
+ | M> | ||
+ | MZ/ | ||
+ | ME' | ||
+ | M!SPT> | ||
+ | MQ"; | ||
+ | MH: | ||
+ | M2+Q: | ||
+ | M1J8*)%Q< | ||
+ | MVIXDD]9U' | ||
+ | M\D=%_0!X: | ||
+ | MY*E)WB\)ZY!0SV+5^2R-[=TZ6/ | ||
+ | MN7< | ||
+ | MNC; | ||
+ | M9D: | ||
+ | MT!68[(M3; | ||
+ | M.[# | ||
+ | M&;# | ||
+ | MEW5$(; | ||
+ | M0^.WH_YVK&> | ||
+ | M$L_; | ||
+ | MB969< | ||
+ | M[Z7V\H]LEL[" | ||
+ | M), | ||
+ | M*NW& | ||
+ | M@+25[=L39]" | ||
+ | MID\M7G]8ZC0UX563> | ||
+ | M; | ||
+ | MW# | ||
+ | M550# | ||
+ | M=, | ||
+ | M; | ||
+ | M& | ||
+ | M+5BLGYLT$M\K27AM-, | ||
+ | MU5; | ||
+ | M# | ||
+ | M[?,< | ||
+ | ME++KU%3I' | ||
+ | MHV# | ||
+ | MTU18N# | ||
+ | MD\/ | ||
+ | M[A1-< | ||
+ | MHU1, | ||
+ | M4F_, | ||
+ | M; | ||
+ | M: | ||
+ | M, | ||
+ | MF8?? | ||
+ | MU_)G`M7=!]QJ%*.M^P+\5< | ||
+ | MF, | ||
+ | M/ | ||
+ | MJ%).NNIP&> | ||
+ | MTQ? | ||
+ | M$: | ||
+ | MTHNXXW$' | ||
+ | M2M*\N^PK" | ||
+ | M/ | ||
+ | MOG1@6!P: | ||
+ | MAY; | ||
+ | MS]L=; | ||
+ | M> | ||
+ | M`_B]: | ||
+ | M, | ||
+ | M_; | ||
+ | MB.? | ||
+ | MZ!T9; | ||
+ | MGTYB94!B2? | ||
+ | MU< | ||
+ | M&& | ||
+ | M04Z? | ||
+ | M? | ||
+ | M13/ | ||
+ | M8(OQ$YAD^MZ2N1OR]; | ||
+ | MM/? | ||
+ | M]EW< | ||
+ | M[" | ||
+ | ML1VEC> | ||
+ | M_5+LV? | ||
+ | MWB!OR2GC19XFM.KM!E7QOE%=S, | ||
+ | M> | ||
+ | M6KK_L-G8WR7A^U2/, | ||
+ | M`G3OY# | ||
+ | MS' | ||
+ | M(](' | ||
+ | M[B[G2T[4; | ||
+ | M]!369JX&, | ||
+ | M" | ||
+ | M# | ||
+ | MP`I][NJXHD!5ADG$? | ||
+ | MKMU4Y8VXM&#> | ||
+ | MV++@B*COI2\: | ||
+ | MC^O\> | ||
+ | M; | ||
+ | MP4X6? | ||
+ | M)[" | ||
+ | M)0M\`[5TT=9]6\$^NE4GP=F$797R+=6=< | ||
+ | M^I+LYYN)2" | ||
+ | M_]*+4UZO)USTYJ.? | ||
+ | M9]" | ||
+ | MK: | ||
+ | M" | ||
+ | M=L!W[1=B\" | ||
+ | M9X=9U1[*ZW? | ||
+ | M-@X" | ||
+ | M(# | ||
+ | M(, | ||
+ | MZ; | ||
+ | MDH4==CL\TH^& | ||
+ | M!C9+ZP, | ||
+ | M3!N0P!KI+WBO; | ||
+ | M$]; | ||
+ | M,? | ||
+ | ME' | ||
+ | M@M8(57F[S"> | ||
+ | MV_*5V[]A? | ||
+ | MH" | ||
+ | MEA& | ||
+ | M$CTLP< | ||
+ | MXFQ, | ||
+ | M_^O; | ||
+ | MSB7ZAIA)7H^0`440+0O`PV$):: | ||
+ | MVYC5O[R6# | ||
+ | MSO9LGSUN/ | ||
+ | MLBZ: | ||
+ | MNPGO1; | ||
+ | MPC' | ||
+ | MSO-WD(? | ||
+ | MHD\=" | ||
+ | M`IC@_+I: | ||
+ | M-W-**DZ" | ||
+ | M? | ||
+ | M+# | ||
+ | M/#' | ||
+ | M=!CQK> | ||
+ | M# | ||
+ | M3T? | ||
+ | M8(, | ||
+ | MFX? | ||
+ | M8K$%]HG9+8C.09.5:></' | ||
+ | MC)5? | ||
+ | MQ4%.C.YGQ1B)T%: | ||
+ | ML48: | ||
+ | M< | ||
+ | M0][G"> | ||
+ | M? | ||
+ | MI5Y'' | ||
+ | M$S$R+U!2& | ||
+ | M< | ||
+ | MAJV& | ||
+ | M=AS> | ||
+ | MOU]; | ||
+ | M: | ||
+ | M? | ||
+ | M=D.QB8)\& | ||
+ | M)Q)]J@C8T4; | ||
+ | MQEJS; | ||
+ | MT?> | ||
+ | MJ]< | ||
+ | M-ANQWRALB[L-, | ||
+ | MT[UJQP_' | ||
+ | M5]AA4%+OVBJ$UG%IU2D? | ||
+ | M7MZG1> | ||
+ | M34LN? | ||
+ | M+? | ||
+ | M#? | ||
+ | MYA+J' | ||
+ | MS]: | ||
+ | M!K0)0TT2J& | ||
+ | MR$TE& | ||
+ | MMK< | ||
+ | M# | ||
+ | M4XTV;? | ||
+ | M7+V=UK-]W': | ||
+ | MIG14JJ; | ||
+ | MC+`KT2/ | ||
+ | M=.9!OX$DXG# | ||
+ | M+)]E& | ||
+ | M6^W!< | ||
+ | M" | ||
+ | MP& | ||
+ | M\G> | ||
+ | M@K-W, | ||
+ | MZ.:; | ||
+ | M9E9ZL)O=Q^& | ||
+ | M]^[=K]RGS5-8; | ||
+ | M(/ | ||
+ | MOT\> | ||
+ | M& | ||
+ | M5+K]D+< | ||
+ | M@V+> | ||
+ | MX>& | ||
+ | M!PF8LJ> | ||
+ | MXM9" | ||
+ | MYYN' | ||
+ | M> | ||
+ | MSC!_!V.0? | ||
+ | MT=C*0GB)' | ||
+ | M@' | ||
+ | MB$$%5> | ||
+ | M;#/ | ||
+ | M-& | ||
+ | M=> | ||
+ | MW/; | ||
+ | MK48;# | ||
+ | MNVR64E%9: | ||
+ | M\^\' | ||
+ | M1' | ||
+ | M3K+(P=W^AG_NRE& | ||
+ | MB3=C.X3)21_'> | ||
+ | M]QAT< | ||
+ | M^/ | ||
+ | MBHL+EK, | ||
+ | MI(`!+< | ||
+ | M]`YJ3? | ||
+ | M`!/'" | ||
+ | M-91TF[2< | ||
+ | M' | ||
+ | M_9,#< | ||
+ | MEIO+& | ||
+ | M, | ||
+ | M.H_J4.& | ||
+ | MF.M, | ||
+ | M.RWI9W).? | ||
+ | M0Q^R]LP0S!T> | ||
+ | MX^; | ||
+ | M<?; | ||
+ | M19KJ# | ||
+ | M[, | ||
+ | M]7E9KZN< | ||
+ | MD/ | ||
+ | M65Y1"" | ||
+ | M, | ||
+ | M^_7CPS`.]T5*'' | ||
+ | M' | ||
+ | MCR1P: | ||
+ | M$Z)<> | ||
+ | M; | ||
+ | M70*X$J; | ||
+ | MD(@QJ9U_*JJKR]7T2' | ||
+ | ME: | ||
+ | MLV/ | ||
+ | M? | ||
+ | MI> | ||
+ | M)" | ||
+ | MD_QOX6A< | ||
+ | M7I-^[+4^VB]+APMPQ? | ||
+ | MZ; | ||
+ | M" | ||
+ | M; | ||
+ | MVON+ZUY)`J\SJ81`AYCL_: | ||
+ | MPIZ@^C> | ||
+ | MG" | ||
+ | MQT3Y3; | ||
+ | M-N6FA1Y9, | ||
+ | M!; | ||
+ | M1HD[6\T\1Z1_? | ||
+ | M*[S`V!> | ||
+ | MI:; | ||
+ | MD, | ||
+ | M5YNX709; | ||
+ | M< | ||
+ | ME)2:/ | ||
+ | M(X97!U' | ||
+ | M5=SKD.& | ||
+ | MAKPM)29+2PM!%0=\/ | ||
+ | MO4\' | ||
+ | M> | ||
+ | MU@W*B? | ||
+ | MA/ | ||
+ | MCJ$8.# | ||
+ | MX> | ||
+ | M8+!BJW+"' | ||
+ | M;' | ||
+ | MD@`6_> | ||
+ | M^? | ||
+ | M.L71H%; | ||
+ | M7<' | ||
+ | M' | ||
+ | M[$<, | ||
+ | MJ9L)N9V+Y]]Y& | ||
+ | M< | ||
+ | MKG6)=7MM0A< | ||
+ | MO> | ||
+ | MU%G: | ||
+ | MOVT; | ||
+ | M(-UL5M,; | ||
+ | M[& | ||
+ | M; | ||
+ | MXJZ;/ | ||
+ | M53\%LX5@QC=A`%RSCC1F*ZIF$R.II.7WMG> | ||
+ | M? | ||
+ | MY@Q; | ||
+ | M1=& | ||
+ | MG9,? | ||
+ | MBH)`VW@; | ||
+ | MM-"; | ||
+ | MW< | ||
+ | M, | ||
+ | M=8K=@DU-BG8DBO5F/ | ||
+ | M92, | ||
+ | M> | ||
+ | MA(Z/ | ||
+ | M^5.Q, | ||
+ | M$< | ||
+ | MVBD5.C34< | ||
+ | M; | ||
+ | MF084RZ9' | ||
+ | M4]# | ||
+ | MS3%', | ||
+ | MG=TW[0.G: | ||
+ | M+B1HG5:> | ||
+ | M@C11P, | ||
+ | M/ | ||
+ | M7WZ+> | ||
+ | M9TC: | ||
+ | M8: | ||
+ | MGZSDCSFJJ.' | ||
+ | MI4KK[]"& | ||
+ | MU`$' | ||
+ | M(I? | ||
+ | M+UE8U@-# | ||
+ | ML^F-UPP](N-D^ZCWE=> | ||
+ | MK: | ||
+ | M' | ||
+ | MQ\7M; | ||
+ | MV3)_1]V\YRTP^JAXKRPCI3GX5U@Z& | ||
+ | MR!; | ||
+ | M" | ||
+ | M8PKO_AA86)XW; | ||
+ | M$[& | ||
+ | MFI.B1K!\: | ||
+ | M6LC" | ||
+ | M_/ | ||
+ | M^.!WV7: | ||
+ | M8NP< | ||
+ | M6# | ||
+ | ME=VM>> | ||
+ | M2.D, | ||
+ | MDY]QG: | ||
+ | MRL< | ||
+ | M_\: | ||
+ | MV<#> | ||
+ | MKJ>, | ||
+ | MZZHQ.UH7: | ||
+ | M< | ||
+ | M> | ||
+ | M> | ||
+ | MQ37X_@RR+*# | ||
+ | M/ | ||
+ | MG![9P-H!V`%4OW[1FJ8P6# | ||
+ | MR7G$^5%5)& | ||
+ | M.Q\5AB" | ||
+ | MF-G_\F8; | ||
+ | MGIG0$5/ | ||
+ | M%M-R]\RZNFCS6\$/# | ||
+ | M\> | ||
+ | M4S.X39HL; | ||
+ | M/ | ||
+ | M0[J> | ||
+ | MT5#> | ||
+ | MXL+E@&? | ||
+ | M2' | ||
+ | M[G2[%+]> | ||
+ | M? | ||
+ | M1.LN*WR3J3%=@0R@8T5C]J%#< | ||
+ | M=ZN3H> | ||
+ | M7DFE76> | ||
+ | M3!OM2'> | ||
+ | MRKT)1%+3+_[A-*0!=Z/ | ||
+ | M' | ||
+ | MIN# | ||
+ | MC2B`*NP2" | ||
+ | MP7=W9U?? | ||
+ | MA'< | ||
+ | M& | ||
+ | M4X8# | ||
+ | M83# | ||
+ | MYH< | ||
+ | M7P# | ||
+ | MVO8C!DHJ!=D!# | ||
+ | M!BY0]2@U46O? | ||
+ | MUE8=N.06+)78> | ||
+ | MZ0SN)# | ||
+ | M; | ||
+ | MLS+? | ||
+ | M^U# | ||
+ | MM': | ||
+ | M4!RE; | ||
+ | M0":?> | ||
+ | M-OUW, | ||
+ | M& | ||
+ | M;; | ||
+ | MMJKM*.H\`CVA: | ||
+ | MRU]1?? | ||
+ | M]H^:; | ||
+ | M4M[[6P? | ||
+ | M, | ||
+ | M? | ||
+ | MV`)45M1G<; | ||
+ | MH/: | ||
+ | MN, | ||
+ | MS': | ||
+ | MY9O9O, | ||
+ | M7OW*[:< | ||
+ | ME6YJ^F=K' | ||
+ | M' | ||
+ | MOU*[PX^T& | ||
+ | M$7_D4Y*=[QU4$A" | ||
+ | M& | ||
+ | M, | ||
+ | M_^]# | ||
+ | M# | ||
+ | M9? | ||
+ | MK*< | ||
+ | M0D9& | ||
+ | MS5' | ||
+ | MMT9+.T=\R> | ||
+ | MD21O30H> | ||
+ | M' | ||
+ | MJI!&, | ||
+ | M? | ||
+ | M`3XW+AHA" | ||
+ | M```3``P``````````$" | ||
+ | M=3=!$W4W4$L!`A4# | ||
+ | M0*2!60$``$9, | ||
+ | M```(`.0VXR; | ||
+ | M<& | ||
+ | M# | ||
+ | M4$L!`A4# | ||
+ | M`' | ||
+ | $`0`````` | ||
+ | ` | ||
+ | end | ||
+ | ....... | ||
+ | .... | ||
+ | .. | ||
+ | . C=H #18 | ||
+ | |||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | Obj3d trilogy | ||
+ | ------------- | ||
+ | |||
+ | The rest of this issue is devoted to obj3d. | ||
+ | routines for creating and manipulating 3D worlds, and builds upon the | ||
+ | lib3d routines discussed in issue 16 (the lib3d routines have also been | ||
+ | upgraded, including a multicolor version). | ||
+ | |||
+ | The first article introduces the use of obj3d with a simple example | ||
+ | program which places two objects into the world (and lets you move one of | ||
+ | them around). | ||
+ | a memory map and a list of routines/ | ||
+ | covers " | ||
+ | game-type algorithms and ideas. | ||
+ | |||
+ | Mark Seelye, mseelye@yahoo.com, | ||
+ | editor", | ||
+ | Unfortunately it was not quite finished at the time of this issue, but | ||
+ | hopefully it will be featured in the next issue. | ||
+ | contact Mark! | ||
+ | |||
+ | All files are in a .zip files, included at the end of this | ||
+ | issue: | ||
+ | |||
+ | obj3d.o -- Obj3d library | ||
+ | lib3dv2.o -- v2.0 of lib3d library | ||
+ | lib3dv2mc.o -- Multicolor version | ||
+ | table13 -- $C000 table | ||
+ | rotmat.o -- $5E00 table | ||
+ | ptabgen -- program used to create above tables | ||
+ | |||
+ | bigtets.b.o -- Simple example program | ||
+ | bigtets.b.s -- Source code, in El Cheapo Assembler format | ||
+ | (see the Fridge for Cheapass docs) | ||
+ | |||
+ | stroids.o -- Stroids | ||
+ | stroids.n.s -- Source code for stroids, cheapass format | ||
+ | |||
+ | loader -- Program to load in relevant files | ||
+ | |||
+ | stroids -- Single-file stroids executable (load and run) | ||
+ | |||
+ | obj3d.ref -- obj3d programmer' | ||
+ | lib3dv2.ref -- lib3d v2.0 programmer' | ||
+ | |||
+ | Binaries, source code, and documentation are also available in the | ||
+ | Fridge, at | ||
+ | |||
+ | http:// | ||
+ | |||
+ | |||
+ | |||
+ | |||
+ | Obj3d -- The 3D object library | ||
+ | ---------------------------------> | ||
+ | |||
+ | Obj3d is a library of routines for organizing, manipulating, | ||
+ | and rendering 3d objects in both hires and multicolor mode, using | ||
+ | version 2.0 of the lib3d library routines (described a little later). | ||
+ | Let's just dive right in and look at some example code, to get a feel for | ||
+ | the routines, and just how easy it is to get a 3D object up on the screen. | ||
+ | This example is taken from the example program " | ||
+ | the obj3d.zip archive in this issue. | ||
+ | |||
+ | * | ||
+ | * Test code | ||
+ | * | ||
+ | * Nice pair of tets | ||
+ | * | ||
+ | |||
+ | ORG $1000 | ||
+ | |||
+ | OBS = $0800 ;Object records | ||
+ | GETIN = $FFE4 | ||
+ | |||
+ | TestCode | ||
+ | LDA $D011 | ||
+ | ORA #$20 ; | ||
+ | STA $D011 | ||
+ | LDA #$08 ; | ||
+ | STA $D018 | ||
+ | LDA $DD00 | ||
+ | AND #$F8 | ||
+ | ORA #$02 ;Bank 1 | ||
+ | STA $DD00 | ||
+ | LDA #$80 | ||
+ | STA $028A ;All keys repeat | ||
+ | |||
+ | JSR VERSION | ||
+ | BPL :hires | ||
+ | LDA $D016 | ||
+ | ORA #$10 | ||
+ | STA $D016 | ||
+ | : | ||
+ | JSR ClrColMap | ||
+ | |||
+ | LDA #< | ||
+ | LDY #>OBS | ||
+ | JSR Init3D | ||
+ | |||
+ | LDA #< | ||
+ | LDY #>TETDAT | ||
+ | LDX #01 ;ID | ||
+ | JSR AddObj | ||
+ | STX VOB ;View object | ||
+ | |||
+ | LDA #< | ||
+ | LDY #> | ||
+ | LDX #02 | ||
+ | JSR AddObj | ||
+ | STX ROB ;Remember this object for later | ||
+ | STA POINT ;Object pointer | ||
+ | STY POINT+1 | ||
+ | LDY #5 ;Set object center | ||
+ | :l1 LDA OCEN,Y | ||
+ | STA (POINT),Y | ||
+ | DEY | ||
+ | BPL :l1 | ||
+ | |||
+ | :sec | ||
+ | :setp LDX #$60 ; | ||
+ | LDA #< | ||
+ | LDY #>PATS | ||
+ | JSR SetParms | ||
+ | :loop | ||
+ | JSR ClrBitmap | ||
+ | |||
+ | LDX VOB ;Calculate view | ||
+ | JSR CalcView | ||
+ | JSR SortVis | ||
+ | JSR DrawAllVis | ||
+ | ... | ||
+ | |||
+ | Aside from the trivial screen clearing routine and some data tables, that's | ||
+ | the entire program -- initialize the 64 and the libraries, add some objects, | ||
+ | then call the three subroutines above to render the objects to the screen. | ||
+ | Is that easy or what? | ||
+ | Now let's have a more detailed look at the library, and the example | ||
+ | program above. | ||
+ | |||
+ | Obj3d overview | ||
+ | ============== | ||
+ | |||
+ | Lib3d, as given in C=Hacking #16, is a set of core routines for | ||
+ | doing 3D graphics and calculations. | ||
+ | is a set of routines which uses lib3d. | ||
+ | out of managing and rendering 3d objects, and make 3d graphics accessible | ||
+ | to all those guys who never paid attention during high school math class. | ||
+ | |||
+ | I'd like to offer a bit of advice to any of those guys who might | ||
+ | happen to be reading, though. | ||
+ | for example a game, you're going to need to understand some fundamental | ||
+ | things about 3D graphics, and about why the various library routines work | ||
+ | the way they do. Amazingly enough, C=Hacking has a series of articles on | ||
+ | 3D graphics, with a very comprehensive article in issue #16. I read it, | ||
+ | several times, before doing obj3d, and suggest you do the same. | ||
+ | Knowing coders the way I do, I can see that you're going to ignore | ||
+ | my advice; that's fine. For now, you can just jump in and play around with | ||
+ | the example programs a little bit, to get a feel for things. | ||
+ | later decide to attempt something more involved than displaying objects | ||
+ | on the screen, and are unwilling to take a little time to understand how | ||
+ | things work, then you should expect to fail miserably, or spend many | ||
+ | frustrating hours wondering why something isn't working the way you expect. | ||
+ | Therefore, before you begin work on your dream program, I suggest | ||
+ | spending an hour or so with C=Hacking #16, a pencil, a piece of paper, | ||
+ | and a brain. | ||
+ | do some thinking; if you're expecting to understand things with the flick | ||
+ | of some magical switch, you're probably reading the wrong magazine. | ||
+ | |||
+ | Finally, a word on distribution: | ||
+ | distributable. | ||
+ | You can make money off those programs (good luck!). | ||
+ | to give me credit, if you really don't want to, as I figure anyone will | ||
+ | spot lib3d a mile away. But please, *use* the thing! | ||
+ | wrote it! | ||
+ | And, knowing coders the way I do, if I hear one guy sniff and | ||
+ | loudly exclaim to everyone within earshot that he never uses other people' | ||
+ | code, I'm going to take your tunes, charsets, sprites, editors, assemblers, | ||
+ | and all that hardware you didn't design, and bonk you over the head with it. | ||
+ | |||
+ | Alright, then. Let's talk some code. | ||
+ | |||
+ | Organization | ||
+ | ------------ | ||
+ | |||
+ | The obj3d routines can essentially be split into three groups: | ||
+ | routines to manage objects globally, routines to manipulate individual | ||
+ | objects, and visualization/ | ||
+ | The global management routines are for adding/ | ||
+ | to/from the world, setting up objects for use by other routines, and | ||
+ | retrieving information about objects (e.g. location) for use by the | ||
+ | programmer. | ||
+ | The manipulation routines are used to manipulate individual objects: | ||
+ | to move objects forwards and backwards and side to side, to rotate objects | ||
+ | about their roll, pitch, or yaw axis, etc. In other words, these are | ||
+ | routines to change the position and orientation of a given object within | ||
+ | the world. | ||
+ | The visualization routines are naturally used to visualize the world. | ||
+ | These are routines to compute the view from a specific object and render | ||
+ | that view to the screen, as well as how to render it -- what bitmap, | ||
+ | render it solid or wireframe, etc. There are also routines for drawing | ||
+ | individual objects and even individual faces. | ||
+ | interface into the 3d library. | ||
+ | |||
+ | In order to support the obj3d routines, lib3d has been upgraded | ||
+ | to version 2.0. The rotation routines have been changed to ease a number | ||
+ | of calculations. | ||
+ | and lines, and for determining which version of the library is in memory. | ||
+ | Finally, a multicolor version of the library is available, in addition | ||
+ | to the hires version, which includes screen aspect ratio coordinate- | ||
+ | corrections as well as multicolor rendering routines. | ||
+ | |||
+ | Let's now return to the example program. | ||
+ | |||
+ | Example program explained | ||
+ | ------------------------- | ||
+ | |||
+ | The first part of the code simply sets up 64-related stuff, | ||
+ | turning on bitmap mode, etc. JSR VERSION is used to determine whether | ||
+ | the hires or multicolor version of lib3d is being used; if multicolor, | ||
+ | then multicolor mode is enabled: | ||
+ | |||
+ | * | ||
+ | * Test code | ||
+ | * | ||
+ | * Nice pair of tets | ||
+ | * | ||
+ | |||
+ | OBS = $0800 ;Object records | ||
+ | GETIN = $FFE4 | ||
+ | |||
+ | TestCode | ||
+ | LDA $D011 | ||
+ | ORA #$20 ; | ||
+ | STA $D011 | ||
+ | LDA #$08 ; | ||
+ | STA $D018 | ||
+ | LDA $DD00 | ||
+ | AND #$F8 | ||
+ | ORA #$02 ;Bank 1 | ||
+ | STA $DD00 | ||
+ | LDA #$80 | ||
+ | STA $028A ;All keys repeat | ||
+ | |||
+ | JSR VERSION | ||
+ | BPL :hires | ||
+ | LDA $D016 | ||
+ | ORA #$10 | ||
+ | STA $D016 | ||
+ | : | ||
+ | JSR ClrColMap | ||
+ | |||
+ | ClrColMap is not part of the obj3d library; it's just a simple routine | ||
+ | to clear the color map. The next piece of code initializes the obj3d | ||
+ | and lib3d libraries with some default values: | ||
+ | |||
+ | LDA #< | ||
+ | LDY #>OBS | ||
+ | JSR Init3D | ||
+ | |||
+ | If you ever move some tables around (like the $C000 table), you'll need | ||
+ | to change table pointers _after_ calling this routine, as it initializes | ||
+ | the zero-page lib3d pointers to certain default values. | ||
+ | free area of memory, whose size depends on the number of active objects | ||
+ | you intend to have in the world. | ||
+ | shortly. | ||
+ | JSR AddObj: | ||
+ | |||
+ | LDA #< | ||
+ | LDY #>TETDAT | ||
+ | LDX #01 ;ID | ||
+ | JSR AddObj | ||
+ | STX VOB ;View object | ||
+ | |||
+ | If you look at the obj3d memory map, you'll notice that of the 4k it | ||
+ | occupies only a measly 1.5k is used for code. The rest is all used for | ||
+ | storage -- a whole bunch of lists, used to organize all the data. One | ||
+ | of these lists is the " | ||
+ | to find the first empty spot in the active object list, and assign the | ||
+ | object to that spot. This spot is returned in .X, and this number is | ||
+ | how obj3d will reference the object. | ||
+ | use by the program; you do not generally need to store every object | ||
+ | number. | ||
+ | A maximum of 128 active objects are allowed, so the object number | ||
+ | in .X will always be in the range 0-127. | ||
+ | first open spot, .X will in fact never be larger than the maximum number | ||
+ | of active objects in a given program. | ||
+ | own purposes, too, for example as an index into a list of velocities. | ||
+ | AddObj also " | ||
+ | record in. This area of memory is specified in the earlier call to | ||
+ | JSR Init3D, above, by .AY. Each object currently requires 32 bytes of | ||
+ | storage, so if there can be N active objects in the world at any given | ||
+ | time, this area of memory needs to be of at least size 32*N. There is | ||
+ | a distinction here between " | ||
+ | " | ||
+ | the world. | ||
+ | program like Elite -- although there are many different kinds of objects | ||
+ | (different ships, asteroids, etc.), there are never more than a certain | ||
+ | number present, i.e. active, at any given time. So even if you have | ||
+ | 50 different types of objects, if there are never more than 6 active at | ||
+ | any time only 6*32 bytes of RAM are needed. | ||
+ | So what is an " | ||
+ | completely specify an object: its position, its orientation, | ||
+ | structure. | ||
+ | the object looks like. The position and orientation determine where the | ||
+ | object is, and in what direction it is pointing. | ||
+ | are such important concepts that it is worthwhile to review them further. | ||
+ | Consider something like a chessboard. | ||
+ | a specific square, and when you move it, the piece moves to a different | ||
+ | square (as opposed to e.g. moving the board). | ||
+ | is its position, and in chess this is denoted by C-4 or something | ||
+ | similar. | ||
+ | see? It would depend on the direction we were facing; in other words, | ||
+ | our orientation. | ||
+ | knight, we would need to know its orientation. | ||
+ | In three dimensions, the position is specified by three signed | ||
+ | 16-bit coordinates, | ||
+ | is specified by a 3x3 rotation matrix. | ||
+ | tells you which direction is " | ||
+ | which direction is " | ||
+ | if you're flying a plane, the " | ||
+ | ahead. | ||
+ | changes if the plane turns, or dives, etc. | ||
+ | Thus, in obj3d, an object is defined by a 32-byte " | ||
+ | |||
+ | Object: | ||
+ | CenterX | ||
+ | CenterY | ||
+ | CenterZ | ||
+ | Structure | ||
+ | ID 1 byte Optional ID byte | ||
+ | User byte 1 byte Free data byte | ||
+ | CenterPos | ||
+ | CenXRem | ||
+ | CenYRem | ||
+ | CenZRem | ||
+ | Matrix | ||
+ | MatRem | ||
+ | |||
+ | CenterX/Y/Z are the 16-bit signed coordinates specifying where the object | ||
+ | is located in the world. | ||
+ | defining the vertices and faces which make up the object. | ||
+ | means that objects may share the same basic shape, without wasting lots | ||
+ | of memory; it also means that objects may be e.g. animated (beyond merely | ||
+ | rotating). | ||
+ | The structure layout is discussed a little later. | ||
+ | The ID and user bytes are purely for the use of the programmer; they | ||
+ | are *not* used by the library routines. | ||
+ | type of object this is, and its current velocity. | ||
+ | the content of .X is stored in ID. | ||
+ | CenterPos is an index into a list of relative object centers; this | ||
+ | list is generated when the viewpoint is calculated from a specific object. | ||
+ | CenX/ | ||
+ | represent the fractional portion of the center coordinates, | ||
+ | accurate movement (especially when moving by small amounts). | ||
+ | Matrix and Matrem are the " | ||
+ | is the transpose of the orientation matrix. | ||
+ | -64 to 64. The remainder portion is used by the TurnLeft etc. routines | ||
+ | (in particular, by the lib3d routines ACCROTX/ | ||
+ | portion is used for rotation/ | ||
+ | |||
+ | The viewpoint matrix determines what the world looks like when | ||
+ | rotated about the object. | ||
+ | looks from somewhere within the world, i.e. what direction it is pointing in, | ||
+ | etc. It's the difference between being " | ||
+ | the object. | ||
+ | Computing views was explained in C=Hacking #16. Briefly, when | ||
+ | an airplane e.g. rolls, it rolls about its fuselage -- about a local | ||
+ | coordinate axis -- and when it rolls, the other coordinate axis change | ||
+ | direction, relative to the world (e.g. the wings move). | ||
+ | the usual rotation matrix really only rotates about a _fixed_ coordinate | ||
+ | system. | ||
+ | need a way of rotating about the new coordinate axis, i.e. about an arbitrary | ||
+ | line. It can be done, using quaternions (which are a generalization | ||
+ | of complex numbers), but it is cumbersome and time-consuming (and thus | ||
+ | popular among PC programmers). | ||
+ | the inverse of a " | ||
+ | further realize that the inverse of any rotation matrix is simply its | ||
+ | transpose. | ||
+ | |||
+ | By the way, it should go without saying that the object record values | ||
+ | need to not get screwed up. If they do, all sorts of strange things can | ||
+ | happen. | ||
+ | mysteriously, | ||
+ | |||
+ | The bytes following the user bytes will probably never be used by | ||
+ | a programmer, except perhaps to copy orientation matrices. | ||
+ | up through the user bytes, however, are meant to be modified by the | ||
+ | programmer. | ||
+ | the center of the object, after AddObj. | ||
+ | |||
+ | LDA #< | ||
+ | LDY #> | ||
+ | LDX #02 | ||
+ | JSR AddObj | ||
+ | STX ROB ;Rotate object | ||
+ | STA POINT ;Object pointer | ||
+ | STY POINT+1 | ||
+ | LDY #5 ;Set center | ||
+ | :l1 LDA OCEN,Y | ||
+ | STA (POINT),Y | ||
+ | DEY | ||
+ | BPL :l1 | ||
+ | |||
+ | Not only does AddObj return the object number in .X, it returns a | ||
+ | pointer to the object in .AY. This pointer is also returned by the | ||
+ | routines SetCurOb and GetCurOb. | ||
+ | in POINT, and then sets the location by copying from a little table | ||
+ | |||
+ | OCEN DA 00 ;X-coord | ||
+ | DA 00 ;Y-coord | ||
+ | DA $0100 ; | ||
+ | |||
+ | At the very least, you will probably have to specify the location of | ||
+ | any object you create; otherwise, the default location is the center | ||
+ | of the world, at (0,0,0). | ||
+ | |||
+ | Next, the example program tells obj3d about how objects should | ||
+ | be rendered to the screen: | ||
+ | |||
+ | :sec | ||
+ | :setp LDX #$60 ; | ||
+ | LDA #< | ||
+ | LDY #>PATS | ||
+ | JSR SetParms | ||
+ | |||
+ | SetParms takes four parameters. | ||
+ | or wireframe polygons will be used. (The wireframe routine is not | ||
+ | particularly fast, because it is not particularly smart -- it does not | ||
+ | compute hidden faces. | ||
+ | drawn.) | ||
+ | to. .AY contains a pointer to a table of 8x8 bit patterns, used by the | ||
+ | rendering routines: | ||
+ | |||
+ | PATS ;Pattern table | ||
+ | SOLID = 0 | ||
+ | HEX FFFFFFFFFFFFFFFF | ||
+ | DITHER1 | ||
+ | HEX 55AA55AA55AA55AA | ||
+ | DITHER2 | ||
+ | HEX AA55AA55AA55AA55 | ||
+ | ... | ||
+ | |||
+ | These patterns will later be referenced by their position in the table. | ||
+ | |||
+ | Thus do we come to the main loop, which is just four routines: | ||
+ | :loop | ||
+ | JSR ClrBitmap | ||
+ | |||
+ | LDX VOB ;Calculate view | ||
+ | JSR CalcView | ||
+ | JSR SortVis | ||
+ | JSR DrawAllVis | ||
+ | |||
+ | The first routine is not a part of obj3d; it just clears the bitmap. | ||
+ | JSR CalcView computes what the world looks like from object .X -- from | ||
+ | the object' | ||
+ | and rotating each object' | ||
+ | and storing this relative center in the center list; the value of | ||
+ | CenterPos, in the object record, is an index into this list. | ||
+ | JSR SortVis computes a sorted list of _visible_ objects, | ||
+ | from the center list. An object is considered " | ||
+ | in a 45 degree pyramid in front of the viewpoint object, and if | ||
+ | its z-coordinate is between zmin and zmax. The paramters zmin and zmax | ||
+ | may be set by calling JSR SetVisParms; | ||
+ | offhand -- probably 100 < CenterZ < 8192. The purpose of the sorted | ||
+ | list is to ensure the objects are drawn from back to front -- so that | ||
+ | objects overlap each other correctly. | ||
+ | Finally, after calculating the viewpoint, and calculating the | ||
+ | visible objects, all that remains is to draw all the visible objects, | ||
+ | with JSR DrawAllVis. | ||
+ | with JSR DrawNextVis. | ||
+ | orientation and viewpoint matrices, then added to its relative center | ||
+ | (M R P + M C), then projected, with the projected points stored in two | ||
+ | lists, PLISTX and PLISTY. | ||
+ | called, using the points in these lists. | ||
+ | rotated z-coordinates are stored in PLISTZ, for use in drawing compound | ||
+ | objects (described later). | ||
+ | |||
+ | That takes care of displaying an object. | ||
+ | an object, i.e. moving around? | ||
+ | keys: | ||
+ | |||
+ | a/z s/d q/w -- Pitch, roll, and yaw object | ||
+ | @ / -- Move forwards/ | ||
+ | space -- Switch viewpoints | ||
+ | = -- Draw using wireframe graphics | ||
+ | * -- Draw using solid graphics | ||
+ | |||
+ | You can look at the source code to see how all of this is done. Here, | ||
+ | it will be enough to look at the code to pitch the object and the | ||
+ | code to move the object (and to swap viewpoints, for good measure). | ||
+ | First, the library must be told which object to operate on: | ||
+ | |||
+ | LDX ROB ;Set rotation object | ||
+ | JSR SetCurOb | ||
+ | |||
+ | Recall that after adding in the second object, .X was stored in ROB. | ||
+ | In this program, movement always affects the second object, no matter | ||
+ | which object is the view object. | ||
+ | and branches accordingly: | ||
+ | |||
+ | :wait JSR GETIN | ||
+ | BEQ :wait | ||
+ | CMP #' | ||
+ | BEQ :pitchdn | ||
+ | CMP #' | ||
+ | BEQ :pitchup | ||
+ | CMP #' | ||
+ | BEQ :movf | ||
+ | CMP #'/' | ||
+ | BEQ :movb | ||
+ | CMP #' ' | ||
+ | BNE :wait | ||
+ | |||
+ | LDX VOB ;Swap viewpoint | ||
+ | JSR SetCurOb | ||
+ | JSR GetNextOb | ||
+ | STX VOB | ||
+ | JMP :loop | ||
+ | |||
+ | :pitchdn CLC | ||
+ | DFB $24 | ||
+ | :pitchup SEC | ||
+ | JSR Pitch | ||
+ | JMP :loop | ||
+ | |||
+ | :movf LDA #$07 | ||
+ | DFB $2C | ||
+ | :movb LDA #$F9 | ||
+ | JSR MoveForwards | ||
+ | JMP :loop | ||
+ | |||
+ | To change the viewpoint, the program simply sets the current object | ||
+ | to the current view object, then advances to the next object in the | ||
+ | active object list, and stores that as the new viewpoint object. | ||
+ | The routines Pitch, Roll, and Yaw all function similarly. | ||
+ | all rotate by a fixed amount (2*pi/128 radians, which is about 3 degrees), | ||
+ | and the carry flag specifies the direction of rotation. | ||
+ | is performed about the _local_ coordinate axis of the object, which rotates | ||
+ | with the object; there is also a routine, SetMat, which can rotate about | ||
+ | the fixed world axis. | ||
+ | The MoveForwards, | ||
+ | similarly. | ||
+ | the move, as a signed 8-bit number; the actual distance moved is four | ||
+ | times .A. | ||
+ | |||
+ | And that more or less sums up the less-technical aspects of the obj3d | ||
+ | library routines. | ||
+ | and a memory map, see the files " | ||
+ | archive (they' | ||
+ | code. And now it is time for the more technical details: object structure | ||
+ | data, compound objects, some internals, and lib3d v2.0 updates. | ||
+ | |||
+ | The More Techincal Details | ||
+ | ========================== | ||
+ | |||
+ | As you may recall, the object record contains a pointer to the | ||
+ | object structure data -- the data describing the points and faces that | ||
+ | define what the object looks like. For a normal object, this data is | ||
+ | organized as follows: | ||
+ | |||
+ | Structure: | ||
+ | TypeID = 0 1 byte Object type (normal, compound) | ||
+ | Npoints | ||
+ | Nfaces | ||
+ | Xcoords | ||
+ | Ycoords | ||
+ | Zcoords | ||
+ | |||
+ | faces: | ||
+ | |||
+ | nverts | ||
+ | fillpat | ||
+ | fpoints | ||
+ | |||
+ | TypeID identifies the object type, and will be discussed in more detail | ||
+ | shortly. | ||
+ | number of faces, but an object may not have more than 128 points (which | ||
+ | is a HUGE object!). | ||
+ | X/ | ||
+ | described in C=Hacking #16. Note that the _length_ of each of these points | ||
+ | must be less than 95. For example, a point like (65,65,65) has length | ||
+ | sqrt(65^2 + 65^2 + 65^2) = 65*sqrt(3) = 112 or so, so if this point | ||
+ | were rotated down onto the x-axis it would have coordinates (112,0,0), | ||
+ | which is out of the -95..95 range. | ||
+ | rotating objects if you make this error, so if your objects start freaking | ||
+ | out upon rotation, check this first. | ||
+ | Note, moreover, that the vertices are defined about the center of | ||
+ | rotation of the object; this center is exactly the coordinates CenterX/Y/Z | ||
+ | in the object record, i.e. where the object is located in the world. | ||
+ | Following the coordinates is a list of faces, which are specified | ||
+ | by a fill pattern and a list of vertices. | ||
+ | into the pattern table, which you may recall is set using JSR SetParms; | ||
+ | the actual location of the pattern is PATTAB + 8*fillpat. | ||
+ | is simply a list of indices into the X/Y/Zcoords table; moreover, this | ||
+ | list must close upon itself, e.g. 0-1-2-0, so that it is of length nverts+1, | ||
+ | where nverts is the number vertices in the face. | ||
+ | With all that in mind, here is the data for the first object in | ||
+ | the example program, a simple tetrahedron: | ||
+ | |||
+ | * | ||
+ | * Test object 1: simple tetrahedron | ||
+ | * | ||
+ | TETDAT | ||
+ | DFB 0 ;Normal object | ||
+ | DFB 4 ;Number of points | ||
+ | DFB 4 ;Number of faces | ||
+ | |||
+ | * Point list | ||
+ | |||
+ | TETX DFB 45, | ||
+ | TETY DFB 45, | ||
+ | TETZ DFB 45, | ||
+ | |||
+ | * Face list | ||
+ | |||
+ | FACE1 DFB 3 ;Number of vertices | ||
+ | DFB SOLID ;Fill pattern | ||
+ | DFB 0, | ||
+ | |||
+ | FACE2 DFB 3 | ||
+ | DFB ZIGS | ||
+ | DFB 3,2,1,3 | ||
+ | |||
+ | FACE3 DFB 3 | ||
+ | DFB CROSSSM | ||
+ | DFB 3,0,2,3 | ||
+ | |||
+ | FACE4 DFB 3 | ||
+ | DFB HOLES | ||
+ | DFB 3,1,0,3 | ||
+ | |||
+ | Pretty straightforward, | ||
+ | points in them; triangles will have three points, squares (e.g. sides of | ||
+ | cubes) will have four points, and so on. Thus the way to think about | ||
+ | creating an object is that some points are defined about some center, and | ||
+ | those points are then connected together to make faces. | ||
+ | This kind of definition works fine for simple objects -- that is, | ||
+ | for convex objects, where one part of the object won't obscure a different | ||
+ | part. But what about more complicated objects -- a tank, say, or a cross -- | ||
+ | where one piece of the object might be in front of another piece at one | ||
+ | orientation, | ||
+ | of polygon clipping is fairly complicated and time consuming. | ||
+ | programmers, | ||
+ | and we can make suitably complicated objects by just modifying the above | ||
+ | structure a little bit. | ||
+ | Consider for a moment a cross, or the pointy stars from Cool World. | ||
+ | The basic problem is that a given chunk of the object might be either | ||
+ | in front of the rest of the object or behind it. If it is behind, | ||
+ | then we want to draw it first; if it's in front, then we want to draw | ||
+ | it last. The idea, then, is to divide a complex object up into a number | ||
+ | of smaller objects, and to draw them in the right order. | ||
+ | lexicon, such an object is a _compound object_. | ||
+ | |||
+ | Compound Objects | ||
+ | ---------------- | ||
+ | |||
+ | To define a " | ||
+ | join points into faces. | ||
+ | joining faces into smaller " | ||
+ | what order to draw each oblet in. | ||
+ | Recall that before rendering objects to the screen, JSR SortVis | ||
+ | is called. | ||
+ | and far-away objects are drawn first, so that objects will be rendered | ||
+ | in front of one another on the screen. | ||
+ | with the oblets: associate a _reference point_ with each oblet, depth-sort | ||
+ | the reference points, and then draw the oblets from back to front. | ||
+ | reference point doesn' | ||
+ | any face -- just some point which allows the oblets to be sorted correctly. | ||
+ | As an example, consider the pointy stars from Cool World. | ||
+ | are easy to create, by starting with a cube, and then pulling out the | ||
+ | centers of each face, creating a star with six tines. | ||
+ | tine are a convenient reference point, so the idea is to divide the | ||
+ | star up into six oblets -- the tines -- and use the tip of the tine as | ||
+ | the reference point for each oblet. | ||
+ | may be drawn in the correct order, and viola! | ||
+ | clipping, with very little computational effort. | ||
+ | success with this method is choosing the reference points well. | ||
+ | The format for a compound object is similar to a normal object, | ||
+ | with TypeID set to $80 and Nfaces replaced by Noblets: | ||
+ | |||
+ | Structure: | ||
+ | TypeID = $80 1 byte Object type (normal, compound) | ||
+ | Npoints | ||
+ | Noblets | ||
+ | Xcoords | ||
+ | Ycoords | ||
+ | Zcoords | ||
+ | |||
+ | oblets: | ||
+ | ref points p bytes List of reference points (indices) | ||
+ | |||
+ | oblet 1 | ||
+ | nbytes 1 byte Number of bytes in this oblet | ||
+ | nfaces 1 byte Number of faces | ||
+ | |||
+ | face list: | ||
+ | nverts | ||
+ | fillpat 1 byte Fill pattern (index into pattern list) | ||
+ | fpoints m+1 bytes | ||
+ | |||
+ | oblet 2 | ||
+ | ... | ||
+ | |||
+ | The list of reference points is a list of indices into X/ | ||
+ | and the first point in the list is the reference point for the first oblet, | ||
+ | etc. Note the " | ||
+ | through the oblet list (for example, to find oblet #4 quickly). | ||
+ | is a limit of 32 or so oblets allowed; chances are awfully good you'll | ||
+ | never reach this limit, consdering that an object can only have 128 points! | ||
+ | |||
+ | Here is the second object from the example program, which is just | ||
+ | a modified pointy star. Note that the TypeID is set to $80; a normal | ||
+ | object has TypeID = 0. It is also worth pointing out that the choice | ||
+ | of reference points -- the tip of each surface -- can lead to incorrect | ||
+ | clipping on occasion, because the tips are of different lengths; nevertheless, | ||
+ | they work quite well. | ||
+ | |||
+ | * | ||
+ | * Test object 2: a compound object, | ||
+ | * spaceship-kind of thing (essentially | ||
+ | * cool world stars) | ||
+ | * | ||
+ | STARDAT | ||
+ | |||
+ | DFB $80 ;Compound object | ||
+ | DFB 14 ; | ||
+ | DFB 6 ;Number of oblets | ||
+ | |||
+ | * Point list | ||
+ | |||
+ | DFB 50, | ||
+ | DFB 0, | ||
+ | DFB 0, | ||
+ | |||
+ | * Oblet list: reference points | ||
+ | |||
+ | DFB 0 ;First 6 points | ||
+ | DFB 1 | ||
+ | DFB 2 | ||
+ | DFB 3 | ||
+ | DFB 4 | ||
+ | DFB 5 | ||
+ | |||
+ | * Oblet 1 | ||
+ | |||
+ | DFB 26 ;26 bytes | ||
+ | DFB 4 ;4 faces | ||
+ | |||
+ | * faces | ||
+ | DFB 3 ;4 points | ||
+ | DFB SOLID ;pattern | ||
+ | DFB 0, | ||
+ | |||
+ | DFB 3 | ||
+ | DFB ZIGS | ||
+ | DFB 0,7,6,0 | ||
+ | |||
+ | DFB 3 | ||
+ | DFB ZAGS | ||
+ | DFB 0,6,9,0 | ||
+ | |||
+ | DFB 3 | ||
+ | DFB DITHER1 | ||
+ | DFB 0,9,8,0 | ||
+ | |||
+ | * Oblet 2 | ||
+ | |||
+ | DFB 26 ;26 bytes | ||
+ | DFB 4 ;4 faces | ||
+ | |||
+ | |||
+ | DFB 3 ;4 points | ||
+ | DFB ZIGS | ||
+ | DFB 1,11,12,1 | ||
+ | |||
+ | DFB 3 | ||
+ | DFB BRICK | ||
+ | DFB 1,12,13,1 | ||
+ | |||
+ | DFB 3 | ||
+ | DFB DITHER2 | ||
+ | DFB 1,13,10,1 | ||
+ | |||
+ | DFB 3 | ||
+ | DFB ZAGS | ||
+ | DFB 1,10,11,1 | ||
+ | |||
+ | * Oblet 3 | ||
+ | ... | ||
+ | |||
+ | The remaining oblets are all similar. | ||
+ | |||
+ | lib3d v2.0 | ||
+ | ---------- | ||
+ | |||
+ | Finally, lib3d has been updated, to support the obj3d library and | ||
+ | to be more cool in general. | ||
+ | |||
+ | The first thing to notice is that there are now two libraries: | ||
+ | one for hires, and another for multicolor. | ||
+ | only renders in multicolor, but corrects for the screen aspect ratio | ||
+ | by multiplying all y-coordinates by 5/4 after projection. | ||
+ | |||
+ | The second thing to notice is the addition of three new routines: | ||
+ | PLOT, DRAWLINE, and VERSION. | ||
+ | the high bit set indicates a multicolor version. | ||
+ | at v2.0, so this routine returns either 2 or $82. | ||
+ | PLOT and DRAWLINE are used to, well, plot points and draw lines. | ||
+ | The coordinates are 16-bit signed values, i.e. the routines understand | ||
+ | points that are off the screen. | ||
+ | in zero page and JSR. The line drawing routine is not the world' | ||
+ | but it is still zippy, especially for its (quite small) size and flexibility. | ||
+ | |||
+ | The third thing to notice is that the memory map has been altered | ||
+ | significantly, | ||
+ | instead of $8600, so not only has the jump table moved but there is | ||
+ | no longer room for sprites in bank 2. Zero page has been shuffled around, | ||
+ | with perhaps the most significant result being that all of the $Fx locations | ||
+ | (everything above $C4 actually) are free for use by the programmer. | ||
+ | the memory map for more details. | ||
+ | |||
+ | What you might not have noticed is that several of the routines | ||
+ | have now changed. | ||
+ | pointer to the matrix to be accumulated; | ||
+ | a matrix in zero page. This way, object matrices may be directly | ||
+ | manipulated, | ||
+ | ROTPROJ has also gone through a big change. | ||
+ | equation for a 3D world is | ||
+ | |||
+ | M R P + M C | ||
+ | |||
+ | where P is an object point, R is the local rotation (orientation), | ||
+ | the viewpoint rotation, and C is the object' | ||
+ | just calculate MP + MC; it now calculates MRP + MC, which is why zero | ||
+ | page now contains a viewpoint _and_ an orientation matrix. | ||
+ | stores the z-coordinates before projecting, for use in drawing compound | ||
+ | objects, so there' | ||
+ | without projection is now a pretty useless feature; I left it in, but | ||
+ | it now stores the rotated points to PLISTX/Y/Z, instead of using a special | ||
+ | zero-page pointer for the purpose. | ||
+ | |||
+ | There were also some miscellaneous recodings, to conserve memory | ||
+ | and such, but they' | ||
+ | individual routines and their use, see the lib3d.ref document in the | ||
+ | .zip file. | ||
+ | |||
+ | The End | ||
+ | ------- | ||
+ | |||
+ | Since most of the obj3d routines are either really stupid or were | ||
+ | yanked out of Cool World, I don't think there' | ||
+ | (except the cool sorting algorithm, discussed elsewhere in this issue). | ||
+ | |||
+ | What more is there to say? | ||
+ | |||
+ | Go home and code! | ||
+ | |||
+ | ....... | ||
+ | .... | ||
+ | .. | ||
+ | . C=H #18 | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | Obj3d programmer' | ||
+ | ----- | ||
+ | last update: 4/16/99 | ||
+ | version 2.0 | ||
+ | |||
+ | |||
+ | * Obj3d and lib3d memory map: | ||
+ | |||
+ | |-|x|--|x|---------------|xxxx|------|xxxxxx|------|xxxx|-------------------| | ||
+ | | | | ||
+ | $41 $0200 | ||
+ | |||
+ | |||
+ | $41-$49 | ||
+ | $4A-$52 | ||
+ | $53, | ||
+ | $55-$72 | ||
+ | $60-$67 X1, | ||
+ | $8B-$8E PLISTZLO, | ||
+ | $A3-$AE | ||
+ | centers. | ||
+ | $AF-$B0 | ||
+ | $B1-$B8 | ||
+ | $B9 | ||
+ | $BB-$BC | ||
+ | $BD-$C4 | ||
+ | (PLISTZLO = $8B, above) | ||
+ | |||
+ | $0200 Point queue | ||
+ | $4F00 LINELO -- Record drawn lines in wireframe | ||
+ | $4F68 LINEHI | ||
+ | $4FD0 OBLETS -- Sorted oblet list | ||
+ | $5000 obj3d | ||
+ | $5600-$5D00 | ||
+ | $5600 OBJLO | ||
+ | $5680 OBJHI | ||
+ | $5700 VISOBJ -- Visible object list | ||
+ | $5781 OBCEN | ||
+ | $5800 CX - Translated rotated centers | ||
+ | $5880 HCX - (high byte) | ||
+ | $5900 CY | ||
+ | $5980 HCY | ||
+ | $5A00 CZ | ||
+ | $5A80 HCZ | ||
+ | $5B00 PLISTX - Point list (projected) | ||
+ | $5B80 (high bytes) | ||
+ | $5C00 PLISTY | ||
+ | $5C80 (high byte PLISTY) | ||
+ | $5D00 PLISTZ | ||
+ | $5D80 high byte | ||
+ | $5E00-$5FFF | ||
+ | $8400-$9FFF | ||
+ | $C000-$C2FF | ||
+ | $C300-$C5FF | ||
+ | $C600-$CFFF | ||
+ | |||
+ | ---------------- | ||
+ | |||
+ | So, to put it another way, free areas of RAM are: | ||
+ | |||
+ | $02-$40 | ||
+ | $55-$72 (temporary only; library work variables) | ||
+ | $90-$A2 | ||
+ | $C5-$FF | ||
+ | |||
+ | $02xx-$4FFF | ||
+ | $6000-$83FF | ||
+ | $A000-$BFFF | ||
+ | $D000-$FFFF | ||
+ | |||
+ | |-|x|--|x|---------------|xxxx|------|xxxxxx|------|xxxx|-------------------| | ||
+ | | | | ||
+ | $41 $0200 | ||
+ | |||
+ | Moreover, the range $C000-$C5FF can be made available by relocating the | ||
+ | tables there. | ||
+ | room for 8 sprite definitions. | ||
+ | |||
+ | |||
+ | * Object record | ||
+ | |||
+ | ObjCX = 0 ;Center, X-coord (signed 16-bit) | ||
+ | ObjCY = 2 ;Y-coord | ||
+ | ObjCZ = 4 ;z-coord | ||
+ | ObjData | ||
+ | ObjID = 8 ;User bytes | ||
+ | ObjUser = 9 | ||
+ | ObCenPos = 10 ; | ||
+ | ObjCXRem = 11 ;Center remainders | ||
+ | ObjCYRem = 12 | ||
+ | ObjCZRem = 13 | ||
+ | ObjMat | ||
+ | ObjSize | ||
+ | |||
+ | |||
+ | * Jump table | ||
+ | |||
+ | Init3D | ||
+ | AddObj | ||
+ | DelObj | ||
+ | SetCurOb | ||
+ | GetCurOb | ||
+ | GetNextOb | ||
+ | GetObj | ||
+ | |||
+ | SetMat | ||
+ | Pitch = SetMat+3 | ||
+ | Yaw = Pitch+3 | ||
+ | Roll = Yaw+3 ;Roll - rotate around z-axis | ||
+ | MoveSide | ||
+ | MoveUp | ||
+ | MoveForwards = MoveUp+3 | ||
+ | GetSideVec | ||
+ | GetUpVec | ||
+ | GetFrontVec = GetUpVec+3 | ||
+ | |||
+ | SetParms | ||
+ | |||
+ | SetVisParms = SetParms+3 ; | ||
+ | CalcView | ||
+ | SortVis | ||
+ | DrawAllVis | ||
+ | GetNextVis | ||
+ | RotDraw | ||
+ | DrawFace | ||
+ | |||
+ | * lib3d stuff | ||
+ | |||
+ | CALCMAT | ||
+ | ACCROTX | ||
+ | ACCROTY | ||
+ | ACCROTZ | ||
+ | GLOBROT | ||
+ | ROTPROJ | ||
+ | POLYFILL EQU $8812 | ||
+ | PLOT EQU $8815 | ||
+ | DRAWLINE EQU $8818 | ||
+ | VERSION | ||
+ | |||
+ | |||
+ | Routine descriptions | ||
+ | -------------------- | ||
+ | |||
+ | Init3D | ||
+ | On entry: .AY = pointer to object record storage area | ||
+ | On exit: | ||
+ | |||
+ | This routine is used to initialize various obj3d and lib3d pointers and | ||
+ | variables, and should be called before any other routines are called. | ||
+ | Note that if e.g. tables or the screen center are moved around, the | ||
+ | corresponding variables should be set *after* calling Init3D. | ||
+ | |||
+ | AddObj | ||
+ | On entry: .AY = pointer to object structure data | ||
+ | .X = optional user ID byte | ||
+ | On exit: .AY = pointer to object | ||
+ | .X = object number | ||
+ | C set indicates error (e.g. too many objects!) | ||
+ | |||
+ | AddObj is used to add objects into the world. | ||
+ | the first empty spot in the active object list, and allocates a | ||
+ | corresponding portion of memory in the object storage area, as passed | ||
+ | to Init3D. | ||
+ | |||
+ | SetCurOb | ||
+ | On entry: .X = object number | ||
+ | On exit: .AY = pointer to object | ||
+ | .X = object number | ||
+ | |||
+ | SetCurOb is used to set the current object. | ||
+ | a number returned by AddObj. | ||
+ | e.g. the movement routines, which act on the current object. | ||
+ | |||
+ | GetCurOb | ||
+ | On entry: | ||
+ | On exit: .AY = pointer to object | ||
+ | .X = object number | ||
+ | |||
+ | GetCurOb is used to get the current object number and pointer. | ||
+ | |||
+ | GetOb | ||
+ | On entry: .X = object number | ||
+ | On exit: .AY = pointer to object | ||
+ | |||
+ | GetOb gets a pointer to an object without setting that object to | ||
+ | be the current object. | ||
+ | |||
+ | DelObj | ||
+ | On entry: .X = object number | ||
+ | On exit: C set means rather nasty error | ||
+ | |||
+ | DelObj is used to delete an object from the active object list. | ||
+ | |||
+ | GetNextOb | ||
+ | On entry: | ||
+ | On exit: .X = object number | ||
+ | .AY = object pointer | ||
+ | C = 1 -> error | ||
+ | |||
+ | GetNextOb gets the next object in the active object list, starting from | ||
+ | the current object. | ||
+ | routine may be used to cycle through the list of active objects. | ||
+ | |||
+ | |||
+ | * Object manipulation routines | ||
+ | |||
+ | |||
+ | SetMat | ||
+ | On entry: .X = angle around x-axis | ||
+ | .Y = angle around y-axis | ||
+ | .A = angle around z-axis | ||
+ | On exit: | ||
+ | |||
+ | SetMat is used to set a rotation matrix for the current object. | ||
+ | go from 0..127. | ||
+ | not an object' | ||
+ | the local axis. | ||
+ | |||
+ | Yaw | ||
+ | Pitch | ||
+ | Roll | ||
+ | On entry: C clear -> positive rotation | ||
+ | C set -> negative rotation | ||
+ | On exit: | ||
+ | | ||
+ | Yaw, Pitch, and Roll rotate an object by a fixed amount (3 degrees or so) | ||
+ | about the local coordinate axis. (The local coordinate axis rotates | ||
+ | with the object). | ||
+ | |||
+ | MoveUp | ||
+ | MoveSide | ||
+ | MoveForwards | ||
+ | On entry: .A = distance (signed) | ||
+ | On exit: | ||
+ | |||
+ | MoveUp/ | ||
+ | axis, by an amount proportional to .A; negative values of .A will move in | ||
+ | the opposite direction. | ||
+ | |||
+ | GetUpVec | ||
+ | GetSideVec | ||
+ | GetFrontVec | ||
+ | On entry: | ||
+ | On exit: (.X,.Y,.A) = signed (x,y,z) coordinates of orientation vector | ||
+ | |||
+ | GetVec is used to figure out what direction the current object is | ||
+ | pointing in. The vectors are signed, and of length 64. | ||
+ | |||
+ | |||
+ | * Visualization routines | ||
+ | |||
+ | |||
+ | SetParms | ||
+ | On entry: .AY = pointer to pattern table | ||
+ | .X = Bitmap address (high byte) | ||
+ | C set -> solid polygons | ||
+ | C clear -> wireframe | ||
+ | On exit: | ||
+ | |||
+ | SetParms is used to set certain rendering parameters. | ||
+ | consists of a list of 8x8 patterns. | ||
+ | |||
+ | SetVisParms | ||
+ | On entry: .AY = Maximum object range | ||
+ | .X = Minimum object range | ||
+ | On exit: | ||
+ | |||
+ | SetVisParms is used to set the range in which an object is considered | ||
+ | " | ||
+ | the range is not a radius. | ||
+ | |||
+ | CalcView | ||
+ | On entry: .X = Viewpoint object | ||
+ | On exit: | ||
+ | |||
+ | CalcView computes the view from the viewpoint object, by translating | ||
+ | and rotating centers relative to the viewpoint object. | ||
+ | centers are stored in the CX HCX etc. lists; the ObCenPos element in | ||
+ | the object record is an index into this list. | ||
+ | |||
+ | SortVis | ||
+ | On entry: | ||
+ | On exit: | ||
+ | |||
+ | SortVis computes a sorted list of visible objects. | ||
+ | if they are within a 45 degree cone forwards of the viewpoint object, | ||
+ | and within the min/max visibility range, which may be changed with | ||
+ | SetVisParms. | ||
+ | Sorting is necessary to ensure that objects overlap one another | ||
+ | correctly on the screen. | ||
+ | first object in this list. | ||
+ | |||
+ | DrawAllVis | ||
+ | On entry: | ||
+ | On exit: | ||
+ | |||
+ | DrawAllVis draws all visible objects, in order, as determined by SortVis. | ||
+ | |||
+ | GetNextVis | ||
+ | On entry: | ||
+ | On exit: .X = Current object | ||
+ | .AY = Pointer to object | ||
+ | N set -> at end of list | ||
+ | |||
+ | GetNextVis fetches the next object in the visible object list, setting | ||
+ | it to the current object. | ||
+ | a time, but in the proper order. | ||
+ | |||
+ | RotDraw | ||
+ | On entry: .X = Object number | ||
+ | On exit: Cool rendered polygon | ||
+ | |||
+ | RotDraw renders an object to the screen, by rotating and projecting | ||
+ | the object vertices and then drawing faces, either wireframe or solid. | ||
+ | On exit, the rotated and projected points, i.e. the screen coordinates | ||
+ | of the object, are stored in PLISTX and PLISTY; PLISTZ contains the | ||
+ | rotated (but not projected) z-coordinates. | ||
+ | |||
+ | DrawFace | ||
+ | On entry: .AY = pointer to face data | ||
+ | On exit: .AY = pointer to next face | ||
+ | |||
+ | DrawFace renders a polygon to the screen, either wireframe or solid. | ||
+ | It would be an awfully good idea to have the rotated points in PLIST, | ||
+ | i.e. to call RotDraw before calling this routine. | ||
+ | ....... | ||
+ | .... | ||
+ | .. | ||
+ | . C=H #18 | ||
+ | |||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | Stroids | ||
+ | ------- | ||
+ | o SLJ | ||
+ | |||
+ | When you get down to it, just displaying things on the screen is | ||
+ | awfully easy. And clearly, the obj3d library is up to the task of simple | ||
+ | programs like Cool World. | ||
+ | useful for making applications like games, where objects are constantly | ||
+ | being created and destroyed, and have different properties and velocities | ||
+ | and such. | ||
+ | |||
+ | Hence stroids. | ||
+ | more specifically a demonstration that obj3d is suitable for writing games | ||
+ | and such. In stroids, you can fly around in an asteroid field containing | ||
+ | three different types of randomly drifting asteroids. | ||
+ | this article will discuss the main algorithms and techniques used in the | ||
+ | program. | ||
+ | |||
+ | Stroids is also meant to be a starter project for people who would | ||
+ | like to experiment with the library. | ||
+ | routines have been written; with a little more work, Stroids could be made | ||
+ | into an actual game, and there are lots of opportunities for modification and | ||
+ | improvement. | ||
+ | of possible ideas and modifications, | ||
+ | rather akin to a C=Hacking C=Hallenge, so I'm interested in publishing the | ||
+ | niftier modifications and applications that might result. | ||
+ | |||
+ | Finally, the full source code is included at the end of this article | ||
+ | (the PETSCII el cheapo version is in the .zip). | ||
+ | |||
+ | |||
+ | The program | ||
+ | ----------- | ||
+ | |||
+ | The source code is in El Cheapo format. | ||
+ | load and run the loader program. | ||
+ | |||
+ | joystick in port 2 Pitch and yaw, fire | ||
+ | @ and / | ||
+ | space Switch viewpoint object | ||
+ | |||
+ | r Toggle radar on/off | ||
+ | a/z q/w s/d Pitch, roll, yaw | ||
+ | +/ | ||
+ | = Toggle wireframe mode | ||
+ | |||
+ | Initially, the viewpoint is set to a large, fixed tetrahedron at the | ||
+ | center of the world; your ship is straight ahead, and you can move | ||
+ | it around using the joystick and @/. Pressing space shifts the viewpoint | ||
+ | to the next object -- the ship. Pressing space again will hop onto | ||
+ | the next object -- an asteroid! | ||
+ | all the objects, eventually returning to the first object. | ||
+ | |||
+ | |||
+ | The code | ||
+ | -------- | ||
+ | |||
+ | The purpose of this project was to test out the obj3d routines | ||
+ | in some tougher game-like conditions, in which many non-indentical objects | ||
+ | are present and are being created and destroyed more or less randomly, | ||
+ | and where things like collisions must be detectable. | ||
+ | seemed like a reasonable test -- asteroids can created more or less randomly, | ||
+ | with different sizes and velocities. | ||
+ | the asteroid field, maybe with a radar display showing the relative | ||
+ | positions of asteroids. | ||
+ | dodged. | ||
+ | The program is really pretty simple. | ||
+ | control the asteroid field; one controls creating and destroying asteroids, | ||
+ | the other controls their movement. | ||
+ | render a crude radar display. | ||
+ | all that's left is to move the ship around and wait for keys. A little | ||
+ | IRQ routine is used to keep the program running under 60fps on a SuperCPU, | ||
+ | which also reads the joystick (and which in principle would play music, | ||
+ | handle sound effects, etc.), and that's about it. Thus the main loop is | ||
+ | quite straightforward: | ||
+ | |||
+ | - Update asteroid field | ||
+ | - Set up radar | ||
+ | - Compute/ | ||
+ | - Render radar display | ||
+ | - Move ship and wait for keys | ||
+ | |||
+ | Stroids | ||
+ | JSR Init | ||
+ | LDA #00 | ||
+ | STA CURD | ||
+ | |||
+ | :loop | ||
+ | JSR CheckDensity | ||
+ | JSR DriftStroids | ||
+ | |||
+ | JSR SetRadar | ||
+ | |||
+ | LDX VOB ;Calculate view | ||
+ | JSR CalcView | ||
+ | JSR SortVis | ||
+ | JSR DrawAllVis | ||
+ | |||
+ | JSR DrawRadar | ||
+ | |||
+ | JSR CheckFire | ||
+ | JSR SwapBuf | ||
+ | |||
+ | LDX ROB ;Set rotation object | ||
+ | JSR SetCurOb | ||
+ | LDA VELOCITY | ||
+ | JSR MoveForwards | ||
+ | |||
+ | :wait LDA IRQFLAG | ||
+ | BEQ :wait | ||
+ | |||
+ | |||
+ | The asteroid field | ||
+ | ------------------ | ||
+ | |||
+ | The asteroid field only " | ||
+ | exit this cube they are destroyed, and new asteroids created. | ||
+ | this cube stays fixed in the world, but one of the suggested projects is | ||
+ | to center it on, i.e. make it move with, the ship. The purpose of the | ||
+ | cube is to keep things manageable -- it makes no sense to keep track of | ||
+ | asteroids that are many thousands of units away. That would just massively | ||
+ | bog down the system, and mean that the player wouldn' | ||
+ | with any asteroids. | ||
+ | The asteroid field within the cube is characterized by a " | ||
+ | which may be changed by pressing the + and - keys. There are three different | ||
+ | kinds of asteroids -- large, medium, and small -- and each asteroid has a | ||
+ | " | ||
+ | field weight; when destroyed, its weight is subtracted. | ||
+ | exceeds the " | ||
+ | is a constantly changing asteroid field with a pleasantly uniform density. | ||
+ | To create an asteroid, a random value is chosen for the weight; | ||
+ | this weight determines the size of the asteroid. | ||
+ | (would exceed the field density) then no asteroid is created. | ||
+ | the field changing, since asteroids are not simply replaced by identical | ||
+ | objects, and probably gives big asteroids a chance. | ||
+ | is always " | ||
+ | the field, it can be replaced by multiple smaller ones; if a small asteroid | ||
+ | leaves, it can only be replaced by a smaller one. So I predict that the | ||
+ | field would otherwise quickly degenerate into a bunch of little asteroids. | ||
+ | If an asteroid makes it into the field, it is assigned a random | ||
+ | position, speed, and orientation. | ||
+ | obj3d routine SetMat. | ||
+ | the object, and the velocity is stored in the user byte. This provides an | ||
+ | easy way to identify the type of object, and keeps the velocity along with | ||
+ | the object (instead of putting it in another table). | ||
+ | asteroid objects with the high bit of the ID byte set, it is very easy to | ||
+ | differentiate asteroids from other objects. | ||
+ | just moves in a straight line, until it leaves the play area. There is no | ||
+ | collision detection -- that's a project for you! (But it's pretty easy). | ||
+ | All of this is done using two routines: CheckDensity and DriftStroids. | ||
+ | CheckDensity simply creates new asteroids until it can't create any more; | ||
+ | DriftStroids simply goes through the object list, looking for asteroids | ||
+ | and moving any it finds (recall that the velocity of the asteroid is | ||
+ | stored in the user byte). | ||
+ | can go through the source listing to examine the routines in more detail. | ||
+ | |||
+ | |||
+ | Random numbers | ||
+ | -------------- | ||
+ | |||
+ | It is worthwhile to say a word or two about the random number | ||
+ | generator used. I used it as an experiment in my quest for a very quick | ||
+ | but reasonable random number generator, and it seems to work fairly well -- | ||
+ | certainly better than other ones I tried. | ||
+ | generators become awfully apparent awfully quick, in the asteroid field). | ||
+ | This particular generator uses the " | ||
+ | this method (which was proposed by VonNeumann), | ||
+ | and the middle n-bits are taken as the next number in the sequence. | ||
+ | In this case, the numbers are 8-bits. | ||
+ | sequence, the number is squared, giving a 16-bit number, and the middle | ||
+ | eight bits -- bits 4 through 11 -- are used as the next number in the | ||
+ | sequence. | ||
+ | To do the squaring, I simply used the lib3d multiplication tables | ||
+ | of f(x)=x^2/ | ||
+ | 8-bits. | ||
+ | Middle-squares is not without problems. | ||
+ | have a number of short sequences -- ideally you want a sequence of numbers | ||
+ | that takes a long time to repeat; a sequence like 1 99 63 1 99 63 ... isn't | ||
+ | so useful. | ||
+ | just zero. So I built in a simple modification to compensate for these | ||
+ | deficiencies: | ||
+ | generates itself (like zero will), or b) the current number sequence | ||
+ | exceeds a maximum length (that is, after generating N numbers it re-seeds | ||
+ | itself). | ||
+ | getting stuck. | ||
+ | |||
+ | |||
+ | Radar Display | ||
+ | ------------- | ||
+ | |||
+ | Navigating around a 3D world is very difficult without some form | ||
+ | of reference. | ||
+ | display is potentially much more useful, though, and also more difficult -- | ||
+ | another good test of the routines. | ||
+ | trick: it actually creates a "radar object", | ||
+ | centers, and lets obj3d do the hard calculations. | ||
+ | shortly. | ||
+ | The purpose of the radar is to display where objects are located | ||
+ | relative to the person. | ||
+ | be very similar to the " | ||
+ | viewpoint from a particular object, the surrounding objects need to be | ||
+ | translated and rotated about the viewpoint object. | ||
+ | the obj3d routine CALCVIEW does. So by dipping into the obj3d center list | ||
+ | (after calling CALCVIEW) we can get all the relative locations automatically. | ||
+ | The purpose, however, is to _display_ the relative object locations, | ||
+ | not merely to compute them. Imagine, for a moment, a cube, with us located | ||
+ | at the center of the cube (this is, after all, all a 3d world really is). | ||
+ | Then imagine a bunch of dots around the cube, representing the locations of | ||
+ | other objects. | ||
+ | like that on the screen? | ||
+ | Well, we said it was contained in a cube. But drawing a cube is | ||
+ | pretty easy -- just specify the vertices of the cube, locate the center out | ||
+ | away from the viewer, rotate and project. | ||
+ | easy to compute the points inside the cube -- just add them to the list | ||
+ | of vertices, and rotate and project them along with the rest of the cube, | ||
+ | and then maybe plot them on the screen. | ||
+ | the vertices of the cube aren't really necessary. | ||
+ | The only other thing to realize is that there is an obj3d routine | ||
+ | for rotating and projecting points -- RotDraw. | ||
+ | generating a radar display: create a new object, whose vertices are exactly | ||
+ | the relative object centers computed by CalcView. | ||
+ | center coordinate out a little ways from the viewpoint object, and then | ||
+ | just rotate, project, and plot the resulting points somewhere on the screen. | ||
+ | |||
+ | The radar code is a little tricky, and could surely use some | ||
+ | modification, | ||
+ | is an actual obj3d object, and is the first object created -- thus it | ||
+ | is object number 0. Recall that CalcView translates and rotates all | ||
+ | objects relative to the viewpoint object. | ||
+ | the location of the radar object is set to the location of the viewpoint | ||
+ | object: | ||
+ | |||
+ | * | ||
+ | * DrawRadar -- Compute and draw radar | ||
+ | * display, by creating a new object whose points are simply the | ||
+ | * (scaled) object centers. | ||
+ | * | ||
+ | |||
+ | RADOFF | ||
+ | RADFLAG | ||
+ | |||
+ | IDENTITY DFB 64, | ||
+ | DFB 0,64,0 | ||
+ | DFB 0,0,64 | ||
+ | |||
+ | * Set radar center to view center | ||
+ | |||
+ | SetRadar | ||
+ | LDX VOB | ||
+ | JSR SetCurOb | ||
+ | STA TEMP | ||
+ | STY TEMP+1 | ||
+ | LDX #00 | ||
+ | JSR SetCurOb | ||
+ | STA POINT | ||
+ | STY POINT+1 | ||
+ | LDY #5 | ||
+ | :l LDA (TEMP),Y | ||
+ | STA (POINT),Y | ||
+ | DEY | ||
+ | BPL :l | ||
+ | rts2 RTS | ||
+ | |||
+ | Thus CalcView will translate the center to be (0,0,0) and store those zeros | ||
+ | in the center list (CZ and HCZ, located at $5A00 and $5A80). | ||
+ | of doing this is that the radar object will not be considered " | ||
+ | and hence skipped over by DrawAllVis -- the radar object needs to be drawn | ||
+ | manually. | ||
+ | That' | ||
+ | so it is always in the foreground. | ||
+ | as if it were out in front of the viewpoint object (if you think of the | ||
+ | radar display as a cube, you can see that it needs to be treated as if it | ||
+ | were out in front of the viewer, not right on top of/ | ||
+ | |||
+ | DrawRadar | ||
+ | LDA RADFLAG | ||
+ | BMI rts2 | ||
+ | LDX #00 | ||
+ | JSR GetObj | ||
+ | STA POINT | ||
+ | STY POINT+1 | ||
+ | |||
+ | LDY #ObCenPos | ||
+ | LDA (POINT),Y | ||
+ | TAX | ||
+ | LDA #200 ; | ||
+ | STA CZ,X ;by putting it at (0,0,x); note radar is object 0 | ||
+ | LDA #03 ;(so projection will work out correctly -- don't want | ||
+ | STA HCZ,X ;negative points!) | ||
+ | |||
+ | A value of $03C8 is stored directly into the object center list CZ. This | ||
+ | is the list used by RotDraw: the object centers from this list are added | ||
+ | to the object vertices before projecting. | ||
+ | because it makes the radar a reasonable size on the screen -- smaller | ||
+ | values of the z-coordinate mean the object is closer to the viewer, and | ||
+ | hence will appear larger. | ||
+ | The next step is to set the viewpoint orientation matrix to the | ||
+ | identity matrix. | ||
+ | rotate a vertex P by the orientation matrix R, add it to the center C, | ||
+ | and rotate the whole thing by the viewpoint orientation matrix M. In | ||
+ | this case, the radar needs to be _not_ rotated, so M needs to be set to 1 | ||
+ | (R, the local rotation matrix, is already set to the identity matrix). | ||
+ | The viewpoint matrix used by RotDraw is stored in zero page, at VIEWMAT, | ||
+ | so it's a simple matter of copying the identity matrix defined up above: | ||
+ | |||
+ | LDX #8 | ||
+ | :l1 LDA IDENTITY,X | ||
+ | STA VIEWMAT, | ||
+ | DEX | ||
+ | BPL :l1 | ||
+ | |||
+ | The next step is to create the data for the radar. | ||
+ | The idea is to create an actual radar object, and update the | ||
+ | vertices at each pass of the main loop. The radar object is stored at | ||
+ | the very end of the code: | ||
+ | |||
+ | * | ||
+ | * Radar object. | ||
+ | * the translated and rotated object centers. | ||
+ | * using the obj3d routines, and the points plotted. | ||
+ | * | ||
+ | RADOBJ | ||
+ | DFB 0 ;Normal object | ||
+ | DFB 0 ;Number of points | ||
+ | DFB 0 ;Number of faces | ||
+ | |||
+ | * Point list | ||
+ | |||
+ | RADDAT | ||
+ | |||
+ | The idea is to update the number of points and set the vertices -- stored | ||
+ | at RADDAT -- to be the translated and rotated object centers. | ||
+ | however, that object vertices must be in the range -95..95 or so. A typical | ||
+ | object center, however, is a 16-bit number. | ||
+ | be scaled; the radar code divides them by eight, before adding to the vertex | ||
+ | list, setting any out of range numbers to either -95 or 95. This is done | ||
+ | using the little subroutine DIV8X1, used later. | ||
+ | Before doing anything, however, three pointers need to be set | ||
+ | up which point to the x-, y-, and z-coordinates of the radar object | ||
+ | vertices: | ||
+ | |||
+ | LDX NUMOBJS | ||
+ | DEX | ||
+ | STX RADOBJ+1 | ||
+ | TXA | ||
+ | CLC | ||
+ | ADC #<RADDAT | ||
+ | STA TEMP ; | ||
+ | LDA #>RADDAT | ||
+ | ADC #00 | ||
+ | STA TEMP+1 | ||
+ | TXA | ||
+ | ADC TEMP | ||
+ | STA POINT ;z-coords | ||
+ | LDA TEMP+1 | ||
+ | ADC #00 | ||
+ | STA POINT+1 | ||
+ | |||
+ | Note the STX RADOBJ+1, which sets the number of points stored in the | ||
+ | object. | ||
+ | rotated center list, divides each coordinate by eight, and stores the | ||
+ | result in the object: | ||
+ | |||
+ | LDY #00 ;Order doesn' | ||
+ | :loop | ||
+ | LDA CX,X | ||
+ | STA X1 | ||
+ | LDA HCX,X | ||
+ | JSR DIV8X1 | ||
+ | STA RADDAT,Y | ||
+ | |||
+ | LDA CY,X | ||
+ | STA X1 | ||
+ | LDA HCY,X | ||
+ | JSR DIV8X1 | ||
+ | STA (TEMP),Y | ||
+ | |||
+ | LDA CZ,X | ||
+ | STA X1 | ||
+ | LDA HCZ,X | ||
+ | STA TAB1, | ||
+ | JSR DIV8X1 | ||
+ | STA (POINT),Y | ||
+ | INY | ||
+ | :skip DEX | ||
+ | BNE :loop ;0 is radar object | ||
+ | |||
+ | That's all there is to creating the radar! | ||
+ | rendered to the screen. | ||
+ | The lib3d routines have two zero page variables, XOFFSET and YOFFSET, | ||
+ | which tell the routines where the center of the screen is -- after points | ||
+ | are projected, they are added to XOFFSET and YOFFSET to get the actual | ||
+ | screen coordinates. | ||
+ | the screen, e.g. XOFFSET=160 and YOFFSET=100, | ||
+ | right into the middle of the screen isn't so useful -- better to have it | ||
+ | tucked away off in a corner. | ||
+ | XOFFSET and YOFFSET to make the projection routines think that the origin | ||
+ | is at, say, (24,24), in the upper-left corner of the screen: | ||
+ | |||
+ | LDA XOFFSET | ||
+ | PHA | ||
+ | LDA YOFFSET | ||
+ | PHA | ||
+ | LDA #RADOFF | ||
+ | STA XOFFSET | ||
+ | STA YOFFSET | ||
+ | |||
+ | LDX #00 | ||
+ | JSR RotDraw | ||
+ | |||
+ | PLA | ||
+ | STA YOFFSET | ||
+ | PLA | ||
+ | STA XOFFSET | ||
+ | |||
+ | RotDraw projects the points, and stores them in PLISTX and PLISTY. | ||
+ | all that remains is to move through these lists and plot each point! | ||
+ | I tried this, however, the result was unsatisfactory -- there were just a | ||
+ | bunch of points on the screen, and it was very difficult to tell where | ||
+ | they were located relative to the view, or how far away they were, etc. | ||
+ | So the next logical step was to draw lines instead of points -- that is, | ||
+ | draw a line from the center of the radar (the viewer location) to the | ||
+ | individual points. | ||
+ | e.g. whether an asteroid was in front of or behind the viewer. | ||
+ | last step was to use different colors for the lines, depending on whether | ||
+ | the object is in front or behind the viewer. | ||
+ | checking the sign of the z-coordinates; | ||
+ | in a spare table TAB1, above, in the code which computed the object | ||
+ | vertices. | ||
+ | With all that in mind, here's the rendering code: | ||
+ | |||
+ | : | ||
+ | DEY | ||
+ | LDA PLISTX,Y | ||
+ | STA X1 | ||
+ | LDA PLISTX+$80, | ||
+ | STA X1+1 | ||
+ | LDA PLISTY,Y | ||
+ | STA Y1 | ||
+ | LDA PLISTY+$80, | ||
+ | STA Y1+1 | ||
+ | LDA # | ||
+ | STA X2 | ||
+ | STA Y2 | ||
+ | LDA #00 | ||
+ | STA X2+1 | ||
+ | STA Y2+1 | ||
+ | |||
+ | LDA #< | ||
+ | STA FILLPAT | ||
+ | LDA #>COLOR2 | ||
+ | STA FILLPAT+1 | ||
+ | LDA TAB1, | ||
+ | BMI :draw ;for front/back | ||
+ | LDA #<COLOR1 | ||
+ | STA FILLPAT | ||
+ | LDA #>COLOR1 | ||
+ | STA FILLPAT+1 | ||
+ | |||
+ | :draw JSR DrawLine | ||
+ | |||
+ | DEC RADOBJ+1 | ||
+ | BNE :loop2 | ||
+ | :rts | ||
+ | |||
+ | where COLOR1 and COLOR2 are simple 8x8 patterns: | ||
+ | |||
+ | COLOR1 | ||
+ | COLOR2 | ||
+ | |||
+ | And that's all there is to it! | ||
+ | That pretty much wraps up the non-obvious parts of the code; the | ||
+ | rest is really straightforward. | ||
+ | |||
+ | |||
+ | Simple Modification and Improvement Projects | ||
+ | -------------------------------------------- | ||
+ | |||
+ | Not only did I not feel like doing further work on the project, | ||
+ | it occured to me that others might actually be interested in doing the | ||
+ | simple routines needed to e.g. make it into a game. So in this section | ||
+ | I thought I'd describe some possible directions that the code might be | ||
+ | taken in -- some simple projects that give some practice in using the | ||
+ | obj3d routines and 3d graphics in general. | ||
+ | |||
+ | - Third person perspective | ||
+ | |||
+ | It might be interesting to try putting in a third-person perspective, | ||
+ | i.e. place an empty object above and behind the view object. | ||
+ | |||
+ | - Collisions | ||
+ | |||
+ | Currently there is no collision detection -- in particular, there | ||
+ | is no detection of an asteroid hitting the ship. This is a pretty easy | ||
+ | task -- get the ship's center x-, y-, and z-coordinates, | ||
+ | them to the coordinates of every asteroid. | ||
+ | the object list, check the ID byte (asteroids have the high bit clear), | ||
+ | and if an asteroid compute the difference between the asteroid and ship | ||
+ | centers. | ||
+ | collision. | ||
+ | may be used without performing the subtraction. | ||
+ | If you're feeling more brave, a good challenge would be to let | ||
+ | asteroids collide, and break into smaller asteroids upon collision. | ||
+ | just involves many more comparisons -- perhaps even a little data | ||
+ | structure to process the comparisons more efficiently (e.g. sorting | ||
+ | one of the coordinates, | ||
+ | |||
+ | - Realistic flight model | ||
+ | |||
+ | Currently stroids uses a rather cheesy " | ||
+ | you always move in the forwards direction. | ||
+ | taking intertia into account, would probably be more fun. Physics says | ||
+ | that force causes a change in velocity in the direction of that force -- | ||
+ | in other words, if you thrust in a particular direction, it adds velocity | ||
+ | in that direction. | ||
+ | direction of thrust, using GetFrontVec, | ||
+ | velocity vector: | ||
+ | |||
+ | If thrust | ||
+ | - GetFrontVec (vector has length = 64) | ||
+ | - Scale vector -- maybe divide by 16, keeping fractional part; | ||
+ | maybe treat strictly as fractional part. | ||
+ | - Add to total velocity | ||
+ | |||
+ | Then at each pass through the main loop the code simply adds the total | ||
+ | velocity to the center coordinates. | ||
+ | |||
+ | - Compass, position, fuel, stats | ||
+ | |||
+ | With a more realistic flight model, it becomes imperative to | ||
+ | somehow indicate what direction the ship is moving, compared with the | ||
+ | direction it is pointing. | ||
+ | the position and direction need to be indicated somehow, either graphically | ||
+ | or in plain text. A simple solution would be to use a raster interrupt at | ||
+ | the bottom of the screen, and display the information as text. | ||
+ | Naturally, if a game of some sort is desired, then it might be | ||
+ | helpful to add damage and fuel into the program. | ||
+ | |||
+ | - Better radar | ||
+ | |||
+ | The current radar display is not terribly effective. | ||
+ | is to make an Elite-like radar display, which gives a plane of reference and | ||
+ | indicates altitude and position (i.e. draws right-triangles). | ||
+ | very difficult extension of the radar calculation. | ||
+ | be tilted, which means using SetMat to rotate the radar around the x-axis | ||
+ | when it is first initialized. | ||
+ | every point (x, y, z) add another point (x, 0, z) -- drawing a line | ||
+ | between these two points gives the altitude. | ||
+ | altitude of a right triangle; the current display draws the hypoteneuse). | ||
+ | |||
+ | - Better asteroid objects | ||
+ | |||
+ | Tetrahedrons are not particularly inspiring asteroids. | ||
+ | new non-lame ones! | ||
+ | |||
+ | - Move asteroid field with ship | ||
+ | |||
+ | Currently the asteroid field is centered at (0,0,0) -- that is, the | ||
+ | boundaries of the field are fixed. | ||
+ | the boundaries move with the ship. | ||
+ | |||
+ | - Variable field density | ||
+ | |||
+ | Instead of having a single fixed density to the field, modify the | ||
+ | program so that different areas of space have different asteroid densities. | ||
+ | |||
+ | - Projectiles and lasers | ||
+ | |||
+ | What' | ||
+ | possibility is to add a projectile weapon -- make a little cube shoot out | ||
+ | the front of the ship, for example. | ||
+ | home in on a target. | ||
+ | Another thing to do is to make the lasers actually do something. | ||
+ | Detecting a laser hit on an object is non-trivial -- ships which are closer | ||
+ | are larger, and should be easier to hit. The basic issue is to determine | ||
+ | whether or not the object covers the center of the screen -- that is, whether | ||
+ | there is any point (0,0,z) _inside_ of the object. | ||
+ | to check whether the (rotated and translated) center of a visible object is | ||
+ | within a certain distance of (0,0,z) (i.e. only check the x- and y- | ||
+ | coordinates). | ||
+ | Another possibility is to draw each object, one at a time, and | ||
+ | check to see if the center of the screen (or some small area of the screen) | ||
+ | was plotted to. The thing that makes this method a little tricky is that | ||
+ | the farthest objects are drawn first, so a program needs to keep track of | ||
+ | the _last_ object that draws to the center of the screen, not the first. | ||
+ | And doing that means clearing the pixels being checked whenever they are | ||
+ | rendered to. | ||
+ | There are probably other, better methods of detecting collisions! | ||
+ | |||
+ | - Make a game | ||
+ | |||
+ | Naturally. | ||
+ | |||
+ | Source code | ||
+ | ----------- | ||
+ | |||
+ | See the .zip file, or the Fridge, for the stroids source code. | ||
+ | ....... | ||
+ | .... | ||
+ | .. | ||
+ | . C=H #18 | ||
+ | |||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | begin 644 obj3d.zip | ||
+ | M4$L# | ||
+ | M)(@3+)P/ | ||
+ | MK, | ||
+ | MGE5/ | ||
+ | MPU, | ||
+ | MV!V@TQ: | ||
+ | MSY' | ||
+ | M[SG; | ||
+ | M!`[I0T# | ||
+ | MG.XI22(D(C); | ||
+ | M' | ||
+ | MW30[V8T\V9/ | ||
+ | MT' | ||
+ | MB=G)*2L;: | ||
+ | MFJ+RGPII.0_\MYS1*LG[0I+DWFV2J\3L& | ||
+ | M# | ||
+ | MDISJ@)(: | ||
+ | MBI" | ||
+ | M_R+ZAC: | ||
+ | M=JSTI& | ||
+ | M25CU@T@CY& | ||
+ | M^YI(#" | ||
+ | MTMA5< | ||
+ | M8GZO/ | ||
+ | MJ1]SKKUM-).-7> | ||
+ | M4# | ||
+ | M6$`GBX," | ||
+ | MVHZEZAUD!T7Z*; | ||
+ | M7: | ||
+ | M# | ||
+ | M$3_; | ||
+ | M\1]ZLF.P+8%-1(# | ||
+ | M`](F=-_I3.PB#"' | ||
+ | M4HY*N2KEJ92OTF*5EJBT5*5E*FE4NDNEY2K=K=(*E5: | ||
+ | M[E5IK4KWJ; | ||
+ | MS`!O!M8" | ||
+ | M_@CX// | ||
+ | M> | ||
+ | M$=9UA/ | ||
+ | MKFG@;& | ||
+ | MU\C; | ||
+ | M: | ||
+ | M& | ||
+ | MF.%# | ||
+ | M45GN45; | ||
+ | MXZ26\=713& | ||
+ | MR!Q> | ||
+ | M%23'" | ||
+ | M_GVC\+^X2? | ||
+ | MKQV(*4^=X? | ||
+ | M@SD> | ||
+ | M" | ||
+ | M' | ||
+ | MYJ16J' | ||
+ | M3> | ||
+ | M1^9AF" | ||
+ | M\V'< | ||
+ | MZ^=`6O' | ||
+ | M" | ||
+ | M8!8< | ||
+ | MEFR;: | ||
+ | MH(D6Z? | ||
+ | M3X!S@+MQ!OH/ | ||
+ | M2? | ||
+ | M(.R# | ||
+ | MS< | ||
+ | MW821I`D/ | ||
+ | M: | ||
+ | MO55Z%; | ||
+ | MQD@B7W4# | ||
+ | ME4RYGC$; | ||
+ | M[Y.LL=68GYJ, | ||
+ | M' | ||
+ | M*; | ||
+ | MT< | ||
+ | M? | ||
+ | M[7=@? | ||
+ | MQ$TF5LZXM4MC& | ||
+ | M" | ||
+ | M-A_OM< | ||
+ | MYZ8ZFU' | ||
+ | M05UWA9F5%Z$=N(CTO" | ||
+ | MD" | ||
+ | M3D[" | ||
+ | M65/ | ||
+ | M)ZGW(+M3R': | ||
+ | M[$!ULA/ | ||
+ | M_D9/ | ||
+ | M4I*L68)X`K> | ||
+ | MJ? | ||
+ | M9/& | ||
+ | M1S9D3VP\48C> | ||
+ | M01W; | ||
+ | M8T-41)/ | ||
+ | M)2.V@W]' | ||
+ | MU2]+/ | ||
+ | MYNVB<; | ||
+ | M!< | ||
+ | M``@`, | ||
+ | M+)P/ | ||
+ | MPF> | ||
+ | M, | ||
+ | M%$`J(`V0# | ||
+ | M58![`: | ||
+ | M@%\#' | ||
+ | MEPZB!*%!& | ||
+ | MAD!& | ||
+ | M%R^Y)W/ | ||
+ | MUQ6^I$L^JGOBJ.[: | ||
+ | M0? | ||
+ | M> | ||
+ | MJH51@WNNJRI\%L$3, | ||
+ | M0PWW^MM1\^" | ||
+ | M=Y)!? | ||
+ | M# | ||
+ | MGQ^, | ||
+ | M@$5_-0D$POAO*(? | ||
+ | M[# | ||
+ | M"? | ||
+ | MQ7COK; | ||
+ | M-; | ||
+ | M:<&, | ||
+ | M.R`UJ3@? | ||
+ | MI:: | ||
+ | MX,; | ||
+ | M(*]Q" | ||
+ | MMKFG$_EU]]TEL@Z(K)Y" | ||
+ | MVSTK< | ||
+ | M? | ||
+ | MFY" | ||
+ | MG: | ||
+ | MS): | ||
+ | MI/ | ||
+ | MB9]D+PJSS%>' | ||
+ | MFD3# | ||
+ | MOU===$/ | ||
+ | M1; | ||
+ | MZ; | ||
+ | MV" | ||
+ | M+6[QM+`^Q0GV.-_+> | ||
+ | MG# | ||
+ | M]S97$VYD# | ||
+ | MS; | ||
+ | M: | ||
+ | M: | ||
+ | M7W^K8+6/ | ||
+ | M: | ||
+ | M*%FG8)TZ/ | ||
+ | MOL< | ||
+ | M00L[P0T)$B; | ||
+ | M*6: | ||
+ | MAPE-.5-< | ||
+ | M$9' | ||
+ | M\^Q1S$EZ3R4V> | ||
+ | M8RP6E4HH& | ||
+ | M!ZCA[V!-P# | ||
+ | M(]H;; | ||
+ | M/ | ||
+ | MD]PU.< | ||
+ | M< | ||
+ | MV@5Q9!NXSX*-]; | ||
+ | MV`? | ||
+ | MS< | ||
+ | MNF/' | ||
+ | MTIBK1I[GY' | ||
+ | M4F.1-0C*`]F10Q)T: | ||
+ | M$UC$I^WRM# | ||
+ | M.8$!%)R7%)S$33& | ||
+ | MRV$)LN^`AW< | ||
+ | M2P, | ||
+ | M`ZQ7-P1_$RR<# | ||
+ | MMR/ | ||
+ | M4A1-, | ||
+ | M7*G6ZHUFJ]WI]OJ# | ||
+ | MX.9YCG.Q+,/ | ||
+ | M[K? | ||
+ | MB7@LJFF1L*J& | ||
+ | M86)G96XQ, | ||
+ | ME+Q,, | ||
+ | M6T0L@@@B> | ||
+ | M$GC1..' | ||
+ | MWRZ)Z_EP2FG# | ||
+ | M756> | ||
+ | MP^.H& | ||
+ | MI)!3I%%S" | ||
+ | M;" | ||
+ | M22+D&> | ||
+ | M? | ||
+ | M' | ||
+ | M\L=S" | ||
+ | MO: | ||
+ | M6`-& | ||
+ | M]0> | ||
+ | M"# | ||
+ | MEWR3I_E.& | ||
+ | M2;? | ||
+ | M\=S> | ||
+ | M> | ||
+ | M+F]56`P``ZQ7-SJC&#><# | ||
+ | MYJ^PL%B10X@@I_' | ||
+ | M38A8G# | ||
+ | MITZ^TIV*(T&, | ||
+ | MM7+\I!J(@Z' | ||
+ | MXUE?, | ||
+ | M_\P.96*K+_XE]BA; | ||
+ | M0; | ||
+ | MA-$? | ||
+ | M4ZE>: | ||
+ | M7$XS<, | ||
+ | M1Y=EMB7+8)G@.DP' | ||
+ | M3< | ||
+ | MW# | ||
+ | M$`!S=' | ||
+ | M81F)T: | ||
+ | MG=JJJ.NR$A(3.PC-&/ | ||
+ | M+TV)& | ||
+ | M? | ||
+ | M6D\9ZZV0(5E> | ||
+ | M@N!-$+)!L5< | ||
+ | M!JAJA5F)[H]EP`; | ||
+ | M" | ||
+ | M: | ||
+ | M2NND5# | ||
+ | MBB$%Q^> | ||
+ | M& | ||
+ | M.`[%# | ||
+ | M& | ||
+ | MS)> | ||
+ | M43# | ||
+ | MS1KRE/ | ||
+ | MU' | ||
+ | M%" | ||
+ | MS=L_(), | ||
+ | M`Y' | ||
+ | MBO`: | ||
+ | M$280/ | ||
+ | MWR+D$8XC%!" | ||
+ | MF: | ||
+ | M: | ||
+ | MW< | ||
+ | MEAL>, | ||
+ | MN(TEJ; | ||
+ | MG2Z: | ||
+ | M/ | ||
+ | MP" | ||
+ | M]P"&& | ||
+ | MH5_NG6_FF]ESGMUGG]ESSA]G9A=$3D6^!^0B).](`0# | ||
+ | M!O6/ | ||
+ | M@C? | ||
+ | MW/ | ||
+ | M? | ||
+ | M_R;# | ||
+ | M]? | ||
+ | M+@QPUY' | ||
+ | M/ | ||
+ | M@H!++H' | ||
+ | MSU3NY& | ||
+ | M.(=LIK)_]/ | ||
+ | M4`EQS`=H> | ||
+ | M": | ||
+ | MG> | ||
+ | MM==" | ||
+ | M83S< | ||
+ | MO& | ||
+ | MIF/ | ||
+ | MSDL, | ||
+ | M3S3L+XF)P(+, | ||
+ | M/ | ||
+ | M+H4!EE, | ||
+ | MT*Q7; | ||
+ | M><' | ||
+ | M< | ||
+ | M9DM+GV_1V< | ||
+ | M[; | ||
+ | M5\EZMHJKM# | ||
+ | M-XK: | ||
+ | MVLU%SZ!V*]& | ||
+ | M& | ||
+ | MI' | ||
+ | M@9< | ||
+ | M)1$HX> | ||
+ | M(C" | ||
+ | MNW0I" | ||
+ | M>' | ||
+ | M8:' | ||
+ | MZTO[> | ||
+ | MEY@: | ||
+ | M3BWZ2(]8[$(< | ||
+ | M3FQ)B? | ||
+ | M0I? | ||
+ | M['? | ||
+ | MJXP@8TDW/ | ||
+ | M!67FI8;, | ||
+ | MG@(H]@H.ON6_U2:? | ||
+ | M: | ||
+ | M++L]> | ||
+ | M.)GS? | ||
+ | M.L9[ZQ9OJAD+; | ||
+ | MV> | ||
+ | MOX-)/ | ||
+ | MN)4F; | ||
+ | MY%1))+X@0< | ||
+ | M,, | ||
+ | M]U5" | ||
+ | M=NXIJSU]4_Y? | ||
+ | M6< | ||
+ | M112E" | ||
+ | ME3; | ||
+ | MXL1C,;/ | ||
+ | M=$O-]SK-FFXZ;: | ||
+ | MZM@7KB" | ||
+ | M); | ||
+ | M=# | ||
+ | M\M.: | ||
+ | MNJ>, | ||
+ | MQ" | ||
+ | M[+, | ||
+ | M6[; | ||
+ | MM$5AD6\OW6_H; | ||
+ | M# | ||
+ | M3@`ME4/ | ||
+ | M]9=E@C\+R? | ||
+ | MXXJ' | ||
+ | M^%5XU)$X$G6QX=6\N4'< | ||
+ | MH(SE)2`4A`6_)@H' | ||
+ | M!W& | ||
+ | M+]!+[EZZ)? | ||
+ | MJH]" | ||
+ | MGI+EJ6(E" | ||
+ | M^4ZQ09' | ||
+ | M]? | ||
+ | M+=3C& | ||
+ | M))E5, | ||
+ | MJW< | ||
+ | ML(< | ||
+ | M& | ||
+ | MNADK; | ||
+ | M\0M+]V.& | ||
+ | MO> | ||
+ | M0F-ZAX7*1-9JOJ-\P=.)0_-3VB.HA69=FADV@0.1BW@C/ | ||
+ | MC+)^@IR_2--2E4I%" | ||
+ | MRURDI$2C< | ||
+ | M, | ||
+ | M^_0TE< | ||
+ | MM[6[\\65^HO65# | ||
+ | MT'/ | ||
+ | M]T2M, | ||
+ | MZV-H91H\QE\1V4$/ | ||
+ | MKN=' | ||
+ | M@`RZDDL[& | ||
+ | M\BAOEJ$, | ||
+ | M(9J3X3B->? | ||
+ | M;" | ||
+ | M@=I]R_!=!8WO!QZ1%VR? | ||
+ | M" | ||
+ | M\/ | ||
+ | M.-KG6`V@SJOGG4/ | ||
+ | MZU)X66: | ||
+ | MSCCB;/"/ | ||
+ | MS&, | ||
+ | M& | ||
+ | M`CS$_U=U: | ||
+ | M*EY0DK, | ||
+ | M/ | ||
+ | M' | ||
+ | MPUTBG, | ||
+ | M`' | ||
+ | M; | ||
+ | M%I# | ||
+ | M"? | ||
+ | MGMO='? | ||
+ | MV=SFRTRD;& | ||
+ | M4TO[R@QR3W\IAP<?, | ||
+ | MS)F%*AY*@ET0J: | ||
+ | M91" | ||
+ | M$]BL]A$]2NFBUOMJWTJ@7M\JH2^_?< | ||
+ | MZ' | ||
+ | M\" | ||
+ | M_IJ, | ||
+ | MC.NSE<< | ||
+ | MH$J? | ||
+ | M*6!' | ||
+ | MS^]O*\" | ||
+ | M3MK/ | ||
+ | MJU6XLTB^BV" | ||
+ | MR4K)2K.XLM=' | ||
+ | M'? | ||
+ | M==@S^+)G%=F' | ||
+ | MNF4]$GR,& | ||
+ | MT< | ||
+ | M084A# | ||
+ | M_90I; | ||
+ | MDQ-$6=0F7*=9*$V5ND^C$.RH2P(0@8T$53)_1.M!QE+[# | ||
+ | MHV@AD@[< | ||
+ | M5@74_EI< | ||
+ | M*2S`R\7& | ||
+ | M; | ||
+ | M6!5%Z[^9@KOG9VSK5=$_@< | ||
+ | M-6S[9`O@862QG/ | ||
+ | MD.E[=!C-_54\XS\G" | ||
+ | MR9=/ | ||
+ | MAJ; | ||
+ | M%KYY\_JUKZ^SL^VUC(STS5> | ||
+ | M^? | ||
+ | MW^-8O+.7X7_H13B)? | ||
+ | MW6]G[_TMLDF`< | ||
+ | M" | ||
+ | MH:>' | ||
+ | MO5^\" | ||
+ | M]? | ||
+ | MN7EX^; | ||
+ | MW]FU$8N\E7]FYM> | ||
+ | M: | ||
+ | M1[^A+WM_[5^&&# | ||
+ | MAM&? | ||
+ | M8J4W: | ||
+ | MY^L? | ||
+ | M\8KE> | ||
+ | MH; | ||
+ | MLF5S]? | ||
+ | M^Z\GRIIUNS%8S=WK^!P4F7" | ||
+ | MGC=# | ||
+ | M74KWB< | ||
+ | M%> | ||
+ | MX^#: | ||
+ | MI, | ||
+ | MA7=/ | ||
+ | M/ | ||
+ | M=7\MSTT*$^AT!A< | ||
+ | M+$"' | ||
+ | MF9G, | ||
+ | M, | ||
+ | M-[U^< | ||
+ | M.2_\S[J7:' | ||
+ | MT8E& | ||
+ | MV< | ||
+ | M`O5`!& | ||
+ | ME(*/#? | ||
+ | MR: | ||
+ | M^DZ? | ||
+ | MT-G3]]7KV+2< | ||
+ | M0(KG-M_0$U*2+PQP!Q6NQ=80_: | ||
+ | MV, | ||
+ | M```& | ||
+ | MPG*%R4HACDX^KH;& | ||
+ | M.;" | ||
+ | M1K[., | ||
+ | M`!``8FEG=& | ||
+ | M5+5^4%X\" | ||
+ | MI' | ||
+ | M/ | ||
+ | MC=T(GKOP7+C# | ||
+ | MYU? | ||
+ | M!WL`8, | ||
+ | M< | ||
+ | M!3H, | ||
+ | M*$< | ||
+ | MQY`WP1MW[? | ||
+ | M0J.+R(/ | ||
+ | M> | ||
+ | M.)=WGCMF" | ||
+ | M3)/; | ||
+ | MTV-E0E6?& | ||
+ | M_9_I<:<# | ||
+ | M9KO)=# | ||
+ | M; | ||
+ | ML-Y[01# | ||
+ | M43L8Q8*50.3VP# | ||
+ | M`R=4!A2QWM0(!!_!`# | ||
+ | MA3& | ||
+ | MI/ | ||
+ | ML8VP5ME4FS+; | ||
+ | M7G%U-": | ||
+ | MJGEKQTP]H0I/ | ||
+ | M%NPRVT# | ||
+ | MO> | ||
+ | M28N; | ||
+ | MD7\4S$' | ||
+ | MRY4XB]6+`_A@E, | ||
+ | ME!\H\80W> | ||
+ | MLX6!BV> | ||
+ | ME# | ||
+ | MQY; | ||
+ | M? | ||
+ | M/ | ||
+ | M+3]>" | ||
+ | MY@B=_@1`$? | ||
+ | M2/ | ||
+ | MFM, | ||
+ | M' | ||
+ | MY]+MP^T14.)41BOH_FP> | ||
+ | MKUQH?, | ||
+ | M4)? | ||
+ | MJ0# | ||
+ | MO.$_<>, | ||
+ | M" | ||
+ | M]P< | ||
+ | MMZ)# | ||
+ | MP$W14" | ||
+ | M]L> | ||
+ | MM`& | ||
+ | M8FX/, | ||
+ | M7< | ||
+ | M2P, | ||
+ | MB7XWZV9' | ||
+ | MAWW8G)S*5NW# | ||
+ | M9KI[^C(S^J=_? | ||
+ | MHUW_: | ||
+ | MGDW@JXU? | ||
+ | M-& | ||
+ | M0& | ||
+ | MG2CL,> | ||
+ | MAYY# | ||
+ | M=1$D^X< | ||
+ | MV.T" | ||
+ | M+@< | ||
+ | MGR/ | ||
+ | MH$G> | ||
+ | M&# | ||
+ | ML*TDVFQ]Y" | ||
+ | M)`; | ||
+ | M)Z)NHR? | ||
+ | M];? | ||
+ | M/ | ||
+ | MSB$[UH? | ||
+ | MS\ZG.: | ||
+ | MM7NTKF; | ||
+ | MQ, | ||
+ | MY2CDT`AS=Q# | ||
+ | M2!*VJ7L-<& | ||
+ | M3# | ||
+ | M**H`LYS8# | ||
+ | M@N2P9Q]Z.%O0, | ||
+ | MT$D*, | ||
+ | M; | ||
+ | MRXEEO> | ||
+ | M$' | ||
+ | M%, | ||
+ | M8O3D2^]+(1M@YN/ | ||
+ | M& | ||
+ | M`H2? | ||
+ | MU6(^FT# | ||
+ | MZ' | ||
+ | M(' | ||
+ | MFIVX; | ||
+ | M1P5C' | ||
+ | M" | ||
+ | M*RGB> | ||
+ | M(Y/ | ||
+ | M1\OU" | ||
+ | ML`G*+T1R'' | ||
+ | M# | ||
+ | MXKG$SB=MD+?# | ||
+ | MGBVRCROI0P^-AIY]? | ||
+ | MBD:>? | ||
+ | M< | ||
+ | M-^`K5$^1G./ | ||
+ | M*: | ||
+ | MOP\Y=EZ: | ||
+ | M" | ||
+ | M(W6]=E\ZNDNL& | ||
+ | M' | ||
+ | M# | ||
+ | MME,, | ||
+ | M7@=AC%L*R\D]E1R!Y=M; | ||
+ | MC" | ||
+ | M: | ||
+ | M/: | ||
+ | M7(V7X^EYOF' | ||
+ | M5I" | ||
+ | M? | ||
+ | M, | ||
+ | M# | ||
+ | M$K: | ||
+ | M; | ||
+ | M& | ||
+ | MFQEQ-KG1428GM# | ||
+ | MN$_'? | ||
+ | M!P4Y' | ||
+ | MXMXV!N: | ||
+ | M.T$HW< | ||
+ | MHN)<; | ||
+ | MY-SXM< | ||
+ | M# | ||
+ | M4C[[Z? | ||
+ | M9782]/& | ||
+ | MB_(=MB' | ||
+ | MRZ[Q`+O: | ||
+ | M8K_(1XR3V# | ||
+ | MY> | ||
+ | M-7!.L=+8=PE%YJC1: | ||
+ | M9EG%C/ | ||
+ | M/ | ||
+ | M_; | ||
+ | M`)*PS5(J3ZCI0%'? | ||
+ | M31FOM6, | ||
+ | M# | ||
+ | MYQ[D5Q$Y; | ||
+ | M`3IX_$3? | ||
+ | M+@M# | ||
+ | M? | ||
+ | MA; | ||
+ | MYGI: | ||
+ | MLAZJYO*# | ||
+ | M-260FA)(30FDI@124P*IJ=', | ||
+ | M)S/ | ||
+ | MQS6W< | ||
+ | M?" | ||
+ | MR+> | ||
+ | M< | ||
+ | MD\ETJO]%: | ||
+ | M6\[& | ||
+ | MUNEB3O2.R#&; | ||
+ | MD: | ||
+ | MLR6$UN^[XK[3@6^18TQ[+N@VG1X\(_-K0FVX%? | ||
+ | M4H? | ||
+ | M9L@X:, | ||
+ | ML/ | ||
+ | M9C\905GX*' | ||
+ | MU(](7+NX1X1^M8$B1L]7;& | ||
+ | MGZD8CR<# | ||
+ | MQ0: | ||
+ | M-\WVE1TI> | ||
+ | M\? | ||
+ | M$4CT(4.N0N? | ||
+ | M$$)U$Z=8H$.]3L4I%E# | ||
+ | MW!R%9NX6TZ[K: | ||
+ | M);' | ||
+ | M& | ||
+ | MAS& | ||
+ | M< | ||
+ | M``@`: | ||
+ | M-YP/ | ||
+ | M44D8R*^_SSF[*PGL]KY\N)DF-F+/ | ||
+ | M< | ||
+ | M)0LEIWZ6RS19Y6& | ||
+ | M.\-# | ||
+ | MA8IS" | ||
+ | MX23R6JWE0BT22+3PEWPWW1H& | ||
+ | M: | ||
+ | M< | ||
+ | MK'" | ||
+ | MJXC0(" | ||
+ | M*Y=1DDL6E3E: | ||
+ | M+': | ||
+ | M+V1+0E]*/ | ||
+ | MEXL$8; | ||
+ | MDIO/ | ||
+ | MSQE^668? | ||
+ | M-8QQ(FNXXBU" | ||
+ | M42DAWK# | ||
+ | M@2=" | ||
+ | M9(`K)`GD%7@O)TJ2RE(4O*D4X(LPA1V_*^8^C=0F' | ||
+ | M.& | ||
+ | MV8J!O' | ||
+ | M309B%9T; | ||
+ | M1DV& | ||
+ | ME# | ||
+ | M0CN+H81, | ||
+ | MB/" | ||
+ | M0QK6C; | ||
+ | M6: | ||
+ | M.H6D: | ||
+ | ML9> | ||
+ | MN*: | ||
+ | MS*$\$8[N8/ | ||
+ | MCT? | ||
+ | M1X[> | ||
+ | M@P' | ||
+ | M$V9W/ | ||
+ | M)DW7M[2RD!7](-> | ||
+ | MHN=0XKWIWN\8FMO(I8]BG%*P< | ||
+ | M)6H]F+IWHA\\%E\]4K# | ||
+ | M1PTES18# | ||
+ | M5>; | ||
+ | M[X+JF*E.# | ||
+ | MRGUD=`(D# | ||
+ | MS[IX1BGQOGOA%JG2QK& | ||
+ | M)NJ!:# | ||
+ | M_> | ||
+ | MI^'& | ||
+ | M%K!: | ||
+ | MLYGJ=`^--*S+BAX" | ||
+ | MJRSNN*& | ||
+ | MPYLP_R# | ||
+ | M_IXF7.YW? | ||
+ | MX? | ||
+ | M2!DK`B6Y$::? | ||
+ | M])`< | ||
+ | ME> | ||
+ | MS: | ||
+ | MR# | ||
+ | M0IH, | ||
+ | M: | ||
+ | MU& | ||
+ | MZAF: | ||
+ | M-DF08' | ||
+ | M+A75><' | ||
+ | M!+A& | ||
+ | M*4[; | ||
+ | M# | ||
+ | MUQ1FM< | ||
+ | M-%L? | ||
+ | M:, | ||
+ | MT# | ||
+ | MF=Q]8Q@OT2NJ@S$]II$97NK# | ||
+ | M)^YO8', | ||
+ | MCJ0WGA79Z\/ | ||
+ | MSZ=Z.VC7T)JCL8*FP3Q7R9& | ||
+ | M%&: | ||
+ | MI"; | ||
+ | M`< | ||
+ | MEVKFY9:& | ||
+ | MVEK_' | ||
+ | M7U.7#: | ||
+ | M*O, | ||
+ | M$`!O8FHS9" | ||
+ | MCAW#" | ||
+ | MW5W74\WT' | ||
+ | M; | ||
+ | MW^LS? | ||
+ | MK8=; | ||
+ | MW]_S!/ | ||
+ | MTF(RC? | ||
+ | MM%`631(U$ML' | ||
+ | MJ]6N+R]N[[Y? | ||
+ | M? | ||
+ | M)S4%]2[, | ||
+ | M48YH$(UI-)F*AY=< | ||
+ | M6_6P<" | ||
+ | M; | ||
+ | MT-LIV/ | ||
+ | MTQ# | ||
+ | M$9Y]8< | ||
+ | M!_S$)/ | ||
+ | MH^, | ||
+ | MR(GB: | ||
+ | MHJJ' | ||
+ | MTTK$, | ||
+ | MTS^*9`]-' | ||
+ | M(7> | ||
+ | MFZ(8L\' | ||
+ | M& | ||
+ | M3P22Z; | ||
+ | M97FZ& | ||
+ | M)C&: | ||
+ | M3^*(]C; | ||
+ | MC$FR262=T0A+.7K# | ||
+ | M%VG_P; | ||
+ | ME)B=8; | ||
+ | ME=C-M" | ||
+ | M<? | ||
+ | M& | ||
+ | M> | ||
+ | M& | ||
+ | M; | ||
+ | M?" | ||
+ | MN0R!^H0X^\=7E& | ||
+ | M^I^+H5YPW; | ||
+ | M**/ | ||
+ | M.XLH4KLX' | ||
+ | M!-" | ||
+ | MP`4I.'> | ||
+ | M: | ||
+ | MD/ | ||
+ | M? | ||
+ | M' | ||
+ | MFM$5& | ||
+ | M? | ||
+ | M\:# | ||
+ | M< | ||
+ | M9#?> | ||
+ | M`OR4JUR$!: | ||
+ | MPK< | ||
+ | MKHZ]H_Z2"< | ||
+ | MBNI# | ||
+ | M:? | ||
+ | MQWP# | ||
+ | M56/ | ||
+ | M(L!> | ||
+ | MM7F/> | ||
+ | M]DR& | ||
+ | MR]RTC\7B@\Q*\C+-]5+Z& | ||
+ | MTL^OT< | ||
+ | MH> | ||
+ | M: | ||
+ | M; | ||
+ | M*LUT1`TO-5KJV35)& | ||
+ | M" | ||
+ | MM0X6.0VG-& | ||
+ | MG27TC0, | ||
+ | M5V]TTY6MNVFC# | ||
+ | MAR> | ||
+ | M, | ||
+ | M-' | ||
+ | MKB8:; | ||
+ | M^]]J*)%0Y< | ||
+ | M7QI[(SD++AOII[J]`!IS%X*$0Y_& | ||
+ | MO.;, | ||
+ | M````````0*2!`````& | ||
+ | M8\<: | ||
+ | M`(&,? | ||
+ | M````0*2!, | ||
+ | MUEW'& | ||
+ | M, | ||
+ | M`````````$" | ||
+ | M" | ||
+ | M+F]56`@``ZQ7-SJC&# | ||
+ | M``````````!`I($H)0``< | ||
+ | M``@`L8+C)KDA5+G`(@``PR(```< | ||
+ | M6`@`[8Q^-^V,? | ||
+ | M```!``!`I('' | ||
+ | M)@JW-S2Y" | ||
+ | M`)N)? | ||
+ | M````0*2!: | ||
+ | M: | ||
+ | M55@(`$2#? | ||
+ | M`````0``0(" | ||
+ | +`& | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | ....... | ||
+ | .... | ||
+ | .. | ||
+ | . - fin - | ||
+ | </ |
magazines/chacking18.txt · Last modified: 2015-04-17 04:34 by 127.0.0.1