home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-01-06 | 48.4 KB | 1,575 lines |
- How to write demos that work (Version 4.3) - 3/1/93
- ===================================================
-
- (or How to bash the metal and get away with it)
-
- (or the Amiga Demo Coders Reference Manual)
-
- by Comrade J, Share and Enjoy (retired)
-
- Thanks to everyone on the internet for sending their suggestions,
- bug reports and comments to me on this article. I'll try and keep
- this up to date and posted here frequently. Coming soon, source
- code for a small intro using all these techniques that you can
- use as the base for whatever you want to do....
-
-
- Now, is anyone out there going to write a good Amiga 1200 demo,
- or am I going to have to do it?
-
- --------------------------------------------------------------------
-
- Most demos I've seen use similar startup code to that I was using back
- in 1988. Hey guys, wake up! The Amiga has changed quite a bit since
- then.
-
- So. Here are some tips on what to do and what not to do:
-
-
- 1. RTFM.
- ========
-
- Read the F**king manuals. All of them. Borrow them off friends or from
- your local public library if you have to.
-
- Read the "General Amiga Development Guidelines" in the dark grey (2.04)
- Hardware Reference Manual and follow them TO THE LETTER.
- If it says "Leave this bit cleared" then don't set it!
-
- Don't use self-modifying code. A common bit of code I see is:
-
- ... in the setup code
-
- move.l $6c.w,old
-
- ... at the end of the interrupt
-
- movem.l (sp)+,a0-a6/d0-d7
- dc.w $4ef9 ; jmp instruction
- old dc.l 0 ; self modifying!!!!
-
- DONT DO THIS!
-
- This is much better (although it still hits $6c which is not a
- good thing - It's much better to use AddIntServer() to call your
- interrupts,
-
- ... in the setup code
-
- move.l VBRBase(a0)
- move.l $6c(a0),old ; see under VBR below....
-
- ... at the end of the interrupt
-
- movem.l (sp)+,a0-a6/d0-d7
- move.l old,-(sp) ; push old address to stack
- rts
-
- ... in your data section
-
- old dc.l 0
-
-
- 68020 and above processors with cache enabled often barf at the first
- piece of code (the cache still contains the JMP 0 instruction
- which isn't then altered), but the second piece works fine on the '040.
-
-
-
-
-
- 2. Proper Copper startup.
- =========================
-
- (Please look at the startup example code at the end of this file).
-
- IF you are going to use the copper then this is how you should set it
- up. The current workbench view and copper address are stored, the
- system frozen, and then the copper enabled. On exit the workbench
- view is restored.
-
- This guarantees(*) your demo will run on an AGA (Amiga 1200) machine,
- even if set into some weird screenmode before running your code.
-
- Otherwise under AGA, the hardware registers can be in some strange states
- before your code runs, beware!
-
- The LoadView(NULL) forces the display to a standard, empty position,
- flushing the crap out of the hardware registers: Note. There is
- a bug in the V39 OS on Amiga 1200/4000 and the sprite resolution is
- *not* reset, you will have to do this manually if you use sprites...
-
- Two WaitTOF() calls are needed after the LoadView to wait for both the
- long and short frame copperlists of interlaced displays to finish.
-
- See the bottom of this file for a full, tested, example startup.asm
- code, that you can freely use for your own productions.
-
- It has been suggested to me that instead of using the GfxBase gb_ActiView
- I should instead use the Intuition ib_ViewLord view. This will work
- just as well, but there has been debate as to whether in the future
- with retargetable graphics (RTG) this will work in the same way. As the
- GfxBase is at a lower level than Intuition, I prefer to access it this
- way (but thank's for the suggestion Boerge anyway!). Using gb_ActiView
- code should run from non-Workbench environments (for example, being
- called from within Amos) too... CJ 1, Chris Green (CBM USA) 0 :-)
-
-
- * - Nothing is ever guaranteed where Commodore are involved. They
- may move the hardware registers into chipram next week :-)
-
- 3. Your code won't run from an icon.
- ====================================
-
- You stick an icon for your new demo (not everyone uses the CLI!) and
- it either crashes or doesn't give back all the RAM it uses. Why?
-
- Icon startup needs specific code to reply to the workbench message.
- With the excellent Hisoft Devpac assember, all you need to do is add
- the line
-
- include "misc/easystart.i"
-
- and it magically works!
-
- For those without Devpac, here is the relevent code:
-
- ---------------------------------------------------------
-
- * some startup code to make a Workbench execute look like the CLI
- * based loosely on RKM Vol 1 page 4-36
-
- * Include this at the front of your program
- * after any other includes
- * note that this needs exec/exec_lib.i
-
- IFND EXEC_EXEC_I
- include "exec/exec.i"
- ENDC
- IFND LIBRARIES_DOSEXTENS_I
- include "libraries/dosextens.i
- ENDC
-
-
- movem.l d0/a0,-(sp) save initial values
- clr.l returnMsg
-
- sub.l a1,a1
- move.l 4.w,a6
- jsr _LVOFindTask(a6) find us
- move.l d0,a4
-
- tst.l pr_CLI(a4)
- beq.s fromWorkbench
-
- * we were called from the CLI
- movem.l (sp)+,d0/a0 restore regs
- bra end_startup and run the user prog
-
- * we were called from the Workbench
- fromWorkbench
- lea pr_MsgPort(a4),a0
- move.l 4.w,a6
- jsr _LVOWaitPort(A6) wait for a message
- lea pr_MsgPort(a4),a0
- jsr _LVOGetMsg(A6) then get it
- move.l d0,returnMsg save it for later reply
-
- * do some other stuff here RSN like the command line etc
- nop
-
- movem.l (sp)+,d0/a0 restore
- end_startup
- bsr.s _main call our program
-
- * returns to here with exit code in d0
- move.l d0,-(sp) save it
-
- tst.l returnMsg
- beq.s exitToDOS if I was a CLI
-
- move.l 4.w,a6
- jsr _LVOForbid(a6)
-
- move.l returnMsg(pc),a1
- jsr _LVOReplyMsg(a6) ; ***** This line was missing from
- ; previous articles!!!! **********
-
- jsr _LVOPermit(a6)
-
- exitToDOS
- move.l (sp)+,d0 exit code
- rts
-
- * startup code variable
- returnMsg dc.l 0
-
- * the program starts here
- even
- _main
-
- ---------------------------------------------------------
-
-
-
-
- 4. How do I tell if I'm running on an Amiga 1200/4000?
- ======================================================
-
- Do *NOT* check library revision numbers, V39 OS can and does
- run on standard & ECS chipset machines (This Amiga 3000
- is currently running V39).
-
- This code will check for AGA
-
- move.w $dff07c,d0
- cmp.b #$f8,d0
- bne.s .notaga
-
- ; Do your funky AGA Stuff in here!
-
- .notaga
-
-
-
-
-
- 5. Use Relocatable Code
- =======================
-
- If you write demos that run from a fixed address you should be shot.
- NEVER EVER DO THIS. It's stupid and completely unnecessary.
-
- If you require bitplanes to be on a 64Kb boundary then try the
- following (in pseudo-code because I'm too lazy to write it in asm
- for you):
-
- for c=0 to (top of chip ram) step 65536
-
- if AllocAbs(c,NUMBER_OF_BYTES_YOU_WANT) == TRUE then goto ok:
-
- next c:
-
- print "sorry. No free ram. Close down something and retry demo!"
- stop
-
- ok: Run_Outrageous_demo with mem at c
-
- Keep your code in multiple sections. Several small sections are
- better than one large section, they will more easilly fit in and run
- on a system with fragmented memory.
-
-
-
-
- 6. Don't Crunch demos!
- ======================
-
- Don't ever use Tetrapack or Bytekiller based packers. They are crap.
- Many more demos fall over due to being packed with crap packers than
- anything else. If you are spreading your demo by electronic means
- (which most people do now, the days of the SAE Demodisks are long
- gone!) then assemble your code, and use LHARC to archive it, you
- will get better compression with LHARC than with most runtime
- packers.
-
- If you *have* to pack your demos, then use Powerpacker 4+, Turbo
- Imploder or Titanics Cruncher, which I've had no problems with myself.
-
-
-
-
- 7. Don't use the K-Seka assembler!
- ==================================
-
- It's dead and buried. Get a life, get a real assembler. Hisoft Devpac
- is probably the best all-round assembler, although I use ArgAsm
- which is astonishingly fast. The same goes for hacked versions of
- Seka.
-
-
-
- 8. Don't use the hardware unless you have to!
- =============================================
-
- This one is aimed particularly at utility authors. I've seen some
- *awfully* written utilities, for example (although I don't want
- to single them out as there are plenty of others) the Kefrens
- IFF converter.
-
- There is NO REASON why this has to have it's own copperlist. A standard
- OS-friendly version opening it's own screen works perfectly (I
- still use the original SCA IFF-Converter), and multitasks properly.
-
- If you want to write good utilities, learn C.
-
-
- 9. Beware bogus input falling through to Workbench
- ==================================================
-
- If you keep multitasking enabled and run your own copperlist remember
- that any input (mouse clicks, key presses, etc) fall through to the
- workbench. The correct way to get around this is to add an input
- server to the IDCMP food chain (see - you *do* have to read the
- other manuals!) at a high priority to grab all input events before
- workbench/cli can get to them.
-
- Look at the sourcecode for Protracker for an excellent example of
- how to do the job properly. Well done Lars!
-
-
-
- 10. Have fun!
- =============
-
- Too many people out there (particularly the American OS-Lamic
- Fundamentalists) try to tell us that you should never program at a hardware
- level. If you're programming for fun, ignore them! But try and put
- a little thought into how your code will work on other machines,
- nothing annoys people more than downloading 400Kb of demo and then
- finding it blows up on their machines. I'm not naming any names, but
- there are quite a few groups who I have no intention of downloading
- their demos again because I know it's a waste of download. With
- the launch of the Amiga 1200 you cannot just write for 1.3 Amiga
- 500's any more. Let's go out there and write some shit hot demos!
-
-
- 11. Don't Publish Code you haven't checked!
- ===========================================
-
- Thanks to Timo Rossi for spotting the stupid bug in my copper
- setup routine (using LOFList instead of copinit). Funnily enough
- my own setup routine uses the correct copinit code:
-
- Please ignore the original file (howtocode.txt) and use this instead.
-
-
- 12. Copper End
- ==============
-
- I've remembered where this double copper end comes from:
-
- The ArgAsm assembler has copper macros (CMOVE, CWAIT and CEND)
- built in, and the CEND macro deliberately leaves two copper
- END instructions, the manual states this is important
- for compatibility reasons..
-
- When I get back to the office on Monday I'll look this up...
-
-
-
- 13. Vector Base Register (68010 and up)
- =======================================
-
- The 68010 and above processors have a Vector Base Register (VBR)
- that allows the exception vector table to be placed anywhere in
- RAM, including fast ram. This gives some speed benefits to systems
- with this running, but at a cost.
-
- Anything that accesses the interrupt locations directly, for example:
-
- move.l $6c.w,old
- move.l MyWickedInterruptCode,$6c.w
-
- won't work... Unfortunately this includes 99.9% of demos.
-
- Boerg Noest (borgen@stud.cs.uit.no) writes....
-
- > Next your text should mention VBR. It's easy - like this:
- > move.l 4.w a6
- > moveq #0,d0
- > btst.b #AFB_68010,AttnFlags+1(a6)
- > beq .done
- > movec vbr,d0
- >.done
- > move.l d0,VBRBase
- > rts
- >
- then you just
- > move.l VBRBase,a0
- move.l $6c(a0),oldinterrupt
- > move.l #myinterrupt,$6c(a0)
- >
-
- Ok. I don't know how many people use the VBR to offset their interrupts,
- I certainly don't as it breaks too much software on my 3000, and it
- only gives a minimal speed increase. The main benefit of the 68010
- over the 68000 is the loop cache mode. Common 3 word loops like:
-
- moveq #50,d0
- .lp move.b (a0)+,(a1)+ ; one word
- dbra d0,.lp ; two words
-
- are recognised as loops and speed up dramatically on 68010.
-
-
- 14 Using the blitter.
- =====================
-
- If you are using the blitter in your code and you are leaving the
- system intact (as you should) always use the graphics.library
- functions OwnBlitter() and DisownBlitter() to take control
- of the blitter. Remember to free it for system use, many system
- functions (including floppy disk data decoding) use the blitter.
-
- Another big mistake I've seen is with blitter/processor timing.
-
- Assuming that a particular routine will be slow enough that a blitter
- wait is not needed is silly. Always check for blitter finished, and
- wait if you need to.
-
- Don't assume the blitter will always run at the same speed too. Think
- about how your code would run if the processor or blitter were running
- at 100 times the current speed. As long as you keep this in mind,
- you'll be in a better frame of mind for writing compatible code.
-
- Another big source of blitter problems is using the blitter in interrupts.
-
- Most demos do all processing in the interrupt, with only a
-
- .wt btst #6,$bfe001 ; is left mouse button clicked?
- bne.s .wt
-
- loop outside of the interrupt. However, some demos do stuff outside the
- interrupt too. Warning. If you use blitter in both your interrupt
- and your main code, (or for that matter if you use the blitter via the
- copper and also in your main code), you may have big problems....
-
- Take this for example:
-
- lea $dff000,a5
- bsr WaitBlitterFinish ; check bits for blitter ready
- move.l #-1,BLTAFWM(a5) ; set FWM and LWM in one go
- move.l #source,BLTAPT(a5)
- move.l #dest,BLTDPT(a5)
- move.w #100111110000,BLTCON0(a5)
- move.w #0,BLTCON1(a5)
- move.w #64*height+width/2,BLTSIZE(a5) ; trigger blitter
-
- There is *nothing* stopping an interrupt, or copper, triggering a
- blitter operation between the WaitBlitterFinish call and
- your final BLTSIZE blitter trigger. This can lead to total system blowup.
-
- Code that may, by luck, work on standard speed machines may die horribly
- on faster processors due to timing differences causing this type of
- problem to occurr.
-
- The safest way to avoid this is to keep all your blitter calls together,
- use the copper exclusively, or write a blitter-interrupt routine to
- do your blits for you.
-
- Always remember to do two blitter-wait-checks... eg:
-
- btst.b #DMAB_BLTDONE-8,DMACONR(a1)
- btst.b #DMAB_BLTDONE-8,DMACONR(a1)
-
- The first may not give the correct result on machines with early
- (non Fat) Agnus chips (eg Amiga 1000, German A2000). The Hardware
- Reference manual explains why, if you're interested.
-
-
- 15 NTSC
- =======
-
- As an European myself, I'm naturally biased agains the inferior video
- system, but even though the US & Canada have a relatively minor Amiga
- community compared with Europe (Sorry, it's true :-) we should still
- help them out, even though they've never done a PAL Video Toaster for
- us (sob!).
-
- You have two options.
-
- Firstly, you could write your code only to use the first 200 display
- lines, and leave a black border at the bottom. This annoys PAL owners,
- who rightly expect things to have a full display. It took long enough
- for European games writers to work out that PAL displays were better.
-
- You could write code that automatically checked which system it is
- running on and ran the correct code accordingly:
-
- (How to check: Note, this is probably not the officialy supported method,
- but so many weird things happen with new monitors on AGA machines that
- I prefer this method, it's simpler, and works under any Kickstart)
-
- move.l 4.w,a6 ; execbase
- cmp.b #50,PowerSupplyFrequency(a6) ; 531(a6)
- beq.s .pal
-
- jmp I'm NTSC (or more accurately, I'm running from 60Hz power)
- .pal jmp I'm PAL (or I'm running from 50hz power).
-
-
- If people have already switched modes to PAL, or if they are running
- some weird software like the ICD Flicker Free Video Prefs thingy, then
- this completely ignores them, but that serves them right for trying
- to be clever :-)
-
- Probably better would be to check VBlankFrequency(a6) [530(a6)]
- as well, if both are 60Hz then it's definately a NTSC machine. If
- one or more are 50Hz, then it's probably a better idea to run in PAL.
-
- Under Kickstart 2.04 or greater, the Display Database can be accessed.
- Any program can enquire of the database what type of displays
- are available, so for example "I want a 50hz 15Khz PAL screen. Can
- I display it on this Amiga?" (Unfortunately it doesn't take
- an ASCII string like that, but it's not much more dificult). Of
- course many users will have the default monitor installed (PAL or
- NTSC) and not realise that they can have extra modes by dragging
- the monitor icon into their Monitors drawer, and of course
- this doesn't work on Kickstart 1.3 machines.
-
- Now, if you want to force a machine into the other display system
- you need some magic pokes: Here you go (beware other bits in
- $dff1dc can do nasty things. One bit can reverese the polarity
- on the video sync, not to healthy for some monitors I've heard...)
-
- To turn a NTSC system into PAL (50Hz)
-
- move.w #32,$dff1dc ; Magically PAL
-
- To turn a PAL system into NTSC (60Hz)
-
- move.w #0,$dff1dc ; Magically NTSC
-
- Remember: Not all displays can handle both display systems!
- Commdore 1084/1084S, Philips 8833/8852 and multisync monitors
- will, and very few US TV's will handle PAL signals (especially
- if being used with a NTSC Amiga modulator). I gather that
- most NTSC machines are Amiga 2000's, so perhaps this isn't
- a problem.
-
- So come on you NTSC users, tell us how you would prefer
- the demos written!
-
-
-
- 16 Programming AGA hardware
- ===========================
- I've just got this interesting text file which I thought should be
- included with this document. I've now translated it into
- better English :-)
-
-
- "The following information was gathered by Junkie/PMC Fr and Yragael.
-
- Junkie bought a 1200 and started to dissassemble the workbench copperlist.
- Thanks to this important work and his impressive speed (which lead me
- to dissassemble the copperlist too, I (Yragel) bring you the following
- information:
-
- More will follow (unless we get the official AGA hardware reference
- manual) [You'll be lucky :-) CJ]
-
- We now wait to see your productions on the 1200, and with your cooperation
- we can trip into the hardware of the A1200. [? Are these guys French
- or what? :-)]
-
- (next issue: Sprite corrections, HAM8+, other graphic modes, 3 words
- copperlists ...)
-
- ***************************************************************************
-
- USING THE SUPERHIRES MODE
-
- To use the SuperHires mode (1280 pixels wide), just use the bit 6 of
- register $0100
-
- bit 6 | Mode SuperHires
- -----------------------
- 0 | Normal
- -----------------------
- 1 | SuperHires
- -----------------------
-
- ***************************************************************************
-
- USING 8 BITPLANES
-
- The number of bitplanes available used to be no more than 7 and was coded
- using the bits 14 to 12 of register $0100. To use 8 bitplanes, just use
- bit 4 of register $0100. The value of bits 14 to 12 will then not be
- considered anymore (CJ - It's probably better to set this to %111 for
- future compatibility)
-
- bit 4 | 8 bitplanes mode
- ------------------------
- 0 | 0 to 7 bitplanes (from bits 12 - 14)
- ------------------------
- 1 | 8 bitplanes
- ------------------------
-
- ***************************************************************************
-
- ACCESSING 24 BITS COLOURS
-
- The 24 bit colour values are coded using 2 words:
-
- - the first receives the 4 high bits of each R, G and B componants
- - the second receives the 4 low bits of each R, G and B componants
-
- To modify a color using 24 bits coding, you must use 2 coppers-moves on the
- same color register. The first move must always be the move of the
- word of the 3*4 high bits, the second move is the move of the word of the
- 3*4 low bits.
-
- You set whether to write the high or the low bits
- by setting or clearing bit 9 of register $0106
-
- bit 9 | Components access
- -----------------------------------------------------------------
- 0 | Access to the 4 high bits of components R, G and B
- -----------------------------------------------------------------
- 1 | Access to the 4 low bits of components R, G and B
- -----------------------------------------------------------------
-
- ** Editorial note: Looks like he got a little confused about
- ** high and low bits here:
- **
- ** Take $f1f200 (a bright yellow), the high-bits word is $ff0
- ** (look familiar?), and the low-bits word is $120. - CJ
-
-
-
- ex: change $0180 to $00123456 in the copperlist
-
- $01060000
- $01800135
- $01060200
- $01800246
-
-
- When you want to work using the 12 bits color coding mode, the 3*4 bits
- value you move to the color register is considered by the copper as the 3*4
- high bits. You don't have to care about $0106. It seems bit 9 of register
- $0106 is initialized to 0 at each copjmp.
-
- (Editorial note: The following code *will* work:
-
- $1060000 ; set high bits
- $1800123 ; high bits register 0
- $1820123 ; high bits register 1
- ... etc
- $1060200 ; set low bits
- $1800456 ; low bits register 0
- $1820567 ; low bits register 1
- ... etc
-
- ie, you don't have to do the high bits and low bits of each colour
- immediately after each other. This saves lots of needless $106 commands!)
-
-
-
- ***************************************************************************
-
- ACCESSING THE 256 COLOURS PALETTE
-
- The amiga doesn't work with 256 separate color registers. The same colour
- register is used several times to code several colors.
-
- The amiga just works with 8 differents palettes of 32 colors each, using
- color registers from $0180 to $01BE.
-
- You can choose the palette you want to access via the bits 11 to 14 of
- register $0106
-
-
- bit 14 | bit 13 | bit 12 | Selected palette
- --------------------------------------------------------
- 0 | 0 | 0 | Palette 0 (color 0 to 31)
- --------------------------------------------------------
- 0 | 0 | 1 | Palette 1 (color 32 to 63)
- --------------------------------------------------------
- 0 | 1 | 0 | Palette 2 (color 64 to 95)
- --------------------------------------------------------
- 0 | 1 | 1 | Palette 3 (color 96 to 125)
- --------------------------------------------------------
- 1 | 0 | 0 | Palette 4 (color 128 to 159)
- --------------------------------------------------------
- 1 | 0 | 1 | Palette 5 (color 160 to 191)
- --------------------------------------------------------
- 1 | 1 | 0 | Palette 6 (color 192 to 223)
- --------------------------------------------------------
- 1 | 1 | 1 | Palette 7 (color 224 to 255)
- --------------------------------------------------------
-
- ex: You want to change color 177 to $00123456
-
- Color 177 is color $01A2 of palette 5
-
-
- $01065000
- $01a20135
- $01065200
- $01a20245
-
- * (Editorial note: This was wrong and has been fixed!!! - CJ)
-
- ***************************************************************************
-
- SWITCHING THE PALETTE
-
- You can switch colors. The definition of switching color A and color B is:
-
- - Color registers of colors A and B are NOT modified by the switching
- - Color A is displayed using the content of register of color B and
- vice-versa
-
- The switching of palette can't be used on just n colors of the palette.
- Once you choose a switching value, ALL the palette's colors will be
- switched. The switching value is the value separing the colors to be
- switched and is coded with bits 15 to 8 of register $010C.
-
- ex: You want all the colors separated by one color in the colorlist to be
- switched
-
- $0180 <--
- $0182 <--|--
- $0184 <-- |
- $0186 <-----
- $0188 <--------
- . |
- .
- .
-
- Value 2 will be stocked in bits 15 to 8.
-
- The switching works with the palette as if it was a circular palette. I
- mean if if the copper consider color 255 and must switch by 1 the colors,
- color $0180 will be assiocated to color 255. (??? - CJ)
-
- (Editorial note: A bit confusing here. the high byte of the $dff10c
- register (BPLCON4) seems to be an XOR value:
-
- So, if you set the value to $FF, all colour registers are negated,
- eg Colour 255 = colour 0, colour 1 = 254, etc...
-
- - CJ)
-
-
- ***************************************************************************
-
- USING SPRITES IN LOWRES, HIRES AND SUPERHIRES
-
- To change the reolution of the sprite, just use bit 7 and 6 of register
- $0106
-
- bit 7 | bit 6 | Resolution
- --------------------------
- 0 | 0 | Lowres (140ns)
- --------------------------
- 1 | 0 | Hires (70ns)
- --------------------------
- 0 | 1 | Lowres (140ns)
- --------------------------
- 1 | 1 | SuperHires (35ns)
- --------------------------
-
- (Now.. 70ns sprites may not be available unless the Interlace bit in
- BPLCON0 is set. Don't ask me why.... CJ)
-
- ***************************************************************************
-
- USING 16, 32 AND 64 PIXELS WIDE SPRITES
-
- Well, I still have bug there with sprites in 32 or 64 pixels. Sorry but
- the followings informations may have to be corrected.
-
- Use bit 3 and 2 of register $01FC
-
- bit 3 | bit 2 | Wide
- -------------------------
- 0 | 0 | 16 pixels
- -------------------------
- 1 | 0 | 32 pixels
- -------------------------
- 0 | 1 | 32 pixels
- -------------------------
- 1 | 1 | 64 pixels
- -------------------------
-
- The copper doesn't read the spritelist in the same way regarding the wide
- you choose for your sprite
-
- 16 pixels wide reading:
-
- word C1, word C2
- word A1, word B1
- .
- .
- .
- word An, word Bn
- $0000 0000
-
- C1=first control word
- C2=second control word
-
- Ai and Bi are combined via OR to form the sprite
-
- 32 pixels wide reading:
-
- long C1, long C2
- long A1, long B1
- .
- .
- .
- long An, long Bn
- $0000 0000 0000 00000
-
- C1=first control long
- the first control word is the high word of C1. The low word of C1 must
- contain the second control word.
- C2=second control long
- the second control word is the high word of C2. Low word of C2 is $0000
-
- Ai and Bi are combined via OR to form the sprite
-
- 64 pixels wide reading:
-
- double C1, double C2
- double A1, double B1
- .
- .
- .
- double An, double Bn
- $0000 0000 0000 00000 0000 0000 0000 00000
-
- C1=first control double
- C1=W3:W2:W1:W0 (Wi=words)
- W3 is first control word
- W2 and W1 are second control word
- C2=second control double
- C2=W3:W2:W1:W0 (Wi=words)
- W3 is second control word
-
- Ai and Bi are combined via OR to form the sprite
-
- ***************************************************************************
-
- CHANGING THE SPRITE PALETTE
-
- It is possible to choose the color palette of the sprite. This is done by
- the bits 7 and 4 of register $010C.
-
- bit 7 | bit 6 | bit 5 | bit 4 | Starting color of the sprite's palette
- -------------------------------------------------------------------------
- 0 | 0 | 0 | 0 | $0180/palette 0 (coulor 0)
- -------------------------------------------------------------------------
- 0 | 0 | 0 | 1 | $01A0/palette 0 (color 15)
- -------------------------------------------------------------------------
- 0 | 0 | 1 | 0 | $0180/palette 1 (color 31)
- -------------------------------------------------------------------------
- 0 | 0 | 1 | 1 | $01A0/palette 1 (color 47)
- -------------------------------------------------------------------------
- 0 | 1 | 0 | 0 | $0180/palette 2 (color 63)
- -------------------------------------------------------------------------
- 0 | 1 | 0 | 1 | $01A0/palette 2 (color 79)
- -------------------------------------------------------------------------
- 0 | 1 | 1 | 0 | $0180/palette 3 (color 95)
- -------------------------------------------------------------------------
- 0 | 1 | 1 | 1 | $01A0/palette 3 (color 111)
- -------------------------------------------------------------------------
- 1 | 0 | 0 | 0 | $0180/palette 4 (color 127)
- -------------------------------------------------------------------------
- 1 | 0 | 0 | 1 | $01A0/palette 4 (color 143)
- -------------------------------------------------------------------------
- 1 | 0 | 1 | 0 | $0180/palette 5 (color 159)
- -------------------------------------------------------------------------
- 1 | 0 | 1 | 1 | $01A0/palette 5 (color 175)
- -------------------------------------------------------------------------
- 1 | 1 | 0 | 0 | $0180/palette 6 (color 191)
- -------------------------------------------------------------------------
- 1 | 1 | 0 | 1 | $01A0/palette 6 (color 207)
- -------------------------------------------------------------------------
- 1 | 1 | 1 | 0 | $0180/palette 7 (color 223)
- -------------------------------------------------------------------------
- 1 | 1 | 1 | 1 | $01A0/palette 7 (color 239)
- -------------------------------------------------------------------------
-
- ***************************************************************************
-
-
- That's all. Thanx you VERY MUCH to Junkie/PMC Fr for his work.
-
- Written by Yragael. Translated & Bugfixed by Comrade J
- Chaos. Live it. Learn it. Be it.
-
- _\!/_ The Illuminated Ones _\!/_
- /!\ ««««««««««»»»»»»»»»» /!\
-
- Interesting, eh?
-
-
- 17. Keyboard Timings
- ====================
-
- If you have to read the keyboard by hardware, be very careful
- with your timings. Not only do different processor speeds affect
- the keyboard timings (for example, in the game F-15 II Strike Eagle
- on an Amiga 3000 the key repeat delay is ridiculously slow, you
- ttyyppee lliikkee tthhiiss aallll tthhee ttiimmee. You use
- up an awful lot of Sidewinders very quickly!), but there are differences
- between different makes of keyboard (for example, some Amiga 2000's
- come with Cherry keyboards, these have small function keys (same
- size as normal alphanumeric keys - these keyboards have different
- timings to the normal Mitsumi keyboards.
-
- Again, here's something from Boerge:
-
- >Third, you should warn about f**king up the keyboard read timing. My keyboard
- >absolutely does not work with just reading the $bfxxxx turning it to output,
- >and writing the reply, and turning it to input again. I do not have any code
- >that is especially good, but for now I use this:
- > move.b $bfec01,d0 ;get keycode
- > not.b d0
- > lsr.b #1,d0 ;test up/down flag
- > bcs.s .NoStore ;if up, ignore
- >
- > move.b d0,Key
- >.NoStore
- >
- >;handshake
- > bset #6,$bfee01 ;output
- > clr.b $bfec01
- > moveq #4-1,d0
- >.L1 move.b VHPOSR,d1
- >.L2 cmp.b VHPOSR,d1
- > beq.b .L2
- > dbra d0,.L1
- >
- > bclr #6,$bfee01 ;input again
-
- Remember though, if you have system interrupts and workbench enabled,
- keys can fall-through to workbench. For example, press Amiga-Q in
- your demo and you may find Workbench closed on return!
-
- 18. How to break out of never-ending loops
- ==========================================
-
- Another great tip for Boerge here:
-
- >This is a simple tip I have. I needed to be able to break out of my
- >code if I had neverending loops. I also needed to call my exit code when I did
- >this. Therefore I could not just exit from the keyboard interrupt which I have
- >taken over(along with the rest of the machine). My solution wa to enter
- >supervisor mode before I start my program, and if I set the stack back then
- >I can do an RTE in the interrupt and just return from the Supervisor() call.
- >This is snap'ed from my code:
- >
- > lea .SupervisorCode,a5
- > move.l sp,a4 ;
- > move.l (sp),a3 ;
- > EXEC Supervisor
- > bra ReturnWithOS
- >
- >.SupervisorCode
- > move.l sp,crashstack ; remember SSP
- > move.l USP,a7 ; swap USP and SSP
- > move.l a3,-(sp) ; push return address on stack
- >
- >that last was needed because it was a subroutine that RTSes (boy did I have
- >porblems working out my crashes before I fixed that)
- >Then I have my exit code:
- >
- >ReturnWithOS
- > tst.l crashstack
- > beq .nocrash
- > move.l crashstack,sp
- > clr.l crashstack
- > RTE ; return from supervisor mode
- >.nocrash
- >
- >my exit code goes on after this.
- >
- >This made it possible to escape from an interrupt without having to care
- >for what the exception frames look like.
-
-
-
-
- 19. Version numbers!
- ====================
-
- Put version numbers in your code. This allows the CLI version command
- to determine easily the version of both your source and executable
- files. Some directory utilities allow version number checking too (so
- you can't accidentally copy a newer version of your source over
- an older one, for example). Of course, if you pack your files the
- version numbers get hidden. Leaving version numbers unpacked
- was going to be added to PowerPacker, but I don't know if this is
- done yet.
-
- A version number string is in the format
-
- $VER: howtocode4.txt 4.2 02/01/92.
- ^ ^ ^Version number (date is optional)
- | |
- | | File Name
- |
- | Identifier
-
- The Version command searches for $VER and prints the string it finds
- following it.
-
- For example, adding the line to the begining of your source file
-
- ; $VER: MyFunDemo.s 4.0 01/01/93
-
- and somewhere in your code
-
- dc.b "$VER: MyFunDemo 4.0 01/01/93",0
-
- means if you do VERSION MyFunDemo.s you will get:
-
- MyFunDemo.s 4.0 01/01/93
-
- and if you assemble and do Version MyFunDemo, you'll get
-
- MyFunDemo 4.0 01/01/93
-
- Try doing version howtocode4.txt and see what you get :-)
-
- This can be very useful for those stupid demo compilations
- where everything gets renamed to 1, 2, 3, etc...
-
- Just do version 1 to get the full filename (and real date)
-
- Does this work on Kickstart 1.3? I can't remember, I ditched
- my 1.3 Kickstart 2 years ago :-)
-
-
- 20. CDTV
- ========
-
- I've been asked if there is any special advice on how to program
- demos to work on CDTV, and if hardware access to the CDTV (for
- playing CD Audio, etc) is possible.
-
- The CDTV is essentially a 1Mb chip ram Amiga with a CD-ROM drive.
- The major difference (apart from lack of fast ram or $c00000 ram)
- is that the CDTV roms can take up anything from 100-200Kb of ram.
-
- Many demos fail on CDTV through lack of memory.
-
- You can hack your CDTV to switch on/off these roms (put a switch
- on JP15), when switched off the CDTV has a full 1Mb of memory and
- more software works, but you can still play audio CD's in the CD
- drive..
-
- I have no information on how to program the CDTV at the hardware
- level. Currently the only supported way to access the CDTV
- special functions is by the CDTV.DEVICE, a standard ROM device
- that can be OpenDevice()d and sent IORequests. I don't think
- I'm allowed to give out the documentation for this, sorry :-(
-
- 21. Copper Wait Commands
- ========================
-
- The Hardware Reference manual states a copper wait for the start
- of line xx is done with:
-
- $xx01,$fffe
-
- However (as many of you have found out), this actually triggers
- just before the end of the previous line (around 4 or 5 low-res
- pixels in from the maximum overscan border).
-
- For most operations this is not a problem (and indeed gives a little
- extra time to initialise stuff for the next line), but if you are
- changing the background colour ($dff180), then there is a noticalbe
- 'step' at the end of the scanline.
-
- The correct way to do a copper wait to avoid this problem is
-
- $xx07,$fffe.
-
- This just misses the previous scanline, so the background colour is
- changed exactly at the start of the scanline, not before.
-
-
-
- 22. Screen Modulos (thanks Magnus for this one...)
- ==================================================
-
- Don't assume bitplane modulos (BPL0MOD and BPL1MOD) will be
- set to zero. If you require zero modulos set them, at the start
- of your copperlist is as good a place as any.
-
- Under V39 OS the workbench is interleaved by default, so the
- modulo can be huge...
-
-
-
- 23. Open Graphics Library! (again, thanks Magnus)
- =================================================
-
- I've never seen this in use before, but Magnus spotted
- it. It's got to be one of the worst pieces of code I've
- ever seen! Don't ever do this!
-
- move.l 4.w,a0 ; get execbase
- move.l (a0),a0 ; wandering down the library list...
- move.l (a0),a0 ; right. I think this is graphics.library
-
- ; now goes ahead and uses a0 as gfxbase...
-
- Oh yes, graphics.library is always going to be second down the chain from
- Execbase?
-
- LISTEN and LISTEN good. If you want to access gfxbase (or any other
- library base) OPEN the library. Do not wander down the library
- chain, either by guesswork or by manually checking for "graphics.library"
- in the library base name. OpenLibrary() will do this for you.
-
-
-
- 24. Protracker Replay code bug
- ==============================
-
- I've just got the Protracker 2.3 update, and the replay code (both
- the VBlank and CIA code) still has the same bug from 1.0!
-
- At the front of the file is an equate
-
- >DMAWait = 300 ; Set this as low as possible without losing low notes.
-
- And then it goes on to use 300 as a hard coded value, never refering
- to DMAWait!
-
- Now, until I can get some free time to write a reliable scanline-wait
- routine to replace their DBRA loops (does anyone want to write a better
- Protracker player? Free fame & publicity :-), I suggest you change
- the references to 300 in the code (except in the data tables!) to
- DMAWait, and you make the DMAWait value *MUCH* higher.
-
- I use 1024 on this Amiga 3000 without any apparent problem, but
- perhaps it's safer to use a value around 2000. Has anyone tried
- Protracker on a 68040 machine, if so, what DMAWait value in Prefs
- is needed to make all modules sound ok?
-
- 25. Optimise mode on produces crap code?
- ========================================
-
- If you're using Devpac and have found that the OPT o+ flag produces
- crap code, then you need to add the option o3-. I can't remember
- what this option does, my Devpac 3 manual is at the office.
-
- 26. Argasm produces crap code, whatever happens
- ===============================================
-
- First, Argasm (unlike Devpac) from the Command Line or if called from
- Arexx using Cygnus Ed (my prefered system) defaults to writing linkable
- code, so you need to add
-
- opt l- (disable linkable code)
-
- If you find that your Argasm executables fail then check you haven't
- got any BSR's across sections! Argasm seems to allow this, but of
- course the code doesn't work. Jez San from Argonaut software who
- publish ArgAsm says it's not a bug, but a feature of the linker...
-
- Yeah right Jez...
-
- But Argasm is *fast*, and it produces the *fastest* non-working
- code of any assembler :-)
-
- I still use it though, but Devpac comes in handy for checking
- code every now and then. Argonaut have abandoned ArgAsm so
- the last version (1.09d) is the last. There will be no more...
-
-
- 27. Help! I'm starting to code in assembler. Where do I begin?
- ==============================================================
-
- If you are just starting to learn programming, and you want
- a good place to begin learning assembler, buy Amos!. It's
- very easy to write assembler code, load it into amos and test it.
-
- For example, take this routine:
-
- ;simplemaths.s
-
- add.l d0,d1 ; add contents of d0 to d1
- rts
-
- Assemble this with Devpac and what do you get? Not a lot.
-
- Now, load AMOS and type this:
-
- Pload "ram:simplemaths",1 ' load executable file into bank 1
- Input "Enter a number ";n1
- Input "Enter another number ";n2
- dreg(0) = n1 ' Store n1 in 68000 register d0
- dreg(1) = n2 ' Store n2 in 68000 register d1
- call(1) ' Run your machinecode routine
- Print n1;" plus ";n2;" equals ";dreg(1) ' returns result in d1
-
- You can start playing with 68000 instructions this way, seeing how
- they work, without having to 'jump in the deep end' writing
- routines to set up displays, copperlists, windows or writing to
- the console. Once you have got the hang of 68000, you can drop
- Amos.
-
- 28 How can I tell what processor I am running on?
- =================================================
-
- Look inside your case. Find the large rectangular (or Square) chip,
- read the label :-)
-
- Or...
-
- move.l 4.w,a6
- move.w AttnFlags,d0 ; get processor flags
-
- d0.w is then a bit array which contains the following bits
-
- Bit Meaning if set
-
- 0 68010 processor fitted
- 1 68020 processor fitted
- 2 68030 processor fitted
- 3 68040 processor fitted
- 4 68881 MMU fitted
- 5 68882 MMU fitted
-
- There may well be other flags set now, I only have old documentation
- at home..
-
- DO NOT assume that the system has a >68000 if the word is non-zero!
- 68881 chips are available on add-on boards without any faster processor.
-
- And don't assume that a 68000 processor means a 7Mhz 68000. It may well
- be a 14Mhz processor.
-
- So, you can use this to determine whether specific processor functions
- are available (more on 68020 commands in a later issue), but *NOT*
- to determine values for timing loops. Who knows, Motorola may
- release a 100Mhz 68020 next year :-)
-
-
- 29 All addresses are 32 bit
- ===========================
-
- "Oh look" says clever programmer. "If I access $dcdff180 I can access
- the colour0 hardware register, but it confuses people hacking my
- code!".
-
- Oh no you can't. On a machine with a 32-bit address bus (any
- accelerated Amiga) this doesn't work. And all us hackers know this
- trick now anyway :-)
-
- Always pad out 24-bit addresses (eg $123456) with ZEROs in the high
- byte ($00123456). Do not use the upper byte for data, for storing
- your IQ, for scrolly messages or for anything else.
-
- Similarly, on non ECS machines the bottom 512k of memory was paged
- four times on the address bus, eg:
-
- move.l #$12345678,$0
-
- move.l $80000,d0 ; d0 = $12345678
- move.l $100000,d1 ; d1 = $12345678
- move.l $180000,d2 ; d2 = $12345678
-
- This does not work on ECS and upwards!!!! You will get meaningless
- results if you try this, so PLEASE do not do it!
-
- 30. New Chipsets?
- =================
-
-
- TO : ALL CODERS IN THE AMIGA DEMO-/GAMESCENE !!!
-
-
- New Low- Highend Chipset
- ------------------------
-
- Got this message from a BBS...
-
- >As we heard from serious Commodore developers, there
- >will be a new Low-end and a High-end chipset.
-
- This is no secret. This was announced by Lew Egelbrecht (sp?)
- head of Commodore Engineering a couple of months back
-
- >The High-end chipset will feature totally new customchips.
- >They will bring 16 Bit Sound and some other new features.
- >There will be no DSP (Digital Signal Processor) like
- >the MOTOROLA 68851 in this AMIGA. Instead of it, there
- >will take a new Commodore own Signal Processor part in this
- >wonderful machine.
-
- This is not what I heard. I head the AT&T DSP would probably
- be used (a true floating point DSP, unlike the integer one
- found in the Atari Falcon).
-
- The 68851 is a Memory Management Unit for the 68020, not a DSP.
-
- >The new chips are not fully compatible to AA Chips. Most of
- >the new AA Registers like $dff106 and $dff10c will be changed.
- >That's the reason, why Commodore didn't released a new
- >A1200 and A4000 Hardware Reference Manual.
-
- This is exactly what I have heard from *very* reliable sources.
- It may be that AGA chips are a temporary solution inbetween
- ECS and the new (AAA?) Superchips...
-
- I wonder if the low-end AAA chipset will be any more AGA
- compatible than the high-end?
-
- >The old Registers won't be changed.
- >Consequence: If Coders use the old (Normal & ECS) registers,
- >that are documented from Commodore, there are no probs with
- >the High-end chips. But if they use AA Registers, the new
- >AMIGA will propably CRASH.
-
- You have been warned! But what are the chances we get AAA
- hardware information. :-(
-
- >Probably publishing date of this AMIGA: Summer '93 (August).
-
- More likely early 1994. CBM won't release any new machines
- while AGA machines are still selling well....
-
- >
- >Other facts:- The consumer price of the A4000 will turn under
- > $1200 in 1993!
-
- Not for the 68040 version. The A4000/030 (Amiga 4000 with
- 25Mhz 680EC30 chip) may reach $1500 or less.
-
- > - Kickstart 4.0 is finished!
-
- Yeah right :-)
-
- >The truth of this facts is nearly 100 %.
-
- Hmmmmm....
-
- >Text from SMC of ABYSS
- >{written on 30.12.92}
-
- Commented by CJ on 2nd Jan 1993
-
-
-
- 31. Action Replay Cartridges
- ============================
-
- These things are great fun, even more so if you get into the
- 'sysop mode' (Allows disassembly of ram areas not previously
- allowed by Action Replay, including non-autoconfig ram and
- the cartridge rom!)
-
- To get into sysop mode on Action Replay 1 type:
-
- LORD OLAF
-
- To get into sysop mode on Action Replay 2 type:
-
- MAY
- THE
- FORCE
- BE
- WITH
- YOU
-
- To get into sysop mode on Action Replay 3 type the same as
- Action Replay 2. After this you get a message
- "Try a new one".
- Then type in
-
- NEW
-
- and sysop powers are granted!
-
-
-
-
-
- Startup Code
- ============
-
- I've seperated this out now, cut out this file and keep it safe
- (you may need a grown up to help you with this :-)
-
- 8<-------8<-------8<-------8<-------8<-------8<-------8<-------8<-------
-
- *
- * Startup.asm - A working tested version of startup from Howtocode3.txt
- *
- * CJ - 31/12/92 (Happy new year) [comradej@althera.demon.co.uk]
- *
- * This code sets up one of two copperlists (one for PAL and one for NTSC)
- * machines. It shows something to celebrate 3(?) years since the Berlin
- * wall came down :-) Press left mouse button to return to normality.
- * Tested on Amiga 3000 (ECS/V39 Kickstart) and Amiga 1200 (AGA/V39)
- * Read Howtocode3.txt for information on this source!
- *
- * $VER: startup.asm V2.0 With added fibre and no artificial preservatives
- *
- *
-
-
- opt l-,o+ ; auto link, optimise on
-
- ; opt o3- ; add this for Devpac Assembler
-
- section mycode,code ; need not be in chipram
-
- incdir "include:"
- include "exec/types.i"
- include "exec/funcdef.i" ; keep code simple and
- include "exec/exec_lib.i" ; easy to read - use
- include "graphics/gfxbase.i" ; the includes!
- include "graphics/graphics_lib.i"
-
- include "misc/easystart.i" ; Allows startup from
- ; icon
-
-
- StartCopper:
- move.l 4.w,a6 ; get ExecBase
- lea gfxname,a1 ; graphics name
- moveq #0,d0 ; any version
- jsr _LVOOpenLibrary(a6)
- tst.l d0
- beq End ; failed to open? Then quit
- move.l d0,gfxbase
- move.l d0,a6
- move.l gb_ActiView(a6),wbview
- ; store current view address
- ; gb_ActiView = 32
-
- move.w #0,a1 ; clears full long-word
- jsr _LVOLoadView(a6) ; Flush View to nothing
- jsr _LVOWaitTOF(a6) ; Wait once
- jsr _LVOWaitTOF(a6) ; Wait again.
-
- move.w $dff07c,d0
- cmp.b #$f8,d0
- bne.s .notaga
-
- move.w #0,$dff1fc ; reset sprites (fix V39 bug)
-
- .notaga
- ; Now you can hit the copper!
- move.l 4.w,a6
- jsr _LVOForbid(a6) ; Suspend multitasking!
-
-
- cmp.b #50,VBlankFrequency(a6) ; Am I *running* PAL?
- bne.s .ntsc
-
- move.l #mycopper,$dff080 ; bang it straight in.
- bra.s .lp
-
- .ntsc move.l #mycopperntsc,$dff080
-
-
- .lp btst #6,$bfe001
- bne.s .lp
-
-
- CloseDown:
- move.l 4.w,a6
- jsr _LVOPermit(a6) ; Enable multitasking
-
- move.l wbview,a1
- move.l gfxbase,a6
- jsr _LVOLoadView(a6) ; Fix view
-
- move.l gb_copinit(a6),$dff080 ; Kick it into life
- ; copinit = 36
- move.l a6,a1
- move.l 4.w,a6
- jsr _LVOCloseLibrary(a6) ; EVERYONE FORGETS THIS!!!!
-
- End: rts ; back to workbench/clc
-
- section mydata,data_c ; keep data & code seperate!
-
- mycopper dc.w $100,$0200 ; otherwise no display!
- dc.w $180,$00
- dc.w $8107,$fffe ; wait for $8107,$fffe
- dc.w $180,$f00 ; background red
- dc.w $d607,$fffe ; wait for $d607,$fffe
- dc.w $180,$ff0 ; background yellow
- dc.w $ffff,$fffe
- dc.w $ffff,$fffe
-
- mycopperntsc
- dc.w $100,$0200 ; otherwise no display!
- dc.w $180,$00
- dc.w $6e07,$fffe ; wait for $6e07,$fffe
- dc.w $180,$f00 ; background red
- dc.w $b007,$fffe ; wait for $b007,$fffe
- dc.w $180,$ff0 ; background yellow
- dc.w $ffff,$fffe
- dc.w $ffff,$fffe
-
- wbview dc.l 0
- gfxbase dc.l 0
- gfxname dc.b "graphics.library",0
-
-
-
-
-
-
-
-
-
-
-
- Thanks to everyone who has replied. Any more questions, queries,
- or "CJ, you got it wrong again!" type mail to the email
- address below....
-
- Thanks to: Vic Ricker, Grue, Timo Rossi, Jesse Michael, John Derek Muir,
- Boerge Noest, Christopher Klaus, Doz/Shining, Andrew Patterson, Walter
- Dao and Magnus Timmerby.
-
- Wanted! If you can write sections on the following, I'd be very
- greatful. Some of these I may do myself eventually..
-
-
- Good optimisations (eg lea 16(a0),a1 instead of move.l a0,a1 add.l #16,a1)
-
- Simple 68881/882 programming introduction
- Bootblocks
-
- Any other short programming tips & tricks
-
-
-
- --------------------------------------------------------------------
-
- This text is Copyright (C) 1993 Share and Enjoy, but may be freely
- distributed in any electronic form. The copyright of contributions
- quoted from other authors remains with the original author. If you would
- like to contribute to this file, email me at the address below...
-
- The startup code in this article is freeware and may be used by
- anyone for any purpose.
-
- All opinions expressed in this article are my own, and in no way
- reflect those of Share and Enjoy or any BBS this file may be found
- on.
-
- I didn't write this for fun, I wrote it for you to use! Hopefully
- this will grow into a big file that demo coders can use.
-
- If you strongly disagree with anything I write, or you want to send me
- some source or demos to test on Amiga 1200/4000 etc, or you have
- questions about Amiga programming, or suggestions for future articles,
- or just want to chat about the best way to optimise automatic copperlist
- generation code, then contact me via email at:
-
- comradej@althera.demon.co.uk, or in alt.sys.amiga.demos
-
- I'm having a few problems with sending email at the moment, so please
- be patient if you haven't received a reply. I will try to reply to
- every message within a day or two..
-
-
-