|
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...
[ {CONFIG} = 26
LDMEA fp, {fp, sp, pc}^
|
LDMEA fp, {fp, sp, pc}
]
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...
; picfixer
;
; Assembler code for "fixpiccy" utility.
;
;
; Version 0.01 Saturday, 15th July 2000
; by Richard Murray
; (32bitted Monday, 29th December 2003)
;
; 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}
[ {CONFIG} = 26
LDMEA fp, {fp, sp, pc}^
|
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