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