| 
    
      Example 6
     | 
    
       
     | 
Thus, fixpiccy was devised. It simply whips through the given directory structure examining sprite files. When it finds one, it looks to see if it is a MODE 21 sprite...if so, it will be converted using what I like to call the sledgehammer method - ie, the mode byte is simply changed. No fancy stuff.
If no path is given, it will work out which directory it is currently in, and scan that. So if you had a directory full of pictures that you were constantly adding to (as I was), then you can place fixpiccy into that directory, and just double-click on the program to get going. No need for Obey files or the like.
fixpiccy takes between zero and two parameters. If none are given, it will scan the
directory that it is within (not the CSD). The other two parameters are
"-quiet" (to inhibit any status output), and the directory path. Either
or both can exist, in any order. No other parameters are valid. The first thing found that is
not '-quiet' will be assumed to be a path.
IMPORTANT: fixpiccy is designed to work with single-sprite sprite files. I have not tested behaviour with multiple-sprite files. Support for those, if required, is left as an exercise for the budding assembly language coder (that's you!).
The program is in two sections. The first, c.main is the wrapper. It gets the whole
thing going, sorts out the command line parameters, and scans the directories. The second part,
s.picfixer is the assembler code that handles the sprite interrogation/fixing.
Typically one would code a large application in a high level language such as C, then use
assembler for the speed critical parts. Pretty much the way you might code processor intensive
code in BASIC.
Note, that this example is simply that. An example.
There is little speed benefit to have been gained in writing the image scanner in assembler.
Indeed, the compiler might have been able to generate better code than I for this function.
 
o.main. Though, by now you might like to have a crack at writing
a simple assembler wrapper for it yourself.
As you can see, an assembler function is simply defined as an extern function, like
functions in other source modules. You define the parameters of the function, and when called,
the first parameter is in R0, the next in R1, etc.
This version of the software has several of the comments removed, to save space here. The copy in
the archive is complete.
Additionally, the text size has been reduced slightly.
The important lines, to do with calling the assembler function, are highlighted in blue.
/* fixpiccy
   Name   : fixpiccy
   Version: v0.01
   Date   : Saturday, 15th July 2000
            Project begun 15th July 2000
   Purpose: Library utility to 'fix' MODE 21 pictures to be MODE 28.
   Creator: Richard Murray
   *** Downloaded from http://www.heyrick.co.uk/assembler/ ***
*/
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include "kernel.h"
#include "swis.h"
#define TRUE  1
#define FALSE 0
static _kernel_swi_regs r;
static struct catdat          /* OS_GBPB 12 catalogue data      */
{
       int           load_address;
       int           exec_address;
       int           length;
       int           file_attributes;
       int           object_type;
       int           object_file_type;
       char          object_name[40];  /* = 64 bytes, only 11 wanted for object_name[]  */
};                                     /* (mmm, what's the API for RO4 long filenames?) */
static struct catdat *catdata = NULL;
static char          scan_dir[256] = "";
static int           quiet = FALSE;
static void scan_directory(char *);
static void escape_key(int);
extern void fix_picture(char *, int);
int main(int argc, char *argv[])
{
   int  loop = 0;
   signal(SIGINT, escape_key);
   catdata = calloc(1, sizeof(struct catdat));
   if (!catdata)
   {
      fprintf(stderr, "Unable to allocate space for file descriptor (need 64 bytes)\n");
      exit(EXIT_FAILURE);
   }
   if (argc > 1)
   {
      for (loop = 1; loop < argc; loop++)
         if (strcmp(argv[loop], "-quiet") == NULL)  /* If parameter is "-quiet", do so */
            quiet = TRUE;
         else
            strcpy(scan_dir, argv[loop]);           /* Else assume parameter is a path */
   }
   if (strlen(scan_dir) == 0)
   {
      /* No path? Calculate our own. */
      strcpy(scan_dir, argv[0]);
      loop = strlen(scan_dir);
      while ( (scan_dir[loop] != '.') && (loop > 0) )
         loop--;
      scan_dir[loop] = '\0';
   }
   if (!quiet)
   {
      printf("fixpiccy v0.01 %c 2000 Richard Murray\n==============\n\n", 169);
      printf("Starting search in \"%s\"\n\n", scan_dir);
   }
   scan_directory(scan_dir);
   exit(EXIT_SUCCESS);
}
static void scan_directory(char *path)
{
   /* Search for sprites to process */
   int  scan_next = 0;
   char scan_obj[256];
   do
   {
      /* OS_GBPB 12 - see PRM p2-70 and 2-71 */
      r.r[0] = 12;            r.r[1] = (int) path;
      r.r[2] = (int) catdata; r.r[3] = 1;
      r.r[4] = scan_next;     r.r[5] = 64;
      r.r[6] = (int) "*";
      _kernel_swi(OS_GBPB, &r, &r);
      scan_next = r.r[4];
      if (scan_next != -1 && scan_next != 0)
      {
         if (catdata->object_type != 0)
         {
            sprintf(scan_obj, "%s.%s", path, catdata->object_name);
            if (catdata->object_file_type == 0xFF9)
            {
               /* It is a sprite */
               if (!quiet)
                  printf("Sprite : \"%s\"\n", catdata->object_name);
                  fix_picture(scan_obj, quiet);
            }
            else
            {
               if (catdata->object_file_type == 0x1000)
               {
                  /* It is a directory */
                  if (!quiet)
                     printf("\nScanning directory \"%s\"\n\n", catdata->object_name);
                  scan_directory(scan_obj);
               }
            }
         }
      }
   } while (scan_next > 0);
   if (!quiet)
      printf("\n");
   return;
}
static void escape_key(int sig)
{
   sig = sig;
   printf("\nEscape (exiting)\n\n");
   exit(EXIT_FAILURE);
}
See? Just like calling a C function!
 
You'll see there is no entry directive. The C compiler has this set for the
main() procedure. Instead, we set up our code to reside in the area called
C$$code (also defined by the C compiler).
In order to make our code accessible, we must export it so that it may be known to the linker.
This is achieved with the use of the EXPORT directive. There is no need to specify
the input/output parameters. That is done in the high level language by means of the
extern blah function_name(blah, blah) statement.
Unlike coding when you are writing a little assembler program, there are solid rules that should be obeyed here:
a1 to a4 (R0-R3) are alterable. You can corrupt these
       without worry.SWI OS_WriteS, always ensure
       that your strings are null terminated. Otherwise the OS will carry on printing stuff
       until it reaches a null - some of that stuff having been your instructions!
HEAD. This inserts a function
name just before the APCS stack frame. You can see it in action in the |fix_picture|
definition. The macro should be called immediately prior to the code which sets up the
stack frame, and it should be before the procedure entry point - as it is not executed. Exactly
as shown. If you enter your function with code before the stack frame is set up (which, in
itself, is highly unorthodox), you must Branch beyond, as shown:
|my_routine|
   ...your pre-entry code here...
   B      skip_head_my_routine
   HEAD   ("my_routine")
skip_head_my_routine
   MOV    ip, sp
   STMFD  sp!, {a1, a2, fp, ip, lr, pc}
   SUB    fp, ip, #4
   ...your routine code...
   LDMEA  fp, {fp, sp, pc}^
This is not 32-bit friendly.
I suppose, at a pinch, such breaking of the rules would be allowed for handlers which need to
exit in a real hurry. A sort of CMP a1, #somevalue then MOVNE pc, lr.
I should explain, that registers a1 and a2 contain data passed to the
function, so they are saved on the stack frame. Also, the value of pc is stored,
so it may be evaluated during a backtrace. We don't want to restore it though, so the frame
pointer value is subtracted by four. ip is used to refer to the original position
of the stack pointer, so write-back doesn't mess it up - this is later restored so the entire
stack frame can be easily dumped.For a full explanation of how this stuff works, you will need
to consult the PRMs.
The code is lightly commented, so no in-line comments will be given. If you are attempting this, it is assumed that you have worked through the earlier examples so can follow my coding...
This is not 32-bit friendly.
; picfixer
;
; Assembler code for "fixpiccy" utility.
;
;
; Version 0.01  Saturday, 15th July 2000
; by Richard Murray
;
; Downloaded from http://www.heyrick.co.uk/assembler/
;
a1 RN 0
a2 RN 1
a3 RN 2
a4 RN 3
v1 RN 4
v2 RN 5
v3 RN 6
fp RN 11
ip RN 12
sp RN 13
lr RN 14
pc RN 15
        ; Our code area is <C$$code>, created by the compiler
        AREA  |C$$code|, CODE, READONLY
        ; Only one procedure to export - the piccy fixer
        EXPORT |fix_picture|
        ; This macro sets up the APCS backtrace 'name'
        MACRO
        HEAD     $name
        =        $name, 0
        ALIGN
        &        &FF000000 :OR: (:LEN: $name + 4 :AND: -4)
        MEND
; "fix_picture"
;
; Entry:
;   R0 = Pointer to full filename
;   R1 = '1' if quiet mode.
;
; Uses:
;  R4 = Preserved copy of filename pointer
;  R5 = Preserved copy of quiet mode flag
;  R6 = File handle
;
; On exit, registers preserved.
;
        HEAD    ("fix_picture")
|fix_picture|
        MOV     ip, sp
        STMFD   sp!, {a1, a2, fp, ip, lr, pc}
        SUB     fp, ip, #4
        STMFD   sp!, {v1-v3}
        MOV     v1, a1                          ; Preserve filename pointer
        MOV     v2, a2                          ; Preserve '-quiet' status
        MOV     v3, #0
        ; Open sprite file
        MOV     a2, a1
        MOV     a1, #&CF                        ; Open for update (read/write)
        SWI     &2000D                          ; XOS_Find
        BVS     error_opening
        CMP     a1, #0                          ; Valid file handle?
        BEQ     error_opening
        MOV     v3, a1                          ; Preserve file handle
        ; Set file pointer to 52
        MOV     a1, #1
        MOV     a2, v3
        MOV     a3, #52
        SWI     &20009                          ; XOS_Args
        ; Read byte
        MOV     a2, v3
        SWI     &2000A                          ; XOS_BGet
        BCS     read_error
        CMP     a1, #28                         ; MODE 28?
        BEQ     is_mode28
        CMP     a1, #21                         ; MODE 21?
        BEQ     is_mode21
        ; Else, some other mode...
        CMP     v2, #1
        BEQ     close_file                      ; If quiet, return silently
        SWI     &01                             ; OS_WriteS
        =       "  Sprite is MODE ", 0
        ALIGN
        ADR     a2, mode_buffer
        MOV     a3, #4
        SWI     &DA                             ; OS_ConvertInteger2
        SWI     &02                             ; OS_Write0
        SWI     &01                             ; OS_WriteS
        =       ", cannot convert this", 13, 10, 13, 10, 0
        ALIGN
close_file
        MOV     a1, #0                          ; Close file
        MOV     a2, v3
        MOV     a3, #0
        CMP     v3, #0                          ; Don't do it if file=0 (close all)
        SWINE   &2000D                          ; XOS_Find
return
        LDMFD   sp!, {v1-v3}
        LDMEA   fp, {fp, sp, pc}^
mode_buffer
        DCD     0
        DCD     0
error_opening
        CMP     v2, #1
        BEQ     return                          ; If quiet, return silently
        SWI     &01                             ; OS_WriteS
        =       "  Unable to open file ", 34, 0
        ALIGN
        MOV     a1, v1
        SWI     &02                             ; OS_Write0
        SWI     &01                             ; OS_WriteS
        =       34, 13, 10, 13, 10, 0
        ALIGN
        B       return
read_error
        CMP     v2, #1
        BEQ     close_file                      ; If quiet, return silently
        SWI     &01                             ; OS_WriteS
        =       "  Unable to read from file ", 34, 0
        ALIGN
        MOV     a1, v1
        SWI     &02                             ; OS_Write0
        SWI     &01                             ; OS_WriteS
        =       34, 13, 10, 13, 10, 0
        ALIGN
        B       close_file
write_error
        CMP     v2, #1
        BEQ     close_file                      ; If quiet, return silently
        SWI     &01                             ; OS_WriteS
        =       "  Unable to write to file ", 34, 0
        ALIGN
        MOV     a1, v1
        SWI     &02                             ; OS_Write0
        SWI     &01                             ; OS_WriteS
        =       34, 13, 10, 13, 10, 0
        ALIGN
        B       close_file
is_mode28
        CMP     v2, #1
        BEQ     close_file                      ; If quiet, return silently
        SWI     &01                             ; OS_WriteS
        =       "  Sprite is MODE 28, no conversion necessary", 13, 10, 13, 10, 0
        ALIGN
        B       close_file
is_mode21
        MOV     a1, #1                          ; Setting file pointer back to 52
        MOV     a2, v3
        MOV     a3, #52
        SWI     &20009                          ; XOS_Args
        MOV     a1, #28
        MOV     a2, v3
        SWI     &2000B                          ; XOS_BPut
        BVS     write_error
        CMP     v2, #1
        BEQ     close_file                      ; If quiet, return silently
        SWI     &01                             ; OS_WriteS
        =       "  Sprite converted to MODE 28", 13, 10, 13, 10, 0
        ALIGN
        B       close_file
        END