#+title: THE FROB: An Atari 2600 GameDev System for an Apple ][ #+author: Thomas Cherryhomes #+email: thom.cherryhomes@gmail.com * Purpose - Give a Quick Introduction to GameDev Systems - Talk a bit about the history of The Frob - Give In-Depth Demonstration of The Frob, and its capabilities * What is the Frob? - A Gamedev system for the ATARI VCS, using an Apple ][ - Retailed for $495.00 - Software Provided: Loader, Debugger, and Exploratory tool for VCS development - Hardware Provided: Frob Card, Frob Cartridge Adapter - Documentation Provided: The Frob: A Conoisseur's Guide; Newsletters - You provided Assembler, and a VCS. ** Definition and History - Definition: Frob (n) (MIT, slang) Any small device or object (usually hand-sized) which can be manipulated. Hand me that frob there, will you? - Etymology: Clipping of frobnicate, from frobnitz, coined circa 1958 by David R. Sawyer of the MIT Tech Model Railroad Club (TMRC).[1] Year of origin uncertain, and could be 1958–1981: frob is not listed in the TMRC dictionaries of 1959 or 1960,[2] and is first listed in the Jargon File (1981 edition), so it may date from the 1960s or 1970s. Possibly variant of or influenced by frotsus (“a protruding arm or trunnion”), which is listed in TMRC 1959 and 1960. - Designed by FrobCo (a dba of Pacific Polytechnical Corp) throughout 1981, Released in 1982. - FrobCo Founder Ken Clements describes its creation in this email: #+begin_src Hi Thom, Yes, I created the Frob and Frobco. I have long thought about writing down the whole story, so seeing you get one going is very heartfelt for me. I still have the original wire-wrapped prototype from when I reverse-engineered the VCS (over 40 years ago) to make the development system. My friend, David Daugherty, wrote the Apple II software to run it. Frobco was formed as a DBA of Pacific Polytechnical Corp. in Santa Cruz CA, of which I was President and head technologist. I formed PPC with some other recent (then late 70s) graduate technical guys from UCSC to do consulting new product development. We ended up putting microprocessors in slot machines in Las Vegas, which led to the coin-op industry and video games. When video games hit the home market with the VCS, and we found out that those games were 6502 based, same as the Apple II, it was obvious that we could use our Apples to do VCS development. That led to my reverse engineering the VCS and making the Frob to act as a ROM emulator. I had been building my own computers using microprocessors since 1975. PPC got into business trouble, and it was necessary to sell the company to a technical conglomerate, but we had no physical product, so I decided to productize our internal development system, The Frob, and start selling that so the company could show sales revenue, and get a valuation for M&A. That worked, we built and sold units, folks started writing VCS games with the product, we sold PPC, then I left and started other companies. #+end_src * How Were Games Developed For the 2600? - Target system, Atari VCS (CX2600) used 6507 Microprocessor - Method of loading software was via the cartridge port. - It had no I/O other than the console switches and the joystick ports. - So it was the same as developing software for e.g. a traffic light controller. ** The Familiar Process - Think - Write source code. - Assemble - Load - Burn onto EPROM - Place in Target System - Debug using a monitor (optionally with ICE) - Repeat until manager beats you, and you ship it. ** Anatomy of a Microprocessor Development System - Editor - Assembler - Loader - Debugger ** Editor - Write the software. - Could be line or screen oriented - Produces Source Code ** Assembler - Translate assembly instructions into machine instructions - Produce a relative or absolute binary - Different Assembler programs Target different processors. ** Loader - Translate a relative binary into an absolute binary to run on target system. - Relative binaries can be loaded to different addresses to address e.g. running with a debug monitor or to produce a release binary. ** Debugger - Resident in memory of either target or development system - Can examine target system memory, registers, and state - Can provide instruction disassembly. - Can change (patch) target system memory - Activation by Breakpoint, interrupts, or instructions. - Simplest is resident on target system. - Most complex is in-circuit-emulator (ICE) hardware that replaces CPU on target device. Such as HP 1611A with Personality module (10621A for 6502) - The Frob is a ROM simulator, rather than an ICE. ** Examples of Microprocessor Development Systems - Options Available as of October 1982 *** ATARI in-house development - Done by Consumer Entertainment Division (CED) - Editor, Assembler, and Loader tasks done on PDP-11 - Downloaded to modified Atari VCS with ICE attached via the engineer's terminal's AUX port. - ROM simulation done on modified VCS - Debugging done via HP 1611A logic analyzer *** Sluggo 1 and 2 Development Boards (Avalon-Hill in-house) - Developed by Rebecca Heineman for Avalon Hill - 4K of SRAM (2x6116 2Kx8) mapped into Language Card space - Umbilicus to cartridge slug for VCS - Editor, Assembler, Loader run on Apple ][ - No debugger or ICE. - Garry Kitchen did similar card for Space Jockey development *** CommaVid (Computer Magic) MagiCard - A target-only development system - Available to end-users for $49.95 - Machine Language monitor only. - Used keyboard controllers for hex input - Cassette interface schematics provided - Includes Conway's Game of Life as sample program *** Intel MDS-800 - Evolution of the Intel Intellec Systems - Single or Double Density floppy drives (256K or 512K) - Could run ISIS-II or CP/M (was the official Distribution version of CP/M) - Meant for Intel 8080 and later 8085 development but you can run 6502 assemblers e.g. in CP/M - Typical configuration price: $3695 for a dual drive double density system With EPROM burner and ICE. - Not available to end-users *** FutureData GenRad - $20,000 for a system with 6502 ICE, EPROM, and dual 1 megabyte disk storage - Text Editor, Assemblers, Loader, Debugger with ICE - Used by Gencomp (aka GCC) to develop titles for Warner Communications to be sold via ATARI. Also used by GCC for coin-op titles and mod-kits. *** HP 64000 - High end development system - Integrated Editor, Assemblers, Loader, and Debugger - ICE pods for many different processors, including 6502. - EPROM Burner and simulators - C and Pascal compilers available - Used e.g. by Coleco for ColecoVision and Adam development. - $30,000 for a single 64000 with ICE and EPROM functionality *** The Frob - Produced by Frobco (a dba of Pacific Polytechnical Corp) - Apple ][ Expansion Card containing 4K of SRAM and bi-directional registers - Example EXPLORER software lab demonstrating VCS registers - Program loader, saver; embeddable debugger and monitor. - Source code for all tools in both disk and printed form. - You provide your own assembler, and editor. - $495 for card, disk, manual. - Frob EPROM burner optionally available. - Versions for ATARI 5200, and ColecoVision development also made * Looking at the Frob ** Frob-26 Card - Two 2Kx8 static RAM chips - Decoding Logic - Custom logic implementing bi-directional registers - Fits in any Apple ][ slot except Slot 0. ** Frob-26 Cartridge Adapter - 24-pin Socket for EPROM or Frob Cable - 74LS04 Hex Inverter for inverting Chip Select signal - Small Jellybean cap for filtering ** Frob Cable - 24 pin DIP socketed cable - Connects the Cartridge Adapter with the Frob Card ** The Frob: A Conoisseur's Guide (Manual) - The Operations manual for the Frob - Covered Topics: + Hardware Installation + Hardware Description + Using the Frob Explorer to understand the VCS + Using Frob software tools, FLOAD, FSAVE, PMOVE, FMON, AMON + Source Code Listings for Everything ** FROB-26 Disk "Standard Release V1.3" - Copyright 1983 by Frobco - Released March 1983 - Disk Catalog: #+begin_src DISK VOLUME 254 *A 002 HELLO *A 052 AMON *T 021 FMON *B 003 FMON.OBJ *T 009 PMOVE *B 002 PMOVE.OBJ *T 106 EXPLORER *B 018 EXPLOR.OBJ *T 071 XCONTROL *A 004 EXPLOR *B 013 XCONTROL.OBJ *A 004 FSAVE *A 008 FLOAD #+end_src * Installing the Frob ** Attaching the Frob Cable - Covered in Chapter 4 of Conoisseur's Guide - Attaches to Cartridge 24-pin EPROM socket. - Attaches to Frob 24-pin EPROM socket ** Inserting Frob-26 Card into Apple ][ - Recommended slot is Slot 2, but can be in any non-zero slot - Run cable out back ** Inserting Frob-26 Cartridge Adapter into VCS - Component side facing back, cable facing up ** Inserting Frob-26 Disk - Boots into DOS 3.3 environment with FROB tools. * The Frob Explorer - Shows Status of VCS Registers - You can change them. - ...watch their effects! - ...and learn how the VCS works! ** Loading from the Frob Disk #+begin_src ] RUN EXPLOR #+end_src - EXPLOR loads EXPLORER.OBJ0 into Frob memory for VCS - EXPLOR also loads XCONTROL.OBJ0 into Apple2 for control of VCS ** Starting Explorer - Frob slot must be specified #+begin_src PROGRAM TO LOAD THE FROB EXPLORER PLEASE ENTER FROB SLOT NUMBER (1-7):2 #+end_src - PMOVE is loaded into $0300 and used to load EXPLORER.OBJ0 into Frob memory at $C200 - $C0A0 is set to $30 to: Give control to the VCS ($10) Turn on Bi-Directional Port for Debug comms ($20) - Once loaded, EXPLOR asks to turn on VCS and press RETURN - XCONTROL talks to EXPLORER to get VCS state #+begin_src FROB EXPLORER CONTROL SCREEN ENABLE:0C (PUT 40 HERE FOR SCREEN2) PLAYER1:COLOR 82 VERT 14 HORZ 30 FLP 00 IMAGE: FE 7F 63 62 7C 78 60 F2 SOUND: A:00 B:00 C:00 PLAYER2:COLOR 00 VERT 1E HORZ 30 FLP 00 IMAGE: FE 7F 63 62 7C 78 60 F6 SOUND: A:00 B:00 C:00 BACKGROUND COLOR EA OBJECT COLOR 64 OBJECT A IMAGE: 18 24 42 42 7E 42 42 CE OBJECT B IMAGE: FE 61 61 7E 7E 61 61 FE OBJECT C IMAGE: 7E FF FE F0 F0 FE FF 7E REG03:00 P1VMODE:00 P2VMODE:00 FVMODE:05 P1HRES:00 P2HRES:00 S1HRES:00 S2HRES:00 S3HRES:00 S3SHOT:00 S3HORZ:00 P1DELAY:00 P2DELAY:00 REG27:00 S1CONT:00 S2CONT:00 HZSCRL:00 NOINC:00 COLRES:00 REG2D:00 REG2E:00 REG2F:00 GENERAL WRITE LOC:0081 VAL:00 READ=2A READ ONLY LOCATIONS WITH MASK:81 REG30:00 REG31:00 REG32:00 REG33:00 REG34:00 REG35:00 REG36:00 REG37:00 REG38:00 REG39:00 REG3A:00 REG3B:00 REG3C:00 REG3D:00 REG3E:00 REG3F:00 #+end_src ** Interacting with Explorer - Left/Right Arrow <- -> keys - ; is up - / is down #+begin_src ; <- -> / #+end_src - Use keys to position over hex number fields - Enter new hex values (0-F) with keyboard. ** Changing Color Registers - Background color is currently EA. Very hard to see. - Move cursor over EA, change to 00 to make black - Color changes immediately on VCS. #+CAPTION: Hue (upper nibble) values for any Color Register | Hex | Hue | |-----+---------------| | 00 | Black | | 10 | Gold | | 20 | Orange | | 30 | Bright Orange | | 40 | Pink | | 50 | Purple | | 60 | Purple-Blue | | 70 | Blue | | 80 | Medium Blue | | 90 | Light Blue | | A0 | Torquoise | | B0 | Green Blue | | C0 | Green | | D0 | Yellow Green | | E0 | Orange Green | | F0 | Light Orange | (Hue values are for NTSC displays) #+CAPTION: Luminance (lower nibble) values for any color register | Hex | Luminance | |-----+-------------| | 00 | Darkest | | 02 | | | 04 | | | 06 | | | 08 | Medium Grey | | 0A | | | 0C | | | 0E | White | ** Changing what is displayed on Explorer kernel #+CAPTION: Enable bit values shown in Chapter 8 | Bit | Description | |-----+-------------------------------| | 0 | Player 1 | | 1 | Player 2 | | 2 | Object A | | 3 | Object B | | 4 | Object C | | 5 | Not used | | 6 | Show Screen 2 | | 7 | 1 = Display every other frame | ** Changing Player 1 Image - Reference EXPLORER listing address F170 PART2 - Set Enable to 03 to enable player 1 and 2 - Set PLAYER1 IMAGE to: 18 3C 7E DB FF 5A 81 42 Some notes: - This display kernel, assumes players are 8 lines tall. - Display kernel updates every two scanlines. (96 lines effective resolution) #+begin_src UpprLowr 84218421 |...XX...| $18 |..XXXX..| $3C |.XXXXXX.| $7E |XX.XX.XX| $DB |XXXXXXXX| $FF |.X.XX.X.| $5A |X......X| $81 |.X....X.| $42 #+end_src For Each line: - Player image is being read from RAM - Sent to Player register P1IMAG (GRP0 in ATARI docs) - Player image cleared after last line sent - Code was cribbed from COMBAT. ** Horizontal Motion - Player is moving to the left, why? - P1HORZ, P2HORZ (HMP0, HMP1 in ATARI docs) set to 30 - Can be stopped by setting to 00 - Only upper nibble is used, and is treated as signed value from +7 (left) to -8 (right) #+CAPTION: P1HORZ, P2HORZ (HMP0, HMP1) Values | Hex | Number of clocks | |-----+---------------------| | 70 | move left 7 clocks | | 60 | move left 6 clocks | | 50 | move left 5 clocks | | 40 | move left 4 clocks | | 30 | move left 3 clocks | | 20 | move left 2 clocks | | 10 | move left 1 clock | | 00 | No motion | | F0 | move right 1 clock | | E0 | move right 2 clocks | | D0 | move right 3 clocks | | C0 | move right 4 clocks | | B0 | move right 5 clocks | | A0 | move right 6 clocks | | 90 | move right 7 clocks | | 80 | move right 8 clocks | - Any horizontal motion values are applied when HZSCRL (HMOVE in ATARI docs!) is strobed. COMBAT does this at beginning of displayed frame But it can be done at the beginning of any line. ** Horizontal Motion Reset - Primary method of setting coarse object position. - used to set horizontal position of object in 15 clock increments. - Is why there are Horizontal Motion registers - Object immediately starts outputting on line when reset - Set P1HORZ (HMP0) to 00 to stop motion. - Set P1HRES (RESP0) to any value Note that the Player 1 object is now at the left of the screen. ** Player/Missile Image modes - PxVMODE (NUSIZx in ATARI docs) sets number of players/missiles to render, and how they should be spaced on the current line. #+CAPTION: Player size/copies. | Bits 0-2 | Description | |----------+----------------------| | 000 | One Object | | 001 | Two copies, close | | 010 | Two copies, medium | | 011 | Three Copies, close | | 100 | Two copies, wide | | 101 | Double size Player | | 110 | Three copies, medium | | 111 | Quad sized player | - This register also sets # of copies for missiles. #+CAPTION: Missile size | Bits 4-5 | Description | |----------+-------------| | 00 | 1 Clock | | 01 | 2 Clocks | | 10 | 4 Clocks | | 11 | 8 Clocks | - Show how changing P1VMODE, P2VMODE alter how players/missiles are displayed ** Vertical positioning - Reminder: Graphic Data fed to TIA line by line - So we have to determine whether to display a player on a line. - We can subtract the current scanline, from the player's desired vertical position and determine via carry if we need to display the graphic, and use the difference as index into the player data for the given line. Reference Line 401 ($F1A7) of EXPLORER Borrowed from COMBAT. - Change VERT to 00 Note it doesn't go all the way to the top Because the FROB logo at top is drawn by previous section of kernel - Change VERT to 60 Note it is almost off screen. Due to 2 line kernel - Change VERT back to 00 #+CAPTION: Code fragment to display player 1 image on current scanline #+begin_src F1B0:38 412 SEC ; TEST FOR VERT CLOSE TO PLAYER 1 F1B1:A5 85 413 LDA VLNCT ; GET THE VERTICAL POSITION AT THIS LINE F1B3:E5 AF 414 SBC P1VERT ; SUBTRACT THE PLAYER1 VERTICAL POSITION F1B5:90 04 415 BCC NOP1 ; IF CARRY CLEAR THEN WE ARE NOT THERE YET F1B7:C9 08 416 CMP #8 ; A NOW HOLDS THE INDEX TO THE IMAGE ARRAY F1B9:90 04 417 BCC INP1 ; IF CARRY CLEAR WE ARE IN RANGE TO SHOW A LINE OF P1 IMAGE F1BB:A9 00 418 NOP1 LDA #0 ; IF HERE, NO SHOW F1BD:F0 03 419 BEQ P1DSP F1BF:AA 420 INP1 TAX ; GO GET LINE OF IMAGE F1C0:B5 B3 421 LDA P1IMG,X F1C2:85 1B 422 P1DSP STA P1IMAG ; DISPLAY PLAYER1 IMAGE #+end_src ** Horizontal Flipping - Player objects can be flipped via P1FLIP, P2FLIP (REFP0, REFP1 in ATARI docs!) - Bit 3 reverses horizontal order of pixels written to PxIMAG (GRPx) - Set PLAYER2 FLP to 08 - Notice object is now reflected horizontally - Change HORZ values to show that reflection does not affect horizontal motion values ** Playfield Objects A, B, and C - Enabled via bits 2, 3, and 4 of ENABLE in EXPLORER - Built using the low resolution PLAYFIELD objects. Object A is built using FLDAIM (PF0 in ATARI docs) Object B is built using FLDBIM (PF1 in ATARI docs) Object C is built using FLDCIM (PF2 in ATARI docs) - Display controlled via FVMODE register (CTRLPF in ATARI docs) - FVMODE (CTRLPF) set by default for reflected display *** Altering Playfield Mode - Set ENABLE to 1C to Disable Players 0 and 1 Enable Objects A, B, and C - FVMODE (CTRLPF) set up by default for reflected display = $05 (Reflect and PFP) | Bit | Description | |-----+-----------------------------------------------------------| | 0 | Reflect Playfield | | 1 | SCORE (left side uses P0 color, right side uses P1 color) | | 2 | PFP (=1 means playfield drawn in front of players) | | 3 | Not used | | 4 | Ball Size | | 5 | Ball Size | - Bit 0 of FVMODE (CTRLPF) changes how playfield objects are sent to screen. #+begin_src |4 7|7 0|0 7|4 7|7 0|0 7| |PF0 |PF1 |PF2 |PF0 |PF1 |PF2 | If bit0 = 0, then repeat #+end_src #+begin_src |4 7|7 0|0 7|7 0|0 7|7 4| |PF0 |PF1 |PF2 |PF2 |PF1 |PF0 | If bit0 = 1, then reflect #+end_src - Change FVMODE (CTRLPF) to $00, notice that AB BA X becomes AB AB half-X - Change FVMODE (CTRLPF) to $01, notice AB AB becomes AB BA X - Change FVMODE (CTRLPF) to $03, notice that AB BA X takes on player colors - Change FVMODE (CTRLPF) to $04 Change P1 VERT to 35 Change P1 HORZ to 00 (no motion) Note how PLAYER1 object goes behind the B - Change FVMODE (CTRLPF) to $00 Note how player 1 object is now in front of B *** Altering Playfield Objects - Object A IMAGE is built using FLDAIM (PF0 in ATARI docs) Object B IMAGE is built using FLDBIM (PF1 in ATARI docs) Object C IMAGE is built using FLDCIM (PF2 in ATARI docs) - FLDAIM (PF0) uses bits 4-7 (upper four bits) - FLDBIM (PF1) uses bits 0-7 - FLDCIM (PF2) uses bits 0-7 - 20 bits of playfield. #+begin_src |4 7|7 0|0 7|4 7|7 0|0 7| |PF0 |PF1 |PF2 |PF0 |PF1 |PF2 | If bit0 = 0, then repeat #+end_src #+begin_src |4 7|7 0|0 7|7 0|0 7|7 4| |PF0 |PF1 |PF2 |PF2 |PF1 |PF0 | If bit0 = 1, then reflect #+end_src - Change Object A Image to 50 A0 50 A0 50 A0 50 A0 (Lower nibble does nothing) - Change Object B Image to 18 3C 7E FF FF FF FF FF - Change Object C Image to 22 26 2A 2B 2D 2F 34 42 - Change FVMODE to $01 Notice checkerboard reflects (flips) on either side - Change FVMODE to $00 Observe the repeated effect. ** Screen 2 (1 Line Kernel) - Screen 1 was a 2 line kernel (96 lines vertical res) - Screen 2 is a 1 line kernel (192 lines vertical res) - Last experiment is 6 digit kernel Image data for digit 1 from Player 1 image Image data for digit 2 from Player 2 Image Image Data for digit 3 from Object A image Image Data for digit 4 from Object B image Image data for digit 5 from Object C image Image data for digit 6 from Object X image - Object X image comes from 8 bytes starting at address $DE See EXPLORER reference OBJXIM - Set P1 Image to: 00 38 6C C6 C6 C6 6C 38 - Set P2 Image to: 00 18 38 18 18 18 18 7e - Set Object A Image to: 00 7C C6 06 3C E0 C0 FE - Set Object B Image to: 00 FC 06 06 7C 06 06 FC - Set Object C Image to: 00 CC CC CC FE 0C 0C 0C - Set Object X Image to: 00 FC C0 C0 FC 06 06 FC ** Sound - Two sound channels - Sound A corresponds to AUDCx - Sound B corresponds to AUDFx - Sound C corresponds to AUDVx - AUDCx is 4 bits long (0-15) #+CAPTION: AUDCx Values | Value | Description | |-------+-------------------------| | 0 | Hold at 1 | | 1 | 4 bit poly | | 2 | 4 bit poly / 15 | | 3 | 4 bit poly / 5 bit poly | | 4 | Pure tone / 2 | | 5 | Pure tone / 2 | | 6 | Pure Tone / 31 | | 7 | 5 bit poly / 2 | | 8 | 9 bit poly (NOISE) | | 9 | 5 bit poly | | A | Pure Tone / 31 | | B | Last 4 bits to 1 | | C | Pure Tone / 6 | | D | Pure Tone / 6 | | E | Pure Tone / 93 | | F | 5 Bit Poly / 6 | - AUDFx is 5 bits long (0-31) - AUDVx is 4 bits long (0-15) * Using The Frob from the Apple Monitor - See Chapter 5 of the Conoisseur's Guide "USING THE FROB" - Control Port is Offset 0 of a slot's soft switches, Therefore, if in slot 2, the control port is $C0A0 - 4K SRAM is exposed at slot's address space, e.g. slot 2 is $C200-$C2FF in 256 byte page increments, controlled by control register. - Control Register is 6 bits wide: Bits 0-3 specify which 256 byte page to expose at $Cx00 Bit 4 specifies whether Apple has control of Frob RAM (= 0), or VCS, (= 1) Bit 5 disables (= 0), or enables (= 1) the bi-directional registers. - First, we enter the Monitor #+begin_src ] CALL -151 #+end_src - Next, we specify page zero of the Frob memory to be accessible via our slot's address space #+begin_src * C0A0: 00 #+end_src - At which point we can start entering the program in hex. #+begin_src * C200: 78 D8 A2 FF 9A E8 8A 95 00 E8 * C20A: D0 FB A9 00 85 02 85 02 A9 02 * C214: 85 02 85 00 85 02 85 02 85 02 * C21E: A9 00 85 02 85 00 A9 02 85 02 * C228: A0 29 85 02 88 D0 FB 85 02 84 02 * C233: A0 C0 85 02 84 09 88 D0 F9 85 09 * C23E: A0 16 88 85 02 D0 FB 4C 0C F0 #+end_src - We need to switch to the very last page ($FF00-$FFFF) So we can set the RESET vector, required by the 6507 to start the program when the VCS is powered on. - The reset address should be $F000. That's where our program starts. - The two bytes we need to set are at addresses $FFFC, and $FFFD. These relative to our Frob memory page are now at $C2FC, and $C2FD. So once we set the page, we set those two bytes in little-endian order. #+begin_src * C0A0: 0F * C2FC: 00 F0 #+end_src - Once this is done, we can switch control of the Frob to the VCS By setting bit 4. #+begin_src * C0A0: 10 #+end_src - And we can now turn the VCS on. ** RAINBOW Listing #+begin_src 1 0000 00 00 VSYNC = $00 2 0000 00 01 VBLANK = $01 3 0000 00 02 WSYNC = $02 4 0000 00 09 COLUBK = $09 5 0000 processor 6502 6 0000 ???? 7 f000 ORG $F000 8 f000 9 f000 START 10 f000 78 SEI 11 f001 d8 CLD 12 f002 a2 ff LDX #$FF 13 f004 9a TXS 14 f005 e8 INX 15 f006 8a TXA 16 f007 95 00 LF007 STA VSYNC,X 17 f009 e8 INX 18 f00a d0 fb BNE LF007 19 f00c a9 00 LF00C LDA #$00 20 f00e 85 01 STA VBLANK 21 f010 85 02 STA WSYNC 22 f012 a9 02 LDA #$02 23 f014 85 02 STA WSYNC 24 f016 85 00 STA VSYNC 25 f018 85 02 STA WSYNC 26 f01a 85 02 STA WSYNC 27 f01c 85 02 STA WSYNC 28 f01e a9 00 LDA #$00 29 f020 85 02 STA WSYNC 30 f022 85 00 STA VSYNC 31 f024 85 80 STA $80 32 f026 a9 02 LDA #$02 33 f028 85 01 STA VBLANK 34 f02a a0 29 LDY #$29 35 f02c 85 02 LF02C STA WSYNC 36 f02e 88 DEY 37 f02f d0 fb BNE LF02C 38 f031 85 02 STA WSYNC 39 f033 84 01 STY VBLANK 40 f035 a0 c0 LDY #$C0 41 f037 85 02 LF037 STA WSYNC 42 f039 84 09 STY COLUBK 43 f03b 88 DEY 44 f03c d0 f9 BNE LF037 45 f03e 85 09 STA COLUBK 46 f040 a0 17 LDY #$17 47 f042 88 LF042 DEY 48 f043 85 02 STA WSYNC 49 f045 d0 fb BNE LF042 50 f047 4c 0c f0 JMP LF00C #+end_src * Using FSAVE - Saves entire 4K ROM space as a BSAVE file. - File address is $8000 - Uses PMOVE to move Frob RAM to Apple2 RAM. - Resulting file is 18 sectors long. ** Saving RAINBOW - With RAINBOW in Frob memory, RUN FSAVE. #+begin_src ]RUN FSAVE #+end_src - FSAVE will annouce itself And ask for the Frob slot We'll select 2 #+begin_src PROGRAM TO SAVE FROB FILES TO A BSAVE DISK FILE WHAT SLOT IS THE FROB IN?2 #+end_src - It will ask for the name of a destination file We'll call it RAINBOW.OBJ #+begin_src ENTER NAME OF FILE?RAINBOW.OBJ #+end_src - It will pause so you can insert the correct disk #+begin_src PRESS RETURN WHEN READY TO WRITE DISK? #+end_src - After a bit of disk writing, the program completes. #+begin_src SAVE PROCESS COMPLETE ] #+end_src ** What was saved? - If we go to the Apple Monitor, we can see the saved contents from $8000 to $8FFF. #+begin_src ]CALL-151 *8000L 8000- 78 SEI 8001- D8 CLD 8002- A2 FF LDX #$FF 8004- 9A TXS 8005- E8 INX 8006- 8A TXA 8007- 95 00 STA $00,X 8009- E8 INX 800A- D0 FB BNE $8007 800C- A9 00 LDA #$00 800E- 85 01 STA $01 8010- 85 02 STA $02 8012- A9 02 LDA #$02 8014- 85 02 STA $02 8016- 85 00 STA $00 8018- 85 02 STA $02 801A- 85 02 STA $02 801C- 85 02 STA $02 801E- A9 00 LDA #$00 8020- 85 02 STA $02 *L 8022- 85 00 STA $00 8024- 85 80 STA $80 8026- A9 02 LDA #$02 8028- 85 01 STA $01 802A- A0 29 LDY #$29 802C- 85 02 STA $02 802E- 88 DEY 802F- D0 FB BNE $802C 8031- 85 02 STA $02 8033- 84 01 STY $01 8035- A0 C0 LDY #$C0 8037- 85 02 STA $02 8039- 84 09 STY $09 803B- 88 DEY 803C- D0 F9 BNE $8037 803E- 85 09 STA $09 8040- A0 17 LDY #$17 8042- 88 DEY 8043- 85 02 STA $02 8045- D0 FB BNE $8042 *L 8047- 4C 0C F0 JMP $F00C 804A- 0F ??? 804B- F5 F5 SBC $F5,X 804D- F5 F5 SBC $F5,X 804F- F5 F5 SBC $F5,X 8051- F5 F5 SBC $F5,X 8053- F5 F5 SBC $F5,X 8055- F5 F5 SBC $F5,X 8057- F5 F5 SBC $F5,X 8059- F5 F5 SBC $F5,X 805B- F5 F5 SBC $F5,X 805D- F5 F5 SBC $F5,X 805F- F5 F5 SBC $F5,X 8061- F5 F5 SBC $F5,X 8063- F5 F5 SBC $F5,X 8065- F5 F5 SBC $F5,X 8067- F5 F5 SBC $F5,X 8069- F5 F5 SBC $F5,X 806B- F5 F5 SBC $F5,X 806D- F5 F5 SBC $F5,X *3D0G #+end_src * Using FLOAD - The complementary tool to FSAVE - Loads up to 4K of any BSAVE file into FROB RAM. - Can load 2K files into low ($F000-$F7FF) or high ($F800-$FFFF) RAM. ** Loading Cartridge Data with FLOAD - Off-Screen, we crack open, and read a bunch of Atari ROMs using an EPROM burner and adapter. - We collect a disk of these cartridges for study. - All of these carts are in BSAVE format - We can load them into the Frob with FLOAD and use the Apple Monitor to study them. - Disk Catalog #+begin_src ]CATALOG DISK VOLUME 254 B 010 AIR-SEA BATTLE B 018 ATLANTIS B 018 COMBAT B 018 EARTH DIES SCREAMING B 018 FAST EDDIE B 010 OUTLAW B 018 PITFALL B 018 RIVER RAID B 018 TURMOIL B 010 VIDEO OLYMPICS B 018 WORM WAR I B 018 ADVENTURE B 018 DONKEY KONG B 018 HOME RUN B 010 KABOOM B 018 KEYSTONE KAPERS B 010 NIGHT DRIVER B 018 SPACE JOCKEY B 010 BASKETBALL B 018 BERZERK B 010 BOWLING B 018 BUMPER BASH B 010 CANYON BOMBER B 018 CHALLENGE OF NEXAR B 018 DEFENDER B 018 FANTASTIC VOYAGE B 010 FLAG CAPTURE B 018 FLASH GORDON B 018 MEGAMANIA B 018 MISSILE COMMAND B 018 SPACEMASTER X-7 #+end_src *** Example 1: Pitfall - Loading PITFALL. We RUN FLOAD #+begin_src ]RUN FLOAD #+end_src - Specify Frob slot #1 #+begin_src PROGRAM TO LOAD THE FROB WHAT SLOT IS THE FROB IN?1 #+end_src - We specify PITFALL #+begin_src ENTER NAME OF FILE?PITFALL #+end_src - FLOAD gives opportunity to insert disk with ROMs #+begin_src READY FOR DISK LOAD(Y/N)?Y #+end_src - ROM loads into $8000 - It asks whether it's a 4K file, to determine the starting destination address. #+begin_src IS IT A 4K FILE?Y DOWNLOAD PROCESS COMPLETE ] #+end_src - FLOAD sets Control of Frob RAM to VCS - We turn on VCS, and Pitfall is running. - We can look in Monitor and see ROM loaded at $8000 - We can switch pages using control register and look in Frob RAM *** Example 2: Combat - Same exercise through FLOAD, except, we load a 2K ROM. #+begin_src PROGRAM TO LOAD THE FROB WHAT SLOT IS THE FROB IN?1 ENTER NAME OF FILE?COMBAT READY FOR DISK LOAD (Y/N)?Y IS IT A 4K FILE (Y/N)?N LOAD TO HIGH OR LOW MEMORY (H/L) ?L DOWNLOAD PROCESS COMPLETE #+end_src - Combat is available both at $8000 and at top of Frob memory in page 0 * A Quick look at PMOVE - Utility routine used by EXPLOR, FLOAD, FSAVE, and AMON - Used to move data from Apple memory to Frob memory ** Important offsets - PMOVE exists at $0300 (768) | Offset | Description | |--------+-----------------------------| | PM+18 | Frob Slot # | | PM+19 | Source Page ($xx00) | | PM+20 | # of pages to transfer | | PM+21 | Frob Destination Page start | ** Example use in FROBBLE #+begin_src 5 D$ = CHR$ (4) 15 PRINT D$;"BLOAD PMOVE.OBJ" 17 PM = 768: REM ADDRESS OF PMOVE 20 PRINT "LOADING FROBBLE VCS INTO FROB SLOT 1" 30 PRINT D$;"BLOAD FROBBLE.VCS.OBJ,A$8000" 40 STPAGE = 0:LNPGS = 16:LFROM = 128 50 GOSUB 1000 1000 REM FROB DOWNLOAD ROUTINE 1010 POKE PM + 18,1 1020 POKE PM + 19,LFROM 1030 POKE PM + 20,LNPGS 1040 POKE PM + 21,STPAGE 1050 CALL PM + 22 1060 RETURN #+end_src ** PMOVE Listing #+begin_src 0000: 1 * *********************** 0000: 2 * * COPYRIGHT 1982 * 0000: 3 * * FROBCO ALL RIGHTS RESERVED * 0000: 4 * * * 0000: 5 * *********************** 0000: 6 * 0000: 7 * AUTHOR: KEN CLEMENTS 0000: 8 * DATE: 10/13/82 0000: 9 * LAST MODIFIED: 1/27/83 0000: 10 * VERSION 1.3 0000: 11 * PAGE MOVE 0000: 12 * ----- NEXT OBJECT FILE NAME IS PMOVE.OBJ0 0300: 13 ORG $300 0300:A2 00 14 PMOVE LDX #0 0302:BD 00 10 15 PM1 LDA $1000,X 0305:9D 00 10 16 PM2 STA $1000,X 0308:E8 17 INX 0309:D0 F7 18 BNE PM1 0304: 19 PFROM EQU PM1+2 0307: 20 PTO EQU PM2+2 030B:8E 03 03 21 STX PFROM-1 030E:8E 06 03 22 STX PTO-1 0311:60 23 RTS 0312: 24 * 0312: 25 * 0312: 26 * HERE IS A ROUTINE TO LOAD OR 0312: 27 * UNLOAD THE FROB 0312: 28 * 0312:00 29 FSLOT DFB 0 ; SLOT NUMBER FOR THE FROB 0313:00 30 LFROM DFB 0 ; WHERE IN MEMORY TO LOAD TO/FROM 0314:00 31 LNPGS DFB 0 ; HOW MANY PAGES TO TRANSFER 0315:00 32 STPAGE DFB 0 ; WHAT FROB PAGE TO START WITH 0316: 33 * 0316:A9 03 34 DNLOAD LDA #PTO ; PICKUP LOW ORDER ADDRESS PART 031D:8D 64 03 37 STA TX+1 0320:A9 03 38 LDA #PFROM 032A:8D 76 03 42 STA TX1+1 032D:8D 88 03 43 STA TX2+1 0330:4C 4D 03 44 JMP FXFER 0333: 45 * 0333:A9 03 46 UPLOAD LDA #PFROM 033A:8D 64 03 49 STA TX+1 033D:A9 03 50 LDA #PTO 0347:8D 76 03 54 STA TX1+1 034A:8D 88 03 55 STA TX2+1 034D: 56 * 034D:AD 14 03 57 FXFER LDA LNPGS 0350:F0 34 58 BEQ EXIT ; DON'T DO FOR 0 PAGES 0352:A9 00 59 LDA #0 0354:8D 06 03 60 STA PTO-1 0357:8D 03 03 61 STA PFROM-1 035A:A9 C0 62 LDA #$C0 035C:8D 7D 03 63 STA PPOINT+2 035F:18 64 CLC 0360:6D 12 03 65 ADC FSLOT 0363:8D 07 03 66 TX STA PTO 0366:29 07 67 AND #7 ; GET SLOT NUMBER BACK 0368:18 68 CLC ; ADD IN DEV SELECT OFFSET 0369:69 08 69 ADC #8 036B:0A 70 ASL A ; SHIFT IT OVER TO 036C:0A 71 ASL A ; FORM LOWER BYTE OF 036D:0A 72 ASL A ; DEVSEL ADDRESS FOR SLOT 036E:0A 73 ASL A 036F:8D 7C 03 74 STA PPOINT+1 0372:AD 13 03 75 LDA LFROM ; MEM PAGE ADDRESS 0375:8D 04 03 76 TX1 STA PFROM 0378: 77 * 0378:AD 15 03 78 LDLOOP LDA STPAGE ; FROB START PG 037B:8D E8 03 79 PPOINT STA 1000 ; FROB PAGE SELECT 037E:20 00 03 80 JSR PMOVE 0381:CE 14 03 81 DEC LNPGS 0384:D0 01 82 BNE TX2 ; KEEP GOING UNTIL 0 0386:60 83 EXIT RTS 0387:EE 04 03 84 TX2 INC PFROM 038A:EE 15 03 85 INC STPAGE 038D:4C 78 03 86 JMP LDLOOP 0390: 87 * 0390: 88 * HERE IS A UTILITY LINKAGE TO SOME MONITOR ROUTINES: 0390: 89 * 0390: 90 * LINK TO THE PRINT CHAR ROUTINE 0390:00 91 LINK DFB 0 0391:AD 90 03 92 PCHR LDA LINK ; PUT CHAR IN A 0394:4C ED FD 93 JMP $FDED ; MONITOR ENTRY POINT 0397: 94 * LINK TO THE PRINT HEX ROUTINE 0397:AD 90 03 95 PHEX LDA LINK ; PUT BYTE IN A 039A:4C DA FD 96 JMP $FDDA ; GO DO IN MONITOR 039D: 97 * 039D: 98 * ROUTINE TO PUT OUT ONE HEX DIGIT 039D:AD 90 03 99 PHEXD LDA LINK ; GET DIGIT 03A0:29 0F 100 AND #$0F ; DUMP HIGH PART 03A2:C9 0A 101 CMP #$0A ; SEE IF IN NUMBERIC 03A4:90 05 102 BCC NONALF ; IF SO DONT OFFSET 03A6:69 B6 103 ADC #$B6 ; SHIFT IT UP 03A8:4C ED FD 104 JMP $FDED ; GO PRINT IT 03AB:69 B0 105 NONALF ADC #$B0 ; OFFSET FOR NUMBERIC 03AD:4C ED FD 106 JMP $FDED ; GO PRINT IT 03B0: 107 * 03B0: 108 * #+end_src * Writing FROBBLE tool for Drawing Player Graphics - Frobco provided source code for all tools So you could write your own. - So we write something so we can draw player graphics - Divided into three small programs working in tandem. - Using S/C Assembler 4.0 as our assembler. ** Frobble VCS - Written in S/C Assembler - Assembles to $8000 - Org's to $F000 - For each frame: Do vsync Do vblank Wait 32 scanlines Draw the 8 lines of player data. Wait Until we've drawn all scanlines Do it all again *** Frobble Listing #+begin_src 1000 ****************************** 1010 ** FROBBLE, A PLAYER EDITOR ** 1020 ** FOR THE FROB ** 1030 ****************************** 1040 ** AUTHOR: THOM CHERRYHOMES ** 1050 ****************************** 1060 ** STARTED: 03/11/2025 ** 1070 ** LAST CH: 03/11/2025 ** 1080 ****************************** 1090 ** BUILD WITH S-C ASM V4.0 ** 1100 ****************************** 1110 ** LICENSED UNDER GPL 3.0 ** 1120 ****************************** 1200 .IN VCS EQUATES 1800 VBTINT .EQ 43 ; VERTICAL BLANK TIMER VALUE 1810 TOTLSCAN .EQ $EC ; 236 SCANLINES WITH OVERSCAN 1820 DEFUBK .EQ $94 ; BLUE 1830 DEFUP0 .EQ $BC ; CYAN 1900 ** 1910 TARL .EQ $80 ; PTR FOR FMON 1920 TARH .EQ $81 ; +1 1930 COMCOD .EQ $82 ; COMMAND CODE 1940 SCLUP0 .EQ $83 ; SHADOW FOR COLUP0 1950 SCLUBK .EQ $84 ; SHADOW FOR COLUBK 1951 GRDATA .EQ $90 ; GRAPHIC DATA IN RAM 1960 * 2000 .OR $F000 2001 .TA $8000 2002 .TF FROBBLE.VCS.OBJ0 2010 START LDX #$FF ; RESET - SET UP STK PTR 2020 TXS ; X -> SP 2030 CLD ; CLEAR DECIMAL MODE 2040 LDX #$00 ; DO A MEMORY CLEAR 2050 LDA #$00 2060 CLOOP STA $00,X ; CLEAR NEXT ADDRESS 2070 DEX ; RIPPLE UP TO ZERO 2080 BNE CLOOP ; LOOP IF NOT DONE 2090 ** 2100 ** GO AHEAD AND SET DEFAULT COLORS 2110 ** 2120 SETDEF LDA #DEFUP0 2130 STA SCLUP0 2140 LDA #DEFUBK 2141 LDA #$07 ; QUAD PLAYER 2142 STA NUSIZ0 ; SIZE 2143 LDA #$10 ; MOVE A BIT 2144 STA HMP0 ; PLAYER 0 2150 STA SCLUBK 2151 LDA #$55 2152 STA GRDATA 2153 STA GRDATA+2 2154 STA GRDATA+4 2155 STA GRDATA+6 2156 LDA #$AA 2157 STA GRDATA+1 2158 STA GRDATA+3 2159 STA GRDATA+5 2160 STA GRDATA+7 2170 ** MAIN LOOP 2180 ** 2190 MAIN JSR VS ; VERTICAL SYNC 2200 JSR FMON ; HANDLE FMON 2210 JSR VE ; WAIT FOR VBLANK END 2220 JSR DISPLAY ; DISPLAY KERNEL 2230 JMP MAIN ; ENDLESS LOOP 2240 ** 2250 ** HANDLE VERTICAL SYNC 2260 ** 2270 VS LDA #$02 2280 STA WSYNC ; WAIT FOR NEXT LINE 2290 STA VBLANK ; GO BLACK 2300 STA WSYNC ; DO THREE 2310 STA WSYNC ; BLACK LINES 2320 STA WSYNC ; OF VBLANK 2330 STA VSYNC ; ENABLE VSYNC, BLACKER THAN BLACK 2340 STA WSYNC ; WAIT A LINE 2350 STA WSYNC ; AND ANOTHER 2360 LDA #$00 ; GET READY TO TURN OFF 2370 STA WSYNC ; ONE LAST VSYNC LINE 2380 STA VSYNC ; TURN OFF VSYNC. 2390 LDA #VBTINT ; LOAD 64*64 INTERVALS 2400 STA TIM64T ; INTO TIMER FOR VBLANK WAIT 2410 RTS ; WE ARE NOW AT SCANLINE 1 OF NEW FRAME 2420 ** 2430 ** END VERTICAL BLANK 2440 ** 2450 VE LDA INTIM ; DONE WAITING? 2460 BNE VE ; NOPE, WAIT SOME MORE 2470 RTS 2480 ** 2490 ** HANDLE DISPLAY KERNEL 2500 ** 2510 DISPLAY LDA #$00 2520 LDY #TOTLSCAN ; 192 SCANLINE COUNT IN Y 2530 LDA SCLUP0 ; LOAD SHADOW 2540 STA COLUP0 ; INTO PLAYER 0 COLOR 2550 LDA SCLUBK ; LOAD SHADOW 2560 STA COLUBK ; INTO BKG COLOR 2570 STA WSYNC ; WAIT 1 LINE 2580 DEY ; DECREMENT SCANLINE CTR 2590 STA HMOVE ; STROBE HMOVE 2600 STA WSYNC ; WAIT 1 LINE 2610 DEY ; DECREMENT SCANLINE CTR 2620 STA VBLANK ; TURN OFF VBLANK 2630 ** GO DOWN SCREEN A BIT 2640 LDX #32 ; COUNT DOWN 32 LINES 2641 VSCOOT STA WSYNC ; WAIT A LINE 2650 DEY ; DECREMENT SCANLINE 2660 DEX ; DECREMENT SCOOT 2670 BNE VSCOOT ; LOOP IF NOT DONE YET. 2680 ** DISPLAY PLAYER 2690 LDX #$00 ; PLAYER IS 8 LINES TALL 2700 VUPD LDA GRDATA,X ; LOAD NEXT LINE OF PLAYER DATA 2710 STA GRP0 ; STORE INTO PLAYER 0 REGISTER 2720 STA WSYNC ; WAIT A LINE 2730 INX ; COUNT UP 2740 DEY ; DECREMENT SCANLINE 2750 CPX #$08 ; 8 LINES YET? 2760 BNE VUPD ; NOPE, GO AGAIN. 2770 LDA #$00 ; ZERO OUT 2780 STA GRP0 ; PLAYER 0 DATA TO AVOID TAILS 2790 ** WSYNC UNTIL VISIBLE KERNEL DONE 2800 VWAIT STA WSYNC ; WAIT LINE 2810 DEY ; COUNT DOWN 2820 BNE VWAIT ; DO IT UNTIL WE'RE DONE 2830 RTS 2840 ** 2850 ** THIS PROGRAM EMBEDS THE FROB 2860 ** MONITOR (FMON) TO SEND PLAYER 2870 ** DATA TO THE VCS FROM THE 2880 ** APPLE II. 2890 ** 2900 ** FMON COMMANDS 2910 ** 2920 ** 10 - SET ADDRESS (2 BYTES) 2930 ** 20 - READ BYTE 2940 ** 40 - WRITE BYTE 2950 ** 2960 ** SUB-COMMANDS 2970 ** 2980 ** X0 - DO NOTHING 2990 ** X1 - POST INCREMENT 3000 ** X2 - POST DECREMENT 3010 ** 3020 ** MEMORY USAGE: 3030 ** 3040 ** TARL - TARGET ADDRESS LOW 3050 ** TARH - TARGET ADDRESS HIGH 3060 ** COMCOD - COMMAND CODE STORE 3070 ** 3080 ** EQUATES 3090 ** 3100 AR1 .EQ $FFF0 ; FMON WRITE REGISTER 3110 AR2 .EQ $FFF1 ; FMON STATUS 3120 AR3 .EQ $FFF2 ; FMON READ REGISTER 3130 ** 3140 FMON LDA AR2 ; CHECK IF COMMAND WAITING 3150 AND #$40 3160 BEQ NOCOM ; IF NOT, EXIT 3170 LDA AR3 ; GET COMMAND 3180 STA COMCOD ; STORE IN COMCOD 3190 AND #$10 3200 BEQ L1 3210 JSR SETADDR 3220 JMP FMON 3230 NOCOM RTS ; IF NO COMMAND, JUST RETURN 3240 L1 LDA COMCOD 3250 AND #$20 3260 BEQ L2 3270 JSR READ 3280 JMP FMON 3290 L2 LDA COMCOD 3300 AND #$40 3310 BEQ L3 3320 JSR WRITE 3330 L3 JMP FMON 3340 ** 3350 ** READOK 3360 ** 3370 ** CHECK IF IT'S OKAY TO READ APPLE 3380 ** 3390 READOK LDA AR2 ; CHECK STATUS 3400 AND #$40 ; OK TO READ? 3410 BEQ READOK ; NOPE, KEEP LOOKING 3420 RTS ; YES 3430 PLA 3440 DOREAD RTS 3450 ** 3460 ** 3470 WRITEOK LDA AR2 3480 AND #$80 3490 BEQ WRITEOK 3500 RTS 3510 ** 3520 ** SETADDR 3530 ** 3540 SETADDR JSR READOK 3550 LDA AR3 3560 STA TARH 3570 JSR READOK 3580 LDA AR3 3590 STA TARL 3600 RTS 3610 ** 3620 ** READ 3630 ** 3640 READ JSR WRITEOK 3650 LDY #$00 3660 LDA (TARL),Y 3670 STA AR1 3680 LDA COMCOD 3690 AND #$07 3700 BEQ RL1 3710 AND #$01 3720 BEQ RL2 3730 DEC TARL 3740 JMP RXT 3750 RL1 JMP RXT 3760 RL2 INC TARL 3770 JMP RXT 3780 RXT RTS 3790 ** 3800 ** WRITE 3810 ** 3820 WRITE JSR READOK 3830 LDA AR3 3840 LDY #$00 3850 STA (TARL),Y 3860 LDA COMCOD 3870 AND #$07 3880 BEQ WL1 3890 AND #$01 3900 BEQ WL2 3910 DEC TARL 3920 JMP WXT 3930 WL1 JMP WXT 3940 WL2 INC TARL 3950 JMP WXT 3960 WXT RTS 3970 ** 3980 ** 3990 ** #+end_src ** Frobble A2 - Control Program running on Apple ][ - 8x8 drawing area #+begin_src .█.█.█.█ █.█.█.█. .█.█.█.█ █.█.█.█. .█.█.█.█ █.█.█.█. .█.█.█.█ █.█.█.█. #+end_src - Cursor keys for moving around drawing area #+begin_src I J K M #+end_src - Clear Drawing Area with 'C' - Program Loop: Display graphic pixel data Get key Update Graphic Data Send Graphic Data to VCS Do it all again. *** Frobble A2 Listing #+begin_src 1000 **************************** 1010 ** ** 1020 ** FROBBLE - A PLAYER GFX ** 1030 ** EDITOR FOR THE FROB ** 1040 ** ** 1050 **************************** 1100 ** 1110 ** EQUATES 1120 ** 0020- 1121 CPXS .EQ $20 ; SET PIXEL CHARACTER 00AE- 1122 CPXU .EQ $AE ; UNSET PIXEL CHARACTER FC58- 1130 HOME .EQ $FC58 ; CLEAR AND HOME SCREEN FDED- 1140 COUT .EQ $FDED ; OUTPUT A TO SCREEN 0024- 1150 CH .EQ $24 ; X SCR POS 0025- 1160 CV .EQ $25 ; Y SCR POS FD0C- 1170 RDKEY .EQ $FD0C ; READ KEY TO A FB5B- 1180 TABV .EQ $FB5B ; PLACE CURSOR 0010- 1190 SACMD .EQ $10 ; SET ADDRESS CMD 0020- 1200 RDCMD .EQ $20 ; READ ADDRESS CMD 0040- 1210 WRCMD .EQ $40 ; WRITE ADDRESS CMD 0085- 1220 FSTAT .EQ $85 ; PTR TO STATUS REG 0087- 1230 FDATA .EQ $87 ; PTR TO DATA 0089- 1240 RDHGH .EQ $89 ; READ HIGH 0090- 1250 RDLOW .EQ $90 ; READ LOW 1999 .TF FROBBLE.A2.OBJ0 0800- 20 58 FC 2000 BEGIN JSR HOME ; CLEAR SCREEN 0803- 20 C2 09 2001 JSR INIT ; SET UP 0806- 20 49 09 2010 MAIN JSR DISP 0809- 20 79 08 2011 JSR UPDVCS 080C- 20 B4 09 2020 JSR RSETC 080F- 20 0C FD 2060 JSR RDKEY ; GET KEY 0812- C9 C3 2070 KC CMP #$C3 0814- D0 03 2080 BNE KI ; NOPE 0816- 20 E6 08 2090 JSR CLRDATA ; CLEAR GRAPHIC DATA 0819- C9 C9 2100 KI CMP #$C9 ; 'I' ? 081B- D0 06 2110 BNE KM ; NOPE 081D- 20 01 09 2120 JSR KUP ; YES, DO UP 0820- 4C 06 08 2130 JMP MAIN ; GO BACK 0823- C9 CD 2140 KM CMP #$CD ; 'M' 0825- D0 06 2150 BNE KJ ; NOPE 0827- 20 0D 09 2160 JSR KDN ; YES, DO DN 082A- 4C 06 08 2170 JMP MAIN 082D- C9 CA 2180 KJ CMP #$CA ; 'J' ? 082F- D0 06 2190 BNE KK ; NOPE 0831- 20 19 09 2200 JSR KLF ; GO LEFT 0834- 4C 06 08 2210 JMP MAIN ; BTM 0837- C9 CB 2220 KK CMP #$CB ; 'K' ? 0839- D0 06 2230 BNE KS ; NOPE 083B- 20 25 09 2240 JSR KRT ; GO RIGHT 083E- 4C 06 08 2250 JMP MAIN ; BTM 0841- C9 A0 2260 KS CMP #$A0 ; SPACE? 0843- D0 C1 2270 BNE MAIN ; NOPE 0845- 20 31 09 2280 JSR KTB ; TOGGLE BIT AT CURSOR 0848- 4C 06 08 2290 JMP MAIN ; AND BACK. 3000 ** 3010 ** SET VCS ADDRESS 3020 ** 084B- A0 00 3030 SETADR LDY #$00 084D- B1 85 3040 SA1 LDA (FSTAT),Y 084F- 10 FC 3050 BPL SA1 0851- A9 10 3060 LDA #SACMD 0853- 91 87 3070 STA (FDATA),Y 0855- B1 85 3080 SA2 LDA (FSTAT),Y 0857- 10 FC 3090 BPL SA2 0859- A5 89 3100 LDA RDHGH 085B- 91 87 3110 STA (FDATA),Y 085D- B1 85 3120 SA3 LDA (FSTAT),Y 085F- 10 FC 3130 BPL SA3 0861- A5 90 3140 LDA RDLOW 0863- 91 87 3150 STA (FDATA),Y 0865- 60 3160 RTS 3200 ** 3210 ** VCS WRITE 3220 ** 0866- 48 3230 WRITE PHA 0867- A0 00 3240 LDY #$00 0869- B1 85 3250 WTR1 LDA (FSTAT),Y 086B- 10 FC 3260 BPL WTR1 086D- A9 40 3270 LDA #WRCMD 086F- 91 87 3280 STA (FDATA),Y 0871- B1 85 3290 WTR2 LDA (FSTAT),Y 0873- 10 FC 3300 BPL WTR2 0875- 68 3310 PLA 0876- 91 87 3320 STA (FDATA),Y 0878- 60 3330 RTS 4000 ** 4010 ** UPDATE PLAYER GRAPHIC ON VCS 4020 ** 0879- A9 00 4030 UPDVCS LDA #$00 087B- 85 89 4040 STA RDHGH 087D- A9 90 4050 LDA #$90 087F- 85 90 4060 STA RDLOW 0881- 20 4B 08 4070 JSR SETADR 0884- AD D6 09 4080 LDA GRDATA 0887- 20 66 08 4090 JSR WRITE 088A- A9 91 4100 LDA #$91 088C- 85 90 4110 STA RDLOW 088E- 20 4B 08 4120 JSR SETADR 0891- AD D7 09 4130 LDA GRDATA+1 0894- 20 66 08 4140 JSR WRITE 0897- A9 92 4150 LDA #$92 0899- 85 90 4160 STA RDLOW 089B- 20 4B 08 4170 JSR SETADR 089E- AD D8 09 4180 LDA GRDATA+2 08A1- 20 66 08 4190 JSR WRITE 08A4- A9 93 4200 LDA #$93 08A6- 85 90 4210 STA RDLOW 08A8- 20 4B 08 4220 JSR SETADR 08AB- AD D9 09 4230 LDA GRDATA+3 08AE- 20 66 08 4240 JSR WRITE 08B1- A9 94 4250 LDA #$94 08B3- 85 90 4260 STA RDLOW 08B5- 20 4B 08 4270 JSR SETADR 08B8- AD DA 09 4280 LDA GRDATA+4 08BB- 20 66 08 4290 JSR WRITE 08BE- A9 95 4300 LDA #$95 08C0- 85 90 4310 STA RDLOW 08C2- 20 4B 08 4320 JSR SETADR 08C5- AD DB 09 4330 LDA GRDATA+5 08C8- 20 66 08 4340 JSR WRITE 08CB- A9 96 4350 LDA #$96 08CD- 85 90 4360 STA RDLOW 08CF- 20 4B 08 4370 JSR SETADR 08D2- AD DC 09 4380 LDA GRDATA+6 08D5- 20 66 08 4390 JSR WRITE 08D8- A9 97 4400 LDA #$97 08DA- 85 90 4410 STA RDLOW 08DC- 20 4B 08 4420 JSR SETADR 08DF- AD DD 09 4430 LDA GRDATA+7 08E2- 20 66 08 4440 JSR WRITE 08E5- 60 4450 RTS 8000 ** 8010 ** CLEAR GRAPHIC DATA 8020 ** 08E6- A9 00 8030 CLRDATA LDA #$00 08E8- 8D D6 09 8040 STA GRDATA 08EB- 8D D7 09 8050 STA GRDATA+1 08EE- 8D D8 09 8060 STA GRDATA+2 08F1- 8D D9 09 8070 STA GRDATA+3 08F4- 8D DA 09 8080 STA GRDATA+4 08F7- 8D DB 09 8090 STA GRDATA+5 08FA- 8D DC 09 8100 STA GRDATA+6 08FD- 8D DD 09 8110 STA GRDATA+7 0900- 60 8120 RTS 8200 ** 8210 ** 'I' UP 8220 ** 0901- AC DF 09 8230 KUP LDY CY ; CHECK CY 0904- C0 00 8231 CPY #$00 ; ALREADY AT 0? 0906- F0 04 8240 BEQ KUPX ; IF 0, EXIT 0908- 88 8250 DEY ; Y-- 0909- 8C DF 09 8260 STY CY ; STORE IT 090C- 60 8270 KUPX RTS 8300 ** 8310 ** 'M' DOWN 8320 ** 090D- AC DF 09 8330 KDN LDY CY ; CHECK CY 0910- C0 07 8340 CPY #$07 ; BOTTOM? 0912- F0 04 8350 BEQ KDNX ; YES, EXIT 0914- C8 8360 INY ; Y++ 0915- 8C DF 09 8370 STY CY ; STORE IT. 0918- 60 8380 KDNX RTS 8400 ** 8410 ** 'J' LEFT 8420 ** 0919- AE DE 09 8430 KLF LDX CX ; CHECK CX 091C- E0 00 8440 CPX #$00 ; AT LEFT? 091E- F0 04 8450 BEQ KLFX ; YES EXIT. 0920- CA 8460 DEX ; X-- 0921- 8E DE 09 8470 STX CX ; STORE IT. 0924- 60 8480 KLFX RTS 0925- AE DE 09 8500 KRT LDX CX ; CHECK CX 0928- E0 07 8510 CPX #$07 ; AT RIGHT? 092A- F0 04 8520 BEQ KRTX ; YES, EXIT. 092C- E8 8530 INX ; X++ 092D- 8E DE 09 8540 STX CX ; STORE IT 0930- 60 8550 KRTX RTS ; BYE 8600 ** 8610 ** TOGGLE BIT AT CX,CY 8620 ** 0931- AC DF 09 8630 KTB LDY CY ; GET Y 0934- AE DE 09 8640 LDX CX ; GET X 0937- B9 D6 09 8650 LDA GRDATA,Y ; GET GRAPHIC DATA FOR ROW 093A- 5D 41 09 8660 EOR EORTAB,X ; EOR THE BIT 093D- 99 D6 09 8670 STA GRDATA,Y ; STORE IT BACK 0940- 60 8680 RTS ; DONE. 0941- 80 40 20 0944- 10 08 04 0947- 02 01 8690 EORTAB .HS 8040201008040201 9000 ** 9010 ** DISPLAY GRAPHIC DATA GRDATA 9020 ** 0949- 20 58 FC 9030 DISP JSR HOME 094C- AD D6 09 9039 LDA GRDATA 094F- 20 7D 09 9040 JSR BY2PX 0952- AD D7 09 9050 LDA GRDATA+1 0955- 20 7D 09 9060 JSR BY2PX 0958- AD D8 09 9070 LDA GRDATA+2 095B- 20 7D 09 9080 JSR BY2PX 095E- AD D9 09 9090 LDA GRDATA+3 0961- 20 7D 09 9100 JSR BY2PX 0964- AD DA 09 9110 LDA GRDATA+4 0967- 20 7D 09 9120 JSR BY2PX 096A- AD DB 09 9130 LDA GRDATA+5 096D- 20 7D 09 9140 JSR BY2PX 0970- AD DC 09 9150 LDA GRDATA+6 0973- 20 7D 09 9160 JSR BY2PX 0976- AD DD 09 9170 LDA GRDATA+7 0979- 20 7D 09 9180 JSR BY2PX 097C- 60 9190 RTS 9200 ** 9210 ** DISPLAY SINGLE ROW OF DATA 9220 ** 097D- 20 A2 09 9230 BY2PX JSR BYDSP 0980- 0A 9240 ASL 0981- 20 A2 09 9250 JSR BYDSP 0984- 0A 9260 ASL 0985- 20 A2 09 9270 JSR BYDSP 0988- 0A 9280 ASL 0989- 20 A2 09 9290 JSR BYDSP 098C- 0A 9300 ASL 098D- 20 A2 09 9310 JSR BYDSP 0990- 0A 9320 ASL 0991- 20 A2 09 9330 JSR BYDSP 0994- 0A 9340 ASL 0995- 20 A2 09 9350 JSR BYDSP 0998- 0A 9360 ASL 0999- 20 A2 09 9370 JSR BYDSP 099C- A9 8D 9380 LDA #$8D ; 099E- 20 ED FD 9381 JSR COUT ; DISPLAY IT 09A1- 60 9390 RTS 9400 ** 9410 ** DISPLAY BIT7 OF BYTE IN A 9420 ** 09A2- 10 08 9430 BYDSP BPL BY1 09A4- 48 9440 PHA 09A5- A9 20 9450 LDA #CPXS ; SHOW PIXEL SET 09A7- 20 ED FD 9460 JSR COUT ; DISPLAY IT 09AA- 68 9470 PLA ; RESTORE A 09AB- 60 9480 RTS 09AC- 48 9490 BY1 PHA ; SAVE A 09AD- A9 AE 9500 LDA #CPXU ; SHOW PIXEL UNSET 09AF- 20 ED FD 9510 JSR COUT ; DISPLAY IT 09B2- 68 9520 PLA ; RESTORE A 09B3- 60 9530 RTS 09B4- AD DE 09 9600 RSETC LDA CX 09B7- 85 24 9610 STA CH 09B9- AD DF 09 9620 LDA CY 09BC- 85 25 9630 STA CV 09BE- 20 5B FB 9640 JSR TABV 09C1- 60 9670 RTS 9700 ** 9710 ** INIT 9720 ** 09C2- A9 C0 9730 INIT LDA #$C0 ; $C000 09C4- 85 86 9740 STA FSTAT+1 ; STORE HI 09C6- 85 88 9750 STA FDATA+1 ; STORE HI 09C8- A9 90 9760 LDA #$90 ; $C090 09CA- 85 85 9770 STA FSTAT ; STORE LO 09CC- A9 91 9780 LDA #$91 ; $C091 09CE- 85 87 9790 STA FDATA ; STORE LO 09D0- A9 30 9791 LDA #$30 ; TURN ON BIDI 09D2- 8D 90 C0 9792 STA $C090 ; TO SLOT 1 09D5- 60 9800 RTS ; BACK TO MAIN 9801 9900 ** 9910 ** GRAPHICAL DATA 9920 ** 09D6- 55 AA 55 09D9- AA 55 AA 09DC- 55 AA 9930 GRDATA .HS 55AA55AA55AA55AA 09DE- 00 9940 CX .HS 00 ; CURSOR X 09DF- 00 9950 CY .HS 00 ; CURSOR Y SYMBOL TABLE 0800- BEGIN 09AC- BY1 097D- BY2PX 09A2- BYDSP 0024- CH 08E6- CLRDATA FDED- COUT 0020- CPXS 00AE- CPXU 0025- CV 09DE- CX 09DF- CY 0949- DISP 0941- EORTAB 0087- FDATA 0085- FSTAT 09D6- GRDATA FC58- HOME 09C2- INIT 0812- KC 090D- KDN 0918- KDNX 0819- KI 082D- KJ 0837- KK 0919- KLF 0924- KLFX 0823- KM 0925- KRT 0930- KRTX 0841- KS 0931- KTB 0901- KUP 090C- KUPX 0806- MAIN 0020- RDCMD 0089- RDHGH FD0C- RDKEY 0090- RDLOW 09B4- RSETC 084D- SA1 0855- SA2 085D- SA3 0010- SACMD 084B- SETADR FB5B- TABV 0879- UPDVCS 0040- WRCMD 0866- WRITE 0869- WTR1 0871- WTR2 #+end_src ** FROBBLE Applesoft Program - Easy to use tool program to tie it together - Analogous to EXPLOR - Uses PMOVE to load FROBBLE.VCS into FROB Ram. *** FROBBLE program listing #+begin_src 1 HIMEM: 32767 2 HOME 3 REM FROBBLE VERSION 1.0 5 D$ = CHR$ (4) 10 REM FROBBLE PLAYER DRAW TO FROB 15 PRINT D$;"BLOAD PMOVE.OBJ" 17 PM = 768: REM ADDRESS OF PMOVE 20 PRINT "LOADING FROBBLE VCS INTO FROB SLOT 1" 30 PRINT D$;"BLOAD FROBBLE.VCS.OBJ,A$8000" 40 STPAGE = 0:LNPGS = 16:LFROM = 128 50 GOSUB 1000 60 PRINT "LOADED INTO VCS." 70 POKE 49296,48: REM TRANSFER CONTROL TO VCS WITH BI-DI PORT ON 80 PRINT "TURN ON VCS AND HIT RETURN"; 90 INPUT A$ 100 PRINT D$;"BRUN FROBBLE.A2.OBJ0" 1000 REM FROB DOWNLOAD ROUTINE 1010 POKE PM + 18,1 1020 POKE PM + 19,LFROM 1030 POKE PM + 20,LNPGS 1040 POKE PM + 21,STPAGE 1050 CALL PM + 22 1060 RETURN #+end_src * Basic Development Workflow - Write Program Code - Assemble it - Load it into Frob with FLOAD - Test it. - Repeat ** Scaffold - Take bits from reverse engineering combat - Display a stable frame - Background color *** SCAFFOLD listing #+begin_src 1000 ** 1010 ** VCS KERNEL SCAFFOLD 1020 ** 1030 .OR $F000 1040 .TA $8000 1050 .TF SCAFFOLD.OBJ 1060 .IN VCS EQUATES 1100 ** 1110 ** RESET, CLEAR ALL 1120 ** 1130 RSET SEI 1140 CLD 1150 LDX #$FF 1160 TXS 1170 LDA #$00 1180 LDX #$00 1190 CLOOP STA $00,X 1200 DEX 1210 BNE CLOOP 1300 ** 1310 ** TOP OF FRAME, VSYNC 1320 ** 1330 VS LDA #$02 1340 STA WSYNC ; WAIT 1 LINE BEFORE... 1350 STA VBLANK ; ENABLING VBLANK 1360 STA WSYNC ; WAIT 3 MORE 1370 STA WSYNC ; LINES... 1380 STA WSYNC ; ... 1390 STA VSYNC ; START VSYNC 1400 STA WSYNC ; THREE MORE 1410 STA WSYNC ; LINES... 1420 LDA #$00 ; BEFORE WE... 1430 STA WSYNC ; DISABLE... 1440 STA VSYNC ; VSYNC! 1450 ** 1460 ** WE ARE NOW AT TOP OF FRAME... 1470 ** WE CAN DO LOGIC HERE, AND/OR 1480 ** WAIT FOR A TIMER TO COMPLETE. 1490 ** 1500 LDA #43 ; SET TIMER 1510 STA TIM64T ; 43*64CYCLES 1520 VE LDA INTIM ; R WE DONE YET? 1530 BNE VE ; NOPE. WAIT. 1600 ** 1610 ** WE SHOULD NOW BE IN VISIBLE 1620 ** FRAME, START DISPLAYING STUFF 1630 ** 1640 DSPLY LDA #$00 ; TURN OFF 1650 STA WSYNC ; ... 1660 STA VBLANK ; VBLANK 1670 LDA #$94 ; SET LIGHT BLUE 1680 STA COLUBK ; TO BKG COLR 1690 LDX #$EC ; SCAN CTR 1700 DLOOP STA WSYNC ; WAIT 4 NXT LINE 1710 DEX ; DECREMENT LN 1720 BNE DLOOP ; DONE? NO. 1770 JMP VS ; BACK TO TOP #+end_src *** Load into FLOAD - Load using FLOAD - Set RESET vector using Apple monitor - Transfer control to FROB. * The Bi-directional ports - One 2-bit port for status Uses bits 6 and 7 to indicate which direction is ready for other side to read - For Apple2, this is $C0x0, e.g. slot 2 is $C0A0 - The VCS side is $FFF1 | Bit | Description | |-----+-----------------------| | 0-5 | Unused, float | | 6 | Apple2 ready for byte | | 7 | VCS ready for byte | - One 8-bit port for data. - For apple2, this is $C0x1, e.g. slot 2 is $C0A1 - Since the VCS lacks R/W, this port must be bifurcated to two addresses: | Address | Description | |---------+------------------| | $FFF0 | Write to Apple2 | | $FFF2 | Read from Apple2 | ** Modifying Scaffold to show operation of Data port - Modify Line 1670 to load COLUBK from $FFF2 #+begin_src 1670 LDA $FFF2 ; READ FROM DATA PORT #+end_src - Use FLOAD to load into RAM - Set RESET to F000 #+begin_src * C0A0: 0F * C2FC: 00 F0 #+end_src - Turn on Bi-Directional port #+begin_src * C0A0: 30 #+end_src - Change background color by altering data port #+begin_src * C0A1: 23 #+end_src * FMON - The Monitor - Interactive Debugger for VCS Game Development - AMON which runs on Apple2 - FMON runs on VCS - Both communicate using Bi-Directional ports ** Starting AMON - Started by loading AMON on Apple. - As with all Frob programs, you specify slot # of the Frob so both PMOVE and the bi-directional comms code can work. - You specify whether to load a binary, and to which hex address. - You specify whether you wish to load FMON. Most features requre it. - If FMON is loaded, VCS power cycling is prompted, and FMON is injected into the VCS, with break point near the RAM stack. - AMON then prompts for commands, until you exit with ^C. #+begin_src ]RUN FMON FROB MONITOR PROGRAM WHAT IS THE FROB SLOT NUMBER (1,7)?2 DO YOU WISH TO LOAD A FILE? (Y/N)Y WHAT IS THE FILE NAME?/GAMEDEV/EMBARGO/EMBARGO.0 LOAD TO WHAT VCS HEX ADDRESS?F000 DO YOU WISH TO USE FMON? (Y/N)Y PLEASE TURN ON VCS AND HIT RETURN SETTING UP...READY > #+end_src ** Features - All commands have common syntax of the first argument, followed by command character, followed by a range. #+begin_src >F000.10 #+end_src *** Read RAM, ROM, or Registers '.' - Displays the hex bytes at the specified memory location and range. - Displays 8 hex values per line. - If value is ROM, it's read from shadow area. -- Anything else, a READ MEMORY FMON command is sent to VCS for each byte. - Display first 16 bytes of EMBARGO code #+begin_src >F000.10 F000: 78 D8 A2 FF 9A A9 00 A2 F008: 00 95 00 CA D0 FB A9 05 > #+end_src - Take a look at current value of RIOT timer (INTIM) #+begin_src >0284 0284: DE >0284 0284: 81 #+end_src **** READ MEMORY (FMON) Details - Command Byte $10 - Input Parameter 1: HIGH Byte of address - Input Parameter 2: LOW Byte of address - Input Parameter 3: # of bytes to read (0-255) - All addresses are 16 bits - Can only read up to 255 bytes in one call - Must be called to read at least 1 byte - AMON issues multiple READ MEMORY calls in multiples of 255 bytes as needed. *** Alter RAM, ROM, or Registers '<' - Writes the specified byte argument to the specified address. - Only one byte at a time. - Writes to ROM are written to shadow memory and then sent to FROB. - Change P0 score position in Embargo at $F146 #+begin_src >F146<00 F146:00 >F000G WARNING -- STACK POINTER SET AT 00 DO YOU WISH TO WAIT FOR A BREAK? (Y/N)N >RESTORE COMMUNICATION IS NOW ON TURN THE VCS OFF AND ON TO GET TO FMON >F146<37 F146:37 >F000G WARNING -- STACK POINTER SET AT 00 DO YOU WISH TO WAIT FOR A BREAK? (Y/N)N > #+end_src **** WRITE MEMORY (FMON) Details - Command Byte $20 - Input Parameter 1: HIGH byte of address - Input Parameter 2: LOW byte of address - Input Parameter 3: # of bytes to write (0-255) - Input Parameter n: Byte n to write. - While AMON uses multiple byte write to inject Go Indirect program, the user can only write a single byte from the command prompt. *** Disassemble (List) Memory 'L' - Roughly equivalent to Apple Monitor - Disassembles single instruction at address if no length is provided #+begin_src >F000L F000:78 SEI #+end_src - Disassembles range of instructions if range provided. #+begin_src >F000L10 F000:78 SEI F001:D8 CLD F002:A2 FF LDX #$FF F004:9A TXS F005:A9 00 LDA #$00 F007:A2 00 LDX #$00 F009:95 00 STA $00,X F00B:CA DEX F00C:D0 FB BNE $F009 F00E:A9 05 LDA #$05 > #+end_src - If 'L' is specified, without an address, 30 bytes from the last location listed will be displayed. *** Register Display 'R' - Displays the contents of each register, persisted after a breakpoint. - Only makes sense after a break point. #+begin_src >F01CP >F000G WARNING -- STACK POINTER SET AT 0000 DO YOU WISH TO WAIT FOR A BREAK? (Y/N)Y WAITING FOR BREAK POINT BREAK POINT ENCOUNTERED >R A=05 X=00 Y=00 S=FF P=34 > #+end_src - P = Hex value of processor flags | Bit | Description | |-----+------------------| | 7 | Negative | | 6 | Overflow | | 5 | --- | | 4 | BRK | | 3 | Interrupt Masked | | 2 | Zero | | 1 | Carry | *** Modify Registers 'M' - Modify any 6502 register. - Only makes sense when reacting to a breakpoint #+begin_src >M WHICH REGISTER? (A,X,Y,S,P)X NEW VALUE (HEX)=22 >R A=05 X=22 Y=00 S=FF P=34 > #+end_src *** Go from Address or Breakpoint 'G' - Tells the VCS to start executing code. - Two modes * Go from Breakpoint (G) * Go from Address (xxxxG) - If an address is specified, program execution starts here. - You can ask to wait for a break, if no, control is returned to the monitor - You can modify ROM, but you can't modify RAM or registers whilst VCS is running. It will hang. You can power cycle the VCS, and type RESTORE to restore communication. #+begin_src >F000G WARNING -- STACK POINTER SET AT 00 DO YOU WISH TO WAIT FOR A BREAK? (Y/N)N >RESTORE COMMUNICATION IS NOW ON TURN THE VCS OFF AND ON TO GET TO FMON > #+end_src - If a breakpoint is placed with P, and you wish to wait for a break, G will pause AMON until the breakpoint is reached. #+begin_src >F000L16 F000:78 SEI F001:D8 CLD F002:A2 FF LDX #$FF F004:9A TXS F005:A9 00 LDA #$00 F007:A2 00 LDX #$00 F009:95 00 STA $00,X F00B:CA DEX F00C:D0 FB BNE $F009 F00E:A9 05 LDA #$05 F010:85 82 STA $82 F012:85 84 STA $84 F014:85 86 STA $86 >F00EP >F000G WARNING -- STACK POINTER SET AT 00 DO YOU WISH TO WAIT FOR A BREAK? (Y/N)Y WAITING FOR BREAK POINT BREAK POINT ENCOUNTERED >80.80 0080:00 00 00 00 00 00 00 00 0088:00 00 00 00 00 00 00 00 0090:00 00 00 00 00 00 00 00 0098:00 00 00 00 00 00 00 00 00A0:00 00 00 00 00 00 00 00 00A8:00 00 00 00 00 00 00 00 00B0:00 00 00 00 00 00 00 00 00B8:00 00 00 00 00 00 00 00 00C0:00 00 00 00 00 00 00 00 00C8:00 00 00 00 00 00 00 00 00D0:00 00 00 00 00 00 00 00 00D8:00 00 00 00 00 00 00 00 00E0:00 00 00 00 00 00 00 00 00E8:00 00 00 00 00 00 00 00 00F0:00 00 00 00 00 00 00 00 00F8:00 00 00 00 00 00 00 00 #+end_src - If G is done without an address, execution will continue from the breakpoint. #+begin_src >G DO YOU WISH TO WAIT FOR A BREAK? (Y/N)N > #+end_src *** Place Breakpoint 'P' - Allows you to place a breakpoint at given address #+begin_src >F00EP > #+end_src - Only one breakpoint can be placed at a time. - Internally, AMON places a BRK statement there. And saves the three bytes starting there for safe keeping. The shadow copy is unaffected. - If you wish to set the breakpoint somewhere else, use the 'K' command to remove the previous one before using the 'P' command again. *** Kill Breakpoint 'K' - Removes (kills) the previously set break-point set by the 'P' command. Restores the instructions saved by AMON into the running FROB memory. *** Start target software 'S' - Sets Frob memory under VCS control ($10 in control register) - Typically must set the reset vector if FMON has been loaded. - You'll need to power cycle the VCS. #+begin_src >FFFC<00 FFFC:00 >FFFDS FROB MEMORY NOW UNDER VCS CONTROL (COMMUNICATION WITH FMON IS TURNED OFF) PLEASE POWER UP THE VCS TO START IT > #+end_src *** Halt 'H' - Switches control of FROB memory to Apple2 ($00 in control register) - Re-enter FMON with RESTORE #+begin_src >H FROB MEMORY NOW UNDER APPLE CONTROL > #+end_src *** RESTORE communication - Sets control to VCS, while turning on bi-directional port. ($30 in control register) - Does not change RESET vector. - If FMON is loaded, it will let you know to restart the VCS. #+begin_src >RESTORE COMMUNICATION IS NOW ON TURN THE VCS OFF AND ON TO GET TO FMON #+end_src ** FMON In Detail - FMON runs on the VCS - Tiny. Less than 256 bytes - Lives in last page of ROM space. - Minimal logic: + RESET Entry + IRQ (BRK) Vector + Command Processing + Comm Package + Command Routines + GetAdr Subroutine - Does not maintain a display kernel at all. *** RESET Entry ($FF52) - Set up stack pointer (this doesn't get passed back to AMON immediately, though) - Clear the receive buffer - Fall into Command Loop #+begin_src FF52: 86 RESTART EQU * FF52:A2 FF 87 LDX #$FF ; SETUP STACK POINTER FF54:9A 88 TXS FF55:D8 89 CLD ; CLEAR DECIMAL MODE FF56:AD F2 FF 90 LDA RDATA ; CLEAR BUFFER FF59: 91 * FF59: 92 * FALL INTO COMMAND LOOP FF59: 93 * #+end_src *** IRQ (BRK) Vector ($FF59) - How breakpoints are implemented - Straight into command loop *** Command loop (processing) - Handles grabbing command byte - Compares and jumps as needed. | Cmd Byte | Description | |----------+---------------| | $10 | Read Mem | | $20 | Write Mem | | $30 | Go from Break | | $40 | Go Indirect | #+begin_src FF59: 94 * FF59: 95 * THIS IS THE COMMAND LEVEL FF59: 96 * FF59:20 A1 FF 97 CI JSR RBA ; GET A POSSIBLE COMMAND BYTE FF5C:C9 10 98 CMP #$10 ; DO WE READ MEM? FF5E:F0 5B 99 BEQ RM ; IF SO DO IT FF60:C9 20 100 CMP #$20 ; DO WE WRITE MEM? FF62:F0 66 101 BEQ WM ; IF SO DO IT FF64:C9 30 102 CMP #$30 ; DO WE GO FROM BREAK? FF66:F0 07 103 BEQ GO ; IF SO DO IT FF68:C9 40 104 CMP #$40 ; DO WE JUMP INDIRECT? FF6A:D0 ED 105 BNE CI ; IF NOT KEEP LOOPING FF6C:4C D9 FF 106 JMP GOI ; ELSE DO THE JMP INDIRECT #+end_src *** Comms Package - Performs communication to and from the Apple2 - Uses WDATA ($FFF0) and RDATA ($FFF2) to transfer data. These are the same 8-bit register, but bifurcated to deal with a lack of R/W signal. - Uses PSTAT ($FFF1) to indicate whether the VCS can transmit (bit 7), or whether the Apple2 can trasmit (bit 6). - Using these bits means that a BIT instruction can quickly test for readiness. #+begin_src FF98: 137 * FF98: 138 * HERE IS THE COMM PACKAGE FF98: 139 * FF98:2C F1 FF 140 WBA BIT PSTAT ; GET READY TO WRITE A BYTE TO THE APPLE FF9B:10 FB 141 BPL WBA ; WAIT FOR WRITE OK FLAG FF9D:8D F0 FF 142 STA WDATA ; THEN DO IT AND RETURN FFA0:60 143 RTS FFA1: 144 * FFA1: 145 * FFA1:2C F1 FF 146 RBA BIT PSTAT ; GET READY TO READ SOMETHING FFA4:50 FB 147 BVC RBA ; WAIT FOR READ READY BIT FFA6:AD F2 FF 148 LDA RDATA ; READ THE DATA FFA9:60 149 RTS ; THEN DONE FFAA: 150 * FFAA: 151 * FFAA: 152 * #+end_src - In each case, the routine blocks until the appropriate ready bit is 1 - And then the resulting value is either loaded or stored. *** Command Routines - Processed in the CI routine via a stack of compares. - Any invalid command bytes result in a loop to read another command byte. **** Read Memory - Send bytes at address to Apple2. - Call GETADR subroutine to get address. X register contains length. RBASE set to point to the beginning of source address. - In a loop, grab next byte. - Call WBA in comms package to write out next byte. - Move X and Y indices for next byte - Loop until done. #+begin_src FFBB: 163 * FFBB: 164 * FFBB: 165 * FFBB: 166 * ROUTINE TO READ OUT MEMORY FFBB:20 AA FF 167 RM JSR GETADR ; SET UP ADDRESS AND COUNT FFBE:B1 F8 168 RM1 LDA (RBASE),Y ; GET VALUE FFC0:20 98 FF 169 JSR WBA FFC3:C8 170 INY ; MOVE INDEX FFC4:CA 171 DEX ; DECREMENT COUNT FFC5:D0 F7 172 BNE RM1 ; LOOP TILL DONE FFC7:4C 59 FF 173 JMP CI ; DONE #+end_src **** Write Memory - Receive bytes to VCS address from Apple2 - Call GETADR subroutine to get address X register contains length RBASE set to point of destination address - In a loop, read byte from apple2, - Store byte in VCS - Move X and Y indices for next byte - Loop until done. #+begin_src FFCA: 174 * FFCA: 175 * FFCA: 176 * FFCA: 177 * ROUTINE TO WRITE MEMORY FFCA: 178 * FFCA:20 AA FF 179 WM JSR GETADR ; GO GET ADDRESS AND COUNT FFCD:20 A1 FF 180 WM1 JSR RBA ; GO GET BYTE FFD0:91 F8 181 STA (RBASE),Y ; STORE IT FFD2:C8 182 INY ; MOVE INDEX FFD3:CA 183 DEX ; DECREMENT COUNT FFD4:D0 F7 184 BNE WM1 ; LOOP TILL DONE FFD6:4C 59 FF 185 JMP CI ; DONE #+end_src **** Go from Break - If you use 'G' without an address parameter. - Happens while VCS has processed a breakpoint. - Restore processor flags to VCS. - Restore the saved memory state back into VCS. - Restore Stack pointer - Restore Y, X, and A. - Restore N and Z flags. - Jump back to the breakpoint and continue. #+begin_src FF6F: 107 * FF6F: 108 * ROUTINE TO RESTORE STATE AFTER BREAK POINT AND JMP FF6F: 109 * BACK INTO THE VCS PROGRAM FF6F: 110 * FF6F: 111 * FF6F:AD F4 FF 112 GO LDA F1REST ; GET PART 1 OF THE FLAG RESTORE PROCESS FF72:48 113 PHA ; PUT IN FLAGS BY WAY OF STACK FF73:28 114 PLP FF74: 115 * FF74:A2 20 116 LDX #256-RSAVE ; LOOP COUNT FOR RAM STUFF BACK FF76:AD F1 FF 117 GO1 LDA PSTAT ; LOOK AT STATUS FF79:29 40 118 AND #$40 ; SEE IF STUFF THERE FF7B:F0 F9 119 BEQ GO1 ; IF NOT THEN WAIT FF7D:AD F2 FF 120 LDA RDATA ; THEN GET VALUE FF80:95 DF 121 STA RSAVE-1,X ; PUT BACK IN RAM FF82:CA 122 DEX ; MOVE TO NEXT FF83:D0 F1 123 BNE GO1 ; LOOP FF85: 124 * FF85: 125 * FF85:AE F9 FF 126 LDX SREST ; GET VALUE IN X TO RESTORE STACK POINTER FF88:9A 127 TXS ; RESTORE STACK POINTER FF89:AC F8 FF 128 LDY YREST ; GET VALUE TO RESTORE Y FF8C:AE F7 FF 129 LDX XREST ; GET VALUE TO RESTORE X FF8F:AD F6 FF 130 LDA AREST ; GET VALUE TO RESTORE A FF92:EE F5 FF 131 INC F2REST ; RESTORE Z AND N FLAGS FF95: 132 * NOW ALL IS RESTORED SO JMP BACK FF95:4C 00 FF 133 BJUMP JMP BREAK #+end_src **** Go Indirect - If you use 'G' with an address parameter. - Grab the two address bytes via comms - Then jump indirect to said address. #+begin_src FFD9: 186 * FFD9: 187 * FFD9: 188 * FFD9: 189 * ROUTINE TO TAKE A JMP INDIRECT FFD9: 190 * (USED TO PUT THINGS TO SLEEP) FFD9:20 A1 FF 191 GOI JSR RBA ; GET HIGH ORDER JMP ADDR FFDC:85 F9 192 STA RBASE+1 ; PUT IN POINTER HIGH FFDE:20 A1 FF 193 JSR RBA ; GET LOW ORDER JMP ADDR FFE1:85 F8 194 STA RBASE ; PUT IN POINTER LOW FFE3:6C F8 00 195 JMP (RBASE) ; GO THERE #+end_src *** GETADR Subroutine - Grab a 16 bit address and count using RBA - received Address goes into RBASE and RBASE+1 - Count goes into X register #+begin_src FFAA: 152 * FFAA: 153 * ROUTINE TO SET UP A 16 BIT ADDRESS AND 8 BIT COUNT FFAA: 154 * FFAA:20 A1 FF 155 GETADR JSR RBA ; GO GET HIGH ADDRESS PART FFAD:85 F9 156 STA RBASE+1 ; PUT INTO BASE POINTER FFAF:20 A1 FF 157 JSR RBA ; GET LOWER PART FFB2:85 F8 158 STA RBASE ; PUT IN POINTER FFB4:20 A1 FF 159 JSR RBA ; GET THE LENGTH FFB7:AA 160 TAX ; PUT IN X REG FFB8:A0 00 161 LDY #0 ; USE Y FOR INDEX FFBA:60 162 RTS ; NOW ADDRESS AND COUNT SETUP FFBB: 163 * #+end_src ** AMON In Detail - Runs on Apple2 - Provides Command Interface to Frob and FMON - Can load any 2K or 4K game binary. - FMON contains control logic. AMON contains display logic. *** Important Variables - S%(32) - Stored State for Registers - BP% - Boolean for "Breakpoint Hit?" - PG%(18) - Sleep routine stored at $E0 in VCS RAM. - BIT%(8) - Used to hold one byte's worth of bits (e.g. for Processor flags) - FM% - boolean to indicate whether FMON is loaded onto VCS. - OCODE%(255) - MID$ position into OPCODES$ for a given opcode byte, used for 'L' - ACODE%(255) - maps opcode byte to an ON x GOTO, to handle different 6502 addressing modes, used for 'L' - PM% - Constant to address of PMOVE in apple2 memory ($0300) - BUF$ - a 4K Buffer created using M$ BASIC string techniques. - FSLOT% - FROB Slot # - DEV = Pointer to Frob control registers