Example 6
fixpiccy

 

 

Introduction:

I used my Vision digitiser to grab frames from EuroSport's coverage of the Tour de France. All went well, except that the saved frames are MODE 21 sprites. When viewed full-screen (via the ScreenLoad command), they were rather flickery on my monitor.
The pixel depth of MODE 28 is the same as MODE 21, though the screen dimensions are slightly smaller - 640 x 480 as opposed to 640 x 512. A side effect of this, however, is the refresh rate is higher, leading to a sharper unflickering display.

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.

 

 

The C code:

The code is fairly self explanatory. If you do not have a C compiler, it is available in the example archive as 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!

 

 

The assembler code:

This is the real stuff. Proper, interwoven in high level language environment assembler. As mentioned above, don't go recoding all sorts of things in assembler just for the sake of it. Unless you want the exercise, there isn't much point. This is, purely, for the exercise. <grin>

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:

You will see that we have defined a macro called 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


Download example sources: fixpiccy.zip
This code requires objasm and (optionally) Norcroft C


Return to assembler index
Copyright © 2000 Richard Murray