seandon4 wrote:
I did not know I could do assembler in Apple II BASIC, so I'll give that a try as well.
Okay, it seems I've opened a can of worms. I'll try to just cover as much as I can in a couple hours of writing. (And before you do/try any of this, I suggest maybe jumping to the end of my post, as I kind of backtrack on my own recommendation and say maybe doing NES stuff with present-day emulators and tools is easier)
On the Apple II there are 2 main BASICs available: the built-in ROM BASIC that comes with the system is called
Integer BASIC (written by Steve Wozniak), and another is called
Applesoft BASIC (done by a couple people who were at Microsoft). Refer to Wikipedia as well on both BASICs for a history of what's what. The BASIC you choose matters: they have more differences than similarities.
Here's a primer on Applesoft BASIC.
This is further made complicated by the fact that there are two different DOSes (disk operating systems -- do not confuse this with "DOS for the PC"!): DOS (a.k.a.
Apple DOS) and ProDOS. For Apple DOS, DOS 3.3 was the most common. Each of these *also* has a set of commands that can be used -- and there are a few of them that are important for writing assembly on the Apple II. But back to BASIC:
If you review both of the above list of commands for each respective BASIC, you should note that there isn't some "magic way" to write assembly: because BASIC isn't assembly. Instead, the Apple II's ROM provides a built-in debugger (called the "System Monitor" or often just "Monitor" -- yes really. Do not confuse this with, say, a CRT or LCD display!) and mini-assembler.
The common way to get in/out of the System Monitor through either BASIC is to do
CALL -151 (
CALL is what you also use to run machine language/binary code in memory -- i.e. your assembled programs can be run through BASIC via
CALL). The address
-151 is actually just a signed 16-bit value of the value/address $FF69 or 65385 decimal; you could do
CALL 65385 and accomplish the same, but
-151 is easier to remember/shorter.
The Monitor is
a bit complicated and cryptic (
here's another reference), but you need to remember what time/year/era this was made. The Monitor has a
* for a prompt. Depending on what Apple II system you're on (I forget which one introduced what, but I'm referring to II vs. II+ vs. IIC vs. IIE vs. IIGS), you can use the
! command to enter the mini-assembler, otherwise
FF90G (the
G means Go (i.e. execute)). When writing assembly code on the Apple II in the mini-assembler, you go back and forth between the mini-assembler and the Monitor regularly. The Monitor can give you a disassembly of something in memory (
1000L would start disassembling what's at address $1000 onward (the
L means List)), or display CPU registers (Ctrl-E). I think
there's a Youtube video of someone showing off the Monitor at some point, and
I've done one myself to test certain BRK opcode behaviour for nesdev (what you see in my YT video is on an Apple IIGS, so the way I got to the Monitor is different -- I could've done it the above way though, as the IIGS is backwards-compatible with the older II series. Also, my YT video does have some IIGS/65816 CPU-isms in it, so it won't be what you'd exactly see on an Apple II).
Once you're in the mini-assembler, you'll have a
! prompt. From here, you can actually write assembly code. The way you use this is a little different than, say, writing code in a text editor, but the overall premise is the same. You can write code by doing something like:
1000: LDA #$A1. This would put the assembled instruction
lda #$a9 into memory locations $1000 and $1001 ($1000 would be $A9 (opcode), and $1001 would be $A1 (operand value)). You could continue with the next instruction by entering <space>
STA $C000 -- the preceding space means "continue from where I last left off assembling", otherwise you have to specify the address every time (yes, you had to keep track of this in your head or on paper!), e.g.
1002: STA $C000. All the addresses shown in the mini-assembler are in hexadecimal; so things like
STA $C000 will end up being shown as
STA C000. You get the premise, I hope. To get out of the mini-assembler and back into the Monitor, you simply hit Enter at the
! prompt.
To get out of the Monitor and back into BASIC/DOS/ProDOS, you would do
3D0G. Some of the links I provided go into what this does; there are, for lack of better wording, "magic memory locations" on the Apple II that do things for you. The Apple II ROM is remarkably versatile and gives you a lot of control and ability while offering built-in routines you can use from assembly programs or from BASIC that do stuff. Oh, in BASIC
PEEK means "get something from raw memory[/tt] and
POKE means "write something to raw memory[/tt].
Here's a list -- it's badly formatted and very confusing, I know, and I KNOW there is a better-organised list but it's 0450 in the morning and I'm getting tired writing this -- ah wait,
here it is (the ones that are described as "useful CALLs" are most helpful). You'll see me use some "special" ones to output things like hexadecimal values (in ASCII) in my YT video.
The next complication comes from what DOS (disk operating system -- yes, like "old MS-DOS for the PC" except for the Apple II) you use. The two common ones on the Apple II are DOS 3.3 and ProDOS. Refer to
Section 3 in this FAQ for a command listing (there are other resources online that are more verbose). I myself have limited experience with DOS 3.3 (I did most of my programming on the non-IIGS Apple II series using ProDOS), but you'll see that those DOSes offer commands like
BLOAD, BSAVE, BRUN to "run binary files": now we're getting somewhere. ProDOS also has a "BASIC extender" (BASIC.SYSTEM) that provides even more commands, but let's not get lost.
So, putting it all together (using the methodology I myself would): you'd boot a disk with ProDOS + BASIC.SYSTEM on it, and be dropped into Applesoft BASIC. You'd then go into the Monitor, into the mini-assembler, and begin writing some code at a memory location that is available for use.
Here's a memory map -- there are safe regions that don't conflict with I/O ports, graphics, the BASIC interpreters, ProDOS, ROM stuff, or the Monitor. When you finish -- and hopefully you've kept track of the SIZE of your assembly program (in bytes!), you go back to the BASIC interpreter and do
BSAVE FILENAME,A$xxxx,L$yyyy where
xxxx is the hexadecimal address of where your program starts, and
yyyy is the length (in hexadecimal) of your program. You could also use something like
BSAVE FILENAME,A$xxxx,Lyyyy (
yyyy in that case would be decimal, not hex -- note the lack of
$ prefix), I believe. The next time you wanted to load this program into memory, you would do
BLOAD FILENAME,A$xxxx (to load it off disk, starting at address
xxxx in hexadecimal). This is
explained here (again badly formatted).
Note: do not confuse LOAD vs. BLOAD, or SAVE vs. BSAVE. LOAD/SAVE are for BASIC programs, while BLOAD/BSAVE "save raw memory to a file". The "B" stands for Binary.
I wonder if anyone's written all that up in decades. Hmm. It's a dying art of sorts, I think. As I went over it all in my head and described the process to you, I started laughing -- I realise the above is paraphrased and trying to encompass a few years of my youth into a single forum post, talking about a system and environment that nobody uses any more. This made me laugh because I thought "sheesh... you know, maybe just using a 6502 cross assembler like asm6/nesasm on a PC in Windows, and then loading a .nes file in FCEUX, and learning how to use the debugger, is easier. Or hell, maybe it's just as cryptic/annoying as the Apple II was." Yeah, I'm starting to think that might be easier -- it just happens to be nothing like, say, writing a C program + compiling/linking it + getting your executable + running it and getting output on-screen using
printf("Hello world\n");.
Yeah, I'd say start with the Nerdy Nights tutorial... :-)
The equivalent of a "HELLO WORLD" in 6502 on the Apple II, using a built-in ROM routine for printing text, and intended for the mini-assembler, would be this (including the prompts to help indicate what's going on/where you are, and I hard-coded/calculated the addresses to make it easier to understand):
Code:
*!
!1000: LDX #0
!1002: LDA $1020,X
!1005: BEQ $1010
!1007: EOR #$80
!1009: JSR $FDED
!100C: INX
!100D: JMP $1002
!1010: RTS
!
*1020:48 45 4C 4C 4F 20 57 4F 52 4C 44 0D 00
*1000G
HELLO WORLD
*
- The "magic
EOR #$80 is because of
the Apple II text memory format: for non-inverted, non-flashing text, the uppercase ASCII table starts at $C0, not $40 like on PCs. Thus, the MSB effectively defines whether or not the character is "normal" or not; the
EOR #$80 quickly sets bit 7
- The raw value $0D is a carriage return (Apples and Macs use CR for line endings, unlike BSD/Linux which uses LF or PCs that use CR+LF)
- The raw value $00 I use as an end-of-string marker (i.e. C-style strings; I never liked Pascal-style strings, despite coding in Pascal).
- The final
RTS is how I get back to the Monitor **specifically in this context**. It's the Return To Subroutine opcode (normally used to get out of a
JSR), but because the last value on the stack when program runs is the return address of the Monitor entry vector,
RTS gets me back. If I had run this code from BASIC via
CALL 4096 (rather than from the Monitor itself), it would have returned me to the BASIC interpreter.
And yes, I did test it (see screenshot) on a IIGS, but you get the premise. :-)
PRINT "HELLO WORLD" in Applesoft BASIC would have done the same thing.