ADVERTISEMENT
|
|
Picture Resource Scavenger
By David T. Craig, Kansas City, MO
INTRODUCTION
This article describes a MPW Pascal 3.0 unit that scavenges QuickDraw picture resources from corrupted resource files.
This unit produces as output a standard Macintosh text file containing the source listing for all the scavenged picture resources in a format compatible with Apple's Rez tool along with the actual scavenged picture resources which may be viewed by a program like Apple's ResEdit.
All non-corrupted picture resources (type PICT) within a file will be found by this program. This program makes no assumptions about the resource file. As such, this program ignores any format that the resource may contain. For example, the Resource Header is not assumed to be intact and is not used.
Progress information may also be displayed by this unit during the scavenging process. Currently, this information consists of simple phrases which detail the scavenger's progress on a picture basis. This information is only displayed if the program that uses this unit is running under the Apple MPW Shell. If the MPW Shell is not active, then no progress information appears.
A file containing PICT resources may become corrupted in many ways. Once corrupted, a file's resources can not be accessed. If Apple's ResEdit program can not open a file, then you are seriously in need of this PICT scavenger. Most resource files which become corrupt generally have an invalid Resource Header and Resource Map. These two areas within a resource file point to the actual resource data.
This unit should be viewed from a global Macintosh perspective as a first step in providing a set of modules that reconstruct corrupted data from Macintosh files. This concept is not new and has been around, as I far as I know, since the Xerox Alto in the mid 1970's. In this author's opinion, these types of services should exist at two levels. The first level is the lowest level and involves device data integrity. This means disk drives. Apple Computer should provide this level of service via its Macintosh Operating System. If a file becomes corrupted the OS should detect this fact, inform the user, and attempt to regain the corrupted data. Apple in its wonderful Lisa computer provided such a feature via the Lisa Scavenger and Tag Bytes on disk media. Apple has not pursued this avenue with the Macintosh. See Apple Macintosh Technical Note #94 for Tag Byte information. The second level of data integrity involves the integrity of file contents. This level of service can only be provided by the authors of Macintosh applications and their related documents. Implementing this service would require checksums (which includes CRCs), redundant file data structures, and built-in application recovery methods.
USING THIS UNIT
This unit contains two public routines. Routine PICT_Unit_Version returns a string representing the version number and compilation date and time of this unit. Routine PICT_Scavenger performs the actual scavenging process. The Pascal interface for this routine follows:
{1}
{
Routine ..... PICT_Scavenger
Purpose ..... Scavenge all the picture (PICT) resources
found in a file and write the picture data to a text file
containing the picture data as PICT resources and Rez
source code
Input .. ps_program_name - name of program (for text file)
ps_input_file_name - corrupted input file to scavenge
ps_input_file_volume - input file volume reference
ps_output_file_name - text output file for Rez source
ps_output_file_volume - output file volume reference
ps_show_progress - show scavenging progress flag
ps_pict_header_info - picture header/tail info
ps_starting_id - picture resource starting ID value
ps_decrement_id - decrement resource ID flag
Output ..ps_num_found_picts - no. pictures found in input file
ps_num_pict_failures - no. picture scavenge failures
ps_error - error result
}
PROCEDURE PICT_Scavenger (ps_program_name: Str255;
ps_input_file_name : Str255;
ps_input_file_volume : INTEGER;
ps_output_file_name : Str255;
ps_output_file_volume : INTEGER;
ps_show_progress : BOOLEAN;
ps_pict_header_info : PS_t_PHI_List;
ps_starting_id : INTEGER;
ps_decrement_id : BOOLEAN;
VAR ps_num_found_picts : INTEGER;
VAR ps_num_pict_failures : INTEGER;
VAR ps_error : INTEGER);
The key to this routine is the data type PS_t_PHI_List. This type contains a list of patterns which the unit uses to search for a PICT resource in a resource file. The type has the following structure:
{2}
PS_t_PHI_List = ARRAY [1..PS_c_MaxPICTVersions{4}] OF
PS_t_PICT_Header_Info;
where PS_t_PICT_Header_Info has the structure:
{3}
PS_t_PICT_Header_Info = RECORD
hi_version_data : Ptr; { header info }
hi_version_data_length : INTEGER;
hi_endpict_data : Ptr;
hi_endpict_data_length : INTEGER; { footer info }
END;
To scavenge version 1 pictures use the following values:
hi_version_data points to buffer holding value $1101
hi_version_data_length = 2
hi_endpict_data points to buffer holding $FF
hi_endpict_data_length = 1
To scavenge version 2 pictures use the following values:
hi_version_data points to buffer holding value $001102FF
hi_version_data_length = 4
hi_endpict_data points to buffer holding $00FF
hi_endpict_data_length = 2
Note that up to 4 picture header patterns may be used, but currently only two are needed since only two picture versions exist.
I wrote a simple MPW Shell tool called PICTScavenger in MPW Pascal 3.0 which tests this unit and provides a nice programming framework for tools. This tool has the following command line:
# 4
PICTScavenger [-p] [-s start_id] [-d] input_file [output_file]
The arguments are as follows:
-p: Show progress information. The default is to NOT show any progress information.
-s: Set starting PICT resource ID value to start_id. The default value is 1000.
-d: Decrement resource ID. The default is to increment the ID value.
Argument input_file is the name of the corrupted file. This argument must always exist in the argument list. This name MUST occur before the name of the output file, if one exists. Argument output_file is the name of the output file which will contain a Rez-compatible source listing of the pictures found in the corrupted input file. If no output file is specified, then a default output file is created whose name consists of the input file name suffixed with .PICT.r.
When run using a test file named test as input the following information appears in a MPW window:
PictScavenger -p test
Macintosh PICT File Scavenger 1.0.0 [9/28/90 - 11:06:12 AM]
David T. Craig (736 Edgewater, Wichita, Kansas 67230)
Not Copyright (c) 1990
PICT Scavenger unit version: 1.1.0 [9/28/90 - 3:25:23 PM]
Scavenging file test to Rez file test.PICT.r ...
Searching for PICT HEAD $1101 and PICT TAIL $FF
Scavenging PICT # 1000 [Address $00010E Length $0001D5]
Scavenging PICT # 1001 [Address $0002E7 Length $000677]
Scavenging PICT # 1002 [Address $000962 Length $000089]
*** PICT scavenge failure [Address $001800]
Searching for PICT HEAD $001102FF and PICT TAIL $00FF
Scavenging PICT # 1003 [Address $0009EF Length $0007A0]
Scavenging PICT # 1004 [Address $001193 Length $000716]
PICT Scavenger Summary:
Input file : test
Output file : test.PICT.r
Scavenged PICTs : 5
Scavenge failures : 1
Elapsed time : 0 minute 13 seconds
NOTE: Scavenge failures indicate that a PICT header was found, but the header was not valid. The file may contain a valid picture which this program could not scavenge.
Thats all, folks.
To compile the Rez file use the following command line :
#5
Rez -t ZZZZ test.PICT.r -o test pictures
FORMAT OF PICTURES IN A RESOURCE FILE
A PICT resource within a resource file has a specific format. All pictures begin with a specific pattern of bytes which differ per picture version. I call this pattern the Picture Header Pattern (PHP). Preceding this pattern exists an 8 byte rectangle which represents the picture frame and a 2 byte integer which represents the picture byte size. This size integer is actually the low order word for the pictures true size. Preceding the size integer in the file is a 4 byte longint value which contains the physical size of the picture data. All pictures end with a pattern of bytes which I call the Picture Tail Pattern (PTP). This pattern is the End-Of-Picture Opcode. For version 1 pictures the pattern contains one byte, for version 2 it has two bytes.
Since a (non-QuickDraw) picture is worth a thousand words?
+------------------+------------------+------------------+--------------+----------------------+--------------+
| PSize 4 | LSize 2 | Frame 8 | PHP X | Pict Data | PTP Y |
+------------------+------------------+------------------+--------------+----------------------+--------------+
For version 1 pictures, X = 2 and Y = 1
For version 2 pictures, X = 4 and Y = 2
PICTURE RESOURCE SEARCH ALGORITHM
The algorithm used to locate picture resources within a resource file is quite straight forward. This algorithm contains 6 steps:
Step 1: Start at the beginning of the resource fork data
Step 2: Locate the next Picture Header Pattern
Step 3: Verify that the Picture Header Pattern is preceded by a valid rectangle and the word and longword least significant word before the rectangle data are equal
Step 4: Verify that the Picture Tail Pattern and the last opcode in the picture data match
Step 5: If steps 2 to 4 are successful, then a picture has been found in the resource file
Step 6: Repeat steps 2-5 until the physical end-of-file of the resource fork is found
BIBLIOGRAPHY
Resource file format : Inside Macintosh, vol. I, p. 128+
QuickDraw pictures : Inside Macintosh, vol. I, p. 158+
QuickDraw picture opcodes: Apple Technical Note # 21, Inside Macintosh, vol. V, p.92+
PICTURES IN TEST

id = 1000

id = 1001

id = 1002

id = 1003

id = 1004
Listing: PICTScavenger.make
# File: PICTScavenger.make
# Target: PICTScavenger
# Sources: PICTScavenger.p U_PICT_Scavenger.p
U_PICT_Scavenger.p.o PICTScavenger.make U_PICT_Scavenger.p
Pascal -ov -u U_PICT_Scavenger.p
PICTScavenger.p.o PICTScavenger.make PICTScavenger.p
Pascal -ov -u PICTScavenger.p
SOURCES = PICTScavenger.p U_PICT_Scavenger.p
OBJECTS = PICTScavenger.p.o U_PICT_Scavenger.p.o
PICTScavenger PICTScavenger.make {OBJECTS}
Link -w -c 'MPS ' -t MPST
{OBJECTS}
"{Libraries}"stubs.o
"{Libraries}"Runtime.o
"{Libraries}"Interface.o
"{PLibraries}"PasLib.o
"{PLibraries}"SANELib.o
"{Libraries}"ToolLibs.o
-o PICTScavenger
### FINIS
Listing: PICTScavenger.p
{
----------------------------------------------------
APPLE MACINTOSH PICT RESOURCE SCAVENGER MPW TOOL
----------------------------------------------------
Version 1.0.0
Author ......... David T. Craig
Address ........ 736 Edgewater, Wichita, Kansas 67230
Date ........... August 1990
Language ....... Apple MPW Pascal 3.0
Computer ....... Apple Macintosh
Environment .... Apple MPW Shell
PURPOSE:
This program is an MPW tool which scavenges PICT (picture) resources
from a corrupted file. Since this program is an MPW
tool, it can only be run under the MPW Shell. Only the resource fork
of a file is searched for picture resources since this is the only fork
of a Macintosh file that should contain resources.
This program produces as output a standard Macintosh text file
containing the source listing for all the scavenged picture resources
in a format compatible with Apple's Rez tool.
To produce a file with actual PICT resources requires compiling
the text file with Rez. Rez version 3.0 works well with the output files
from this program.
USING THIS PROGRAM:
This program is used from the MPW Shell by typing the tool name
and a list of arguments for the tool. This argument list must always
contain at least the name of the corrupted file whose picture resources
are desired. Additional arguments may also exist in the argument list.
The format for the tool and its arguments follow with arguments
enclosed in [] being optional (argument order is unimportant):
PICTScavenger [-p] [-s start_id] [-d] input_file [output_file]
The arguments are as follows:
-p : Show progress information. The default is to NOT show
any progress information.
-s : Set starting PICT resource ID value to start_id. The default
value is 1000.
-d : Decrement resource ID. The default is to increment the
ID value.
Argument input_file is the name of the corrupted file. This
argument must always exist in the argument list. This name MUST occur
before the name of the output file, if one exists.
Argument output_file is the name of the output file which will
contain a Rez-compatible source listing of the pictures found in the
corrupted input file. If no output file is specified, then default output
file created whose name consists of the input file name suffixed with
".PICT.r".
PICT SCAVENGER ERRORS:
Various errors may occur when using PICTScavenger. Before a file
is scavenged the command line arguments are examined. Failure of a
command argument generate one of the following errors:
Error Number Error Meaning
------------ ------------------------------------------
1 Number of arguments in command is invalid
2 Too many file names specified
3 Input file name is missing
4 Starting ID value is invalid
5 Invalid option encountered
Other errors may also occur. These will be displayed without
an error message detailing the type of error. Refer to the list of Macintosh
errors as provided by Apple in its Inside Macintosh.
NOT COPYRIGHT (C) 1990 BY DAVID T. CRAIG
}
PROGRAM Macintosh_PICT_Scavenger_Tool;
{
EXTERNAL MODULES
}
USES
MemTypes, { Macintosh common types }
OSIntf, { Macintosh Operating System interface }
ToolIntf, { Macintosh ToolBox interface }
Packages, { Macintosh Package interface }
PasLibIntf, { Pascal runtime libary interface }
IntEnv, { MPW integrated environment interface }
CursorCtl, { MPW shell cursor unit }
{$U U_PICT_Scavenger.p} U_PICT_Scavenger; { the real working code
}
{
COMPILER DIRECTIVES
}
{$R+ } { [MPWPASCAL] enable range checking }
{$SC+} { [MPWPASCAL] encable short-circuit boolean evaluation }
{$OV+} { [MPWPASCAL] enable integer-type overflow checking }
{
GLOBAL CONSTANTS
}
CONST
{ general program info (appears if Progress option set) }
gc_pgm_name = 'Macintosh PICT File Scavenger';
gc_pgm_version = '1.0.0';
gc_pgm_date = COMPDATE;
gc_pgm_time = COMPTIME;
gc_pgm_author = 'David T. Craig';
gc_pgm_address = '736 Edgewater, Wichita, Kansas 67230';
gc_pgm_copyright = 'Not Copyright (c) 1990';
{ program error values (passed back to MPW Shell) }
gc_err_min = 1; { min and max tool internal error codes
}
gc_err_max = 5;
gc_err_bad_argc = 1; { ArgC value is invalid }
gc_err_extra_file_name = 2; { Too many file names specified }
gc_err_no_input_file = 3; { No input file name found in command
}
gc_err_bad_starting_id = 4; { Start ID value is invalid }
gc_err_bad_option = 5; { Invalid command opt found }
gc_output_file_suffix = '.PICT.r'; { suffix for default output
file }
gc_default_starting_id = 1000; { default starting resource ID }
gc_shell_comment = '### '; { shell message line prefix }
{ scavenge failure notice message }
gc_fail_1 = 'NOTE: Scavenge failures indicate that a PICT header
was found, ';
gc_fail_2 = ' but the header was not valid. The file may contain
a valid ';
gc_fail_3 = ' picture which this program could not scavenge.';
{
GLOBAL TYPES
}
TYPE
{ command interpreter (aka Shell) arguments }
gt_ci_args = RECORD
ci_show_progress : BOOLEAN; { show progress flag }
ci_starting_id : INTEGER; { starting ID value }
ci_decrement_id : BOOLEAN; { decrement ID flag }
ci_input_file : Str255; { input file name }
ci_output_file : Str255; { output file name }
END;
{ version 1 picture starting and ending opcode info }
gt_pict_1_info = RECORD
p1_version : INTEGER; { [2] $1101 }
p1_endpict : INTEGER; { [1] $FF?? (ignore LSB) }
END;
{ version 2 picture starting and ending opcode info }
gt_pict_2_info = RECORD
p2_version : LONGINT; { [4] $0011 $02FF }
p2_endpict : INTEGER; { [2] $00FF }
END;
{
GLOBAL VARIABLES
}
VAR
gv_shell_result : INTEGER; { MPW shell result code }
gv_command_name : Str255; { name of tool command }
gv_ci_args : gt_ci_args; { command interpreter arguments }
gv_num_found_picts : INTEGER;
{ no. PICTs scavenged from input file }
gv_num_pict_failures : INTEGER;
{ no. PICT scavenge failures }
gv_exec_time : LONGINT;
{ scavenger execution time (seconds) }
gv_pict_1_info : gt_pict_1_info; { version 1 picture info }
gv_pict_2_info : gt_pict_2_info; { version 2 picture info }
gv_pict_info : PS_t_PHI_List; { list of picture info }
{
Routine ..... show_Error
Purpose ..... Display an error message to the user
Input ....... the_error_code - error code value
Output ...... (none)
}
PROCEDURE show_Error (the_error_code : INTEGER);
VAR
message : Str255; { english error message for bozo user }
BEGIN { ------------ show_Error ------------ }
{ setup the error message for the user }
CASE the_error_code OF
gc_err_bad_argc : message := 'Invalid argument count';
gc_err_extra_file_name : message := 'Too many file names specified';
gc_err_no_input_file : message := 'No input file name was found';
gc_err_bad_starting_id : message := 'Invalid starting ID value
found';
gc_err_bad_option : message := 'Invalid command line option found';
OTHERWISE message := 'Unknown Macintosh error code';
{ Macintosh error }
END;
{ display the error message to the user }
WRITELN;
WRITELN(gc_shell_comment, 'ERROR # ',the_error_code:1,' in ', gv_command_name,
' (',message,')');
END; { ------------ show_Error ------------ }
{
Routine ..... uppercase_Phrase
Purpose ..... Uppercase the characters in a phrase
Input ....... the_phrase - phrase to uppercase (eg: "David")
Output ...... the_phrase - uppercased phrase (eg: "DAVID")
}
PROCEDURE uppercase_Phrase (VAR the_phrase : Str255);
VAR
ch_index : 1..255; { phrase character index }
BEGIN { ------------ uppercase_Phrase ------------ }
IF LENGTH(the_phrase) > 0 THEN
FOR ch_index := 1 TO LENGTH(the_phrase) DO
BEGIN
IF the_phrase[ch_index] IN ['a'..'z'] THEN
the_phrase[ch_index] :=
CHR( ORD(the_phrase[ch_index]) - ORD('a') + ORD('A')
);
END;
END; { ------------ uppercase_Phrase ------------ }
{
Routine ..... parse_Command_Arguments
Purpose ..... Parse command arguments from shell command line
Input ....... the_arg_count - number of arguments in line
Output ...... the_ci_args - parsed argument info
the_shell_result - parsing result error code
}
PROCEDURE parse_Command_Arguments( the_arg_count : INTEGER; VAR the_ci_args
: gt_ci_args; VAR the_shell_result : INTEGER);
CONST
k_arg_option_symbol = '-'; { option prefix character }
{ option characters }
k_option_progress = 'P'; { option: show progress }
k_option_start_id = 'S'; { option: starting ID value }
k_option_dec_id = 'D'; { option: decrement ID value }
VAR
arg_index : INTEGER; { argument list indexer }
arg_phrase : Str255; { argument phrase from argument list }
arg_value : LONGINT; { value of a numeric argument phrase }
good_option : BOOLEAN; { valid option found flag }
BEGIN { --------- parse_Command_Arguments --------- }
{ initialize the command arguments }
the_ci_args.ci_show_progress := FALSE;
the_ci_args.ci_starting_id := gc_default_starting_id;
the_ci_args.ci_decrement_id := FALSE;
the_ci_args.ci_input_file := '';
the_ci_args.ci_output_file := '';
{ scan the argument list extracting the arguments and }
{ performing error checking on all the read arguments }
arg_index := 0;
REPEAT
BEGIN
{ get the next argument from the argument list }
arg_index := arg_index + 1;
IF arg_index < the_arg_count THEN
BEGIN
arg_phrase := ArgV^[arg_index]^;
{ got an argument ! }
{ test if argument is an option (vs. file name) }
IF arg_phrase[1] = k_arg_option_symbol THEN
BEGIN
{ ####################################### }
{ ### ARGUMENT: OPTION ### }
{ ####################################### }
good_option := FALSE;
{ assume option is invalid }
DELETE(arg_phrase,1,1);
{ remove option leading symbol }
{ check for an empty argument option }
IF LENGTH(arg_phrase) >= 1 THEN
BEGIN
uppercase_Phrase(arg_phrase);
{ ####################### SHOW PROGESS }
IF arg_phrase = k_option_progress THEN
BEGIN
good_option := TRUE;
the_ci_args.ci_show_progress := TRUE;
END;
{ ################## STARTING ID VALUE }
IF (the_shell_result = NoErr) AND
(arg_phrase = k_option_start_id) THEN
BEGIN
good_option := TRUE;
arg_index := arg_index + 1;
IF arg_index >= the_arg_count THEN
the_shell_result := gc_err_bad_starting_id
{ ERROR }
ELSE
BEGIN
arg_phrase := ArgV^[arg_index]^;
StringToNum(arg_phrase, arg_value);
IF (arg_value < -MAXINT-1) OR (arg_value
> MAXINT) THEN
the_shell_result := gc_err_bad_starting_id
{ ERROR }
ELSE
the_ci_args.ci_starting_id := LoWord(arg_value);
END;
END;
{ ################ DECREMENT ID VALUE }
IF (the_shell_result = NoErr) AND
(arg_phrase = k_option_dec_id) THEN
BEGIN
good_option := TRUE;
the_ci_args.ci_decrement_id := TRUE;
END;
END;
{ check that the found option is valid }
IF NOT(good_option) AND (the_shell_result = NoErr)
THEN
the_shell_result := gc_err_bad_option;
END
ELSE
{ a file name must be present if arg is not an option }
BEGIN
{ ######################################## }
{ ### ARGUMENT: NON-OPTION ### }
{ ######################################## }
{ NOTE: extract INPUT file first,
OUTPUT file second }
IF the_ci_args.ci_input_file = '' THEN
the_ci_args.ci_input_file := arg_phrase
ELSE
BEGIN
IF the_ci_args.ci_output_file = '' THEN
the_ci_args.ci_output_file := arg_phrase
ELSE
BEGIN
the_shell_result := gc_err_extra_file_name;
END;
END;
END;
END;
END;
UNTIL (arg_index >= the_arg_count) OR (the_shell_result <> NoErr);
{ verify that the input file name is present and assign a default
name to the output file name if the output file name is missing }
IF the_shell_result = NoErr THEN
BEGIN
IF the_ci_args.ci_input_file = '' THEN
the_shell_result := gc_err_no_input_file;
IF the_shell_result = NoErr THEN
BEGIN
IF the_ci_args.ci_output_file = '' THEN
the_ci_args.ci_output_file :=
CONCAT(the_ci_args.ci_input_file,gc_output_file_suffix);
END;
END;
END; { ---------- parse_Command_Arguments ---------- }
{
Routine ..... init_Shell_Stuff
Purpose ..... Initialize various items for the Shell and the tool
Input ....... (none)
Output ...... the_tool_name - name of Shell tool
}
PROCEDURE init_Shell_Stuff (VAR the_tool_name : Str255);
BEGIN { ------------ init_Shell_Stuff ------------ }
{ fetch tool name from MPW Shell command line }
the_tool_name := ArgV^[0]^;
uppercase_Phrase(the_tool_name);
{ UC tool name since UC is pretty }
{ enable text output line buffering by calling MPW Shell RT library
}
PLSetVBuf(OUTPUT,NIL,_IOLBF,0);
END; { ------------ init_Shell_Stuff ------------ }
{
Routine ..... show_Elapsed_Time
Purpose ..... Write a line containing the elapsed time for the tool
Input ....... the_elapsed_sec_time - elapsed time in seconds
Output ...... (none)
}
PROCEDURE show_Elapsed_Time (the_elapsed_sec_time : LONGINT);
VAR
elapsed_min : INTEGER; { no. elapsed minutes }
elapsed_sec : INTEGER; { no. elapsed seconds }
BEGIN { ------------ show_Elapsed_Time ------------ }
the_elapsed_sec_time := ABS(the_elapsed_sec_time);
elapsed_min := the_elapsed_sec_time DIV 60;
elapsed_sec := the_elapsed_sec_time MOD 60;
WRITE(elapsed_min:1,' ');
IF elapsed_min <=1 THEN WRITE('minute ')
ELSE WRITE('minutes ');
WRITE(elapsed_sec:1,' ');
IF elapsed_sec <=1 THEN WRITELN('second')
ELSE WRITELN('seconds');
END; { ------------ show_Elapsed_Time ------------ }
{
THE MAIN EVENT
}
BEGIN { -------- Macintosh_PICT_Scavenger_Tool -------- }
gv_shell_result := NoErr;
{ initialize MPW shell result code }
init_Shell_Stuff(gv_command_name);
IF NOT(ArgC IN [2..6]) THEN
BEGIN
gv_shell_result := gc_err_bad_argc; { !!! ERROR !!! }
show_Error(gv_shell_result);
END
ELSE { MPW Shell command line is valid, so parse the descriptor line
}
BEGIN
{ fetch the command arguments from the MPW Shell command line
}
parse_Command_Arguments(ArgC,gv_ci_args,gv_shell_result);
IF gv_shell_result <> NoErr THEN
show_Error(gv_shell_result) { !!! ERROR !!! }
ELSE
BEGIN
{ tell the user a little about this wonderful tool }
IF gv_ci_args.ci_show_progress THEN
BEGIN
WRITELN;
WRITELN(gc_pgm_name,' ',gc_pgm_version, ' [', gc_pgm_date,'
- ',gc_pgm_time,']');
WRITELN(gc_pgm_author,' (',gc_pgm_address,')');
WRITELN(gc_pgm_copyright);
WRITELN;
WRITELN('PICT Scavenger unit version: ',PICT_Unit_Version);
END;
{ perform the actual PICT scavenging process by first setting
up the picture information list and then calling the module which actually
performs the scavenging process }
gv_pict_1_info.p1_version := $1101;
gv_pict_1_info.p1_endpict := $FF00; {< ignore LSB }
gv_pict_2_info.p2_version := $001102FF;
gv_pict_2_info.p2_endpict := $00FF;
gv_pict_info[1].hi_version_data := @gv_pict_1_info.p1_version;
gv_pict_info[1].hi_version_data_length := 2;
gv_pict_info[1].hi_endpict_data := @gv_pict_1_info.p1_endpict;
gv_pict_info[1].hi_endpict_data_length := 1;
gv_pict_info[2].hi_version_data := @gv_pict_2_info.p2_version;
gv_pict_info[2].hi_version_data_length := 4;
gv_pict_info[2].hi_endpict_data := @gv_pict_2_info.p2_endpict;
gv_pict_info[2].hi_endpict_data_length := 2;
gv_pict_info[3].hi_version_data := NIL;
{ >>>>> UNUSED <<<<< }
gv_pict_info[3].hi_version_data_length := 0;
gv_pict_info[3].hi_endpict_data := NIL;
gv_pict_info[3].hi_endpict_data_length := 0;
gv_pict_info[4] := gv_pict_info[3];
{ >>>>> UNUSED <<<<< }
gv_exec_time := TickCount;
{ start the execution timer }
{ ############################## go for it ... }
PICT_Scavenger(CONCAT(gv_command_name,' ', gc_pgm_version),
gv_ci_args.ci_input_file,0,
gv_ci_args.ci_output_file,0,
gv_ci_args.ci_show_progress,
gv_pict_info, gv_ci_args.ci_starting_id,
gv_ci_args.ci_decrement_id, gv_num_found_picts,
gv_num_pict_failures, gv_shell_result);
gv_exec_time := ABS(TickCount - gv_exec_time) DIV 60; { end
the timer }
IF gv_shell_result <> NoErr THEN
show_Error(gv_shell_result) { !!! ERROR !!! }
ELSE
BEGIN
{ tell the user a little about the scavenging process }
IF gv_ci_args.ci_show_progress THEN
BEGIN
WRITELN;
WRITELN('PICT Scavenger Summary:');
WRITELN;
WRITELN(' Input file : ',gv_ci_args.ci_input_file);
WRITELN(' Output file : ',gv_ci_args.ci_output_file);
WRITELN(' Scavenged PICTs : ',gv_num_found_picts:1);
WRITELN(' Scavenge failures : ',gv_num_pict_failures:1);
WRITE (' Elapsed time : ');
show_Elapsed_Time(gv_exec_time);
WRITELN;
IF gv_num_pict_failures > 0 THEN
BEGIN
WRITELN(gc_fail_1);
WRITELN(gc_fail_2);
WRITELN(gc_fail_3);
WRITELN;
END;
END;
{ tell the user that I'm done (for now) }
IF gv_ci_args.ci_show_progress THEN
BEGIN
WRITELN('That''s all, folks.');
END;
END;
END;
END;
IF (gv_shell_result >= gc_err_min) AND (gv_shell_result <= gc_err_max)
THEN
BEGIN
{ display the command line for this tool since the user does not appear
to know how to use this command }
WRITELN(gc_shell_comment,gv_command_name,
' [-p] [-s start_id] [-d] input_file [output_file]');
WRITELN(gc_shell_comment,' -p : show progress information');
WRITE (gc_shell_comment,' -s : setup starting PICT resource
ID value ');
WRITELN( '[',-MAXINT-1:1,'..',MAXINT:1,']');
WRITELN(gc_shell_comment,' -d : decrement PICT resource ID value');
END;
IEExit(gv_shell_result); { tell MPW shell how I'm feeling }
END. { --------- Macintosh_PICT_Scavenger_Tool --------- }
{
FINIS
}
Listing: U_PICT_Scavenger.p
{
--------------------------------------------------
APPLE MACINTOSH PICT RESOURCE SCAVENGER MODULE
--------------------------------------------------
Version 1.1.0
Author ......... David T. Craig
Address ........ 736 Edgewater, Wichita, Kansas 67230
Date ........... August 1990
Language ....... Apple MPW Pascal 3.0
Computer ....... Apple Macintosh
Environment .... Apple MPW Shell
PURPOSE:
This module scavenges PICT (picture) resources from a corrupted
file. Only the resource fork of a file is searched for picture resources
since this is the only fork of a Macintosh file that should contain resources.
This module produces as output a standard Macintosh text file
containing the source listing for all the scavenged picture resources
in a format compatible with Apple's Rez tool.
To produce a file with actual PICT resources requires compiling
the text file with Rez. Rez version 3.0 works well with the output files
from this program. One may also use Apple's stand-alone version of Rez
which is named SARez. SARez runs under the Finder and does not require
the MPW development Shell. The output file also contains the acutal scavenged
picture resources which may be viewed by a program like Apple's ResEdit.
USING THIS MODULE:
This module contains two public routines.
Routine PICT_Unit_Version returns a string representing the version
number and compilation date and time of this module.
Routine PICT_Scavenger performs the actual scavenging process.
Callers should provide it with the name of the corrupted file
and the name of the output text file to contain the picture source code
in a format for Rez.
NOT COPYRIGHT (C) 1990 BY DAVID T. CRAIG
}
UNIT U_PICT_Scavenger;
{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% }
{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% }
INTERFACE
{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% }
{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% }
{
EXTERNAL MODULES
}
USES
MemTypes, { Macintosh common types }
OSIntf, { Macintosh Operating System interface }
ToolIntf, { Macintosh ToolBox interface }
Packages, { Macintosh Package interface }
PasLibIntf, { Pascal runtime libary interface }
IntEnv; { MPW integrated environment interface }
{
COMPILER DIRECTIVES
}
{$R+ } { [MPWPASCAL] enable range checking }
{$OV+} { [MPWPASCAL] enable overflow checking }
{
GLOBAL MODULE CONSTANTS
}
CONST
PS_c_MaxPICTVersions = 4;
{ max no. picture versions supported }
PS_c_Err_Minimum = 5000;
{ minimum error value from this module }
PS_c_Err_Maximum = 5002;
{ maximum error value from this module }
PS_c_Err_PICT_Header_Ptr = 5000;
{ picture list pointer is NIL }
PS_c_Err_PICT_Header_Length = 5001;
{ picture list length is invalid }
PS_c_Err_Internal = 5002;
{ internal error (big time trouble) }
{
GLOBAL MODULE TYPES
}
TYPE
{ pict vers. header pattern info record for HEADER & FOOTER }
PS_t_PICT_Header_Info = RECORD
hi_version_data : Ptr; { header info }
hi_version_data_length : INTEGER;
hi_endpict_data : Ptr;
hi_endpict_data_length : INTEGER; { footer info }
END;
{ list of picture version pattern headers (currently a fixed size)
}
PS_t_PHI_List = ARRAY [1..PS_c_MaxPICTVersions] OF PS_t_PICT_Header_Info;
{
Routine ..... PICT_Unit_Version
Purpose ..... Return version information about this module as a phrase
Input ....... (none)
Output ...... PICT_Unit_Version - version info phrase
}
FUNCTION PICT_Unit_Version : Str255;
{
Routine ..... PICT_Scavenger
Purpose ..... Scavenge all the picture (PICT) resources found in
a file and write the picture data to a text file containing the picture
data as PICT resources and Rez source code
Input .. ps_program_name - name of program (for text file)
ps_input_file_name - corrupted input file to scavenge
ps_input_file_volume - input file volume reference
ps_output_file_name - text output file for Rez source
ps_output_file_volume - output file volume reference
ps_show_progress - show scavenging progress flag
ps_pict_header_info - picture header/tail info
ps_starting_id - picture resource starting ID value
ps_decrement_id - decrement resource ID flag
Output..ps_num_found_picts - # pictures found in input file
ps_num_pict_failures - no. picture scavenge failures
ps_error - error result
}
PROCEDURE PICT_Scavenger (ps_program_name : Str255;
ps_input_file_name : Str255;
ps_input_file_volume : INTEGER;
ps_output_file_name : Str255;
ps_output_file_volume : INTEGER;
ps_show_progress : BOOLEAN;
ps_pict_header_info : PS_t_PHI_List;
ps_starting_id : INTEGER;
ps_decrement_id : BOOLEAN;
VAR ps_num_found_picts : INTEGER;
VAR ps_num_pict_failures : INTEGER;
VAR ps_error : INTEGER);
{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% }
{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% }
IMPLEMENTATION
{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% }
{ %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% }
{$S SgPICTScavenger} { segment this guy }
{
GLOBAL PRIVATE CONSTANTS
}
CONST
pc_unit_version = '1.1.0'; { unit version info }
pc_max_data_length = 32; { max length of PICT header data or endpict
data }
pc_cursor_info_sig = $31415926; { signature for cursor info record
}
pc_max_cursor = 4; { no. spinning cursors }
pc_debug = FALSE; { internal debugging flag for MPW Shell }
{
GLOBAL PRIVATE TYPES
}
TYPE
{ info about the output file that holds picture resources }
pt_file_info = RECORD
fi_file_name : Str255;
fi_file_volume : INTEGER;
END;
{ PICT resource HEADER info as contained in a resource file }
pt_pict_header = RECORD
phd_length_long : LONGINT; {[4] true length of res data }
phd_length_word : INTEGER; {[2] low word of len value }
phd_frame : Rect; { [8] PICT bounding box }
END;
pt_hex_byte = STRING[2];
{ hexadecimal strings for dec->hex conversion }
pt_hex_word = STRING[4];
pt_hex_long = STRING[8];
{ busy cursor stuff }
pt_cursor_data = ARRAY [0..pc_max_cursor-1] OF Cursor;
pt_cursor_info = RECORD
ci_signature : LONGINT; { init signature value }
ci_count : INTEGER; { spin count }
ci_cursor : pt_cursor_data; { cursor data list }
END;
{
GLOBAL PRIVATE VARIABLES
}
VAR
pv_file_info : pt_file_info; { output file information }
pv_show_progress : BOOLEAN; { global "show progress" flag }
pv_cursor_info : pt_cursor_info; { busy cursor info }
pv_debug : BOOLEAN; { internal debugging flag for MPW }
{
MODULE PRIVATE ROUTINES
}
{
Routine ..... sbyte_2_USByte
Purpose ..... Convert signed byte (-128..127) to unsigned byte (0..255)
Input ....... the_sbyte
Output ...... sbyte_2_USByte
Notes ....... This routine is needed since a Macintosh Ptr type points
to a signed byte value (-128..127) and this module favors highly unsigned
bytes (0..255).
}
FUNCTION sbyte_2_USByte (the_sbyte : SignedByte) : CHAR;
BEGIN { ------------ sbyte_2_USByte ------------ }
IF the_sbyte < 0 THEN
sbyte_2_USByte := CHR(the_sbyte + 256)
ELSE
sbyte_2_USByte := CHR(the_sbyte);
END; { ------------ sbyte_2_USByte ------------ }
{
Routine ..... my_Rotate_Cursor
Purpose ..... Rotate the spinning progress cursor one rotation
Input ....... (none)
Output ...... (none)
Notes ....... If the cursor data has not be initialized, this routine
automatically initializes the cursor data. The cursor data is defined
in this routine and not in a resource.
}
PROCEDURE my_Rotate_Cursor;
BEGIN { ------------ my_Rotate_Cursor ------------ }
IF pv_cursor_info.ci_signature <> pc_cursor_info_sig THEN
BEGIN
pv_cursor_info.ci_signature := pc_cursor_info_sig;
pv_cursor_info.ci_count := 0;
{ use the Apple standard BEACH BALL cursor }
WITH pv_cursor_info.ci_cursor[0] DO
BEGIN
StuffHex(@Data,CONCAT('07C01F303F087F047F04FF02FF02FFFE',
'81FE81FE41FC41FC21F819F007C00000'));
StuffHex(@Mask,CONCAT('07C01FF03FF87FFC7FFCFFFEFFFEFFFE',
'FFFEFFFE7FFC7FFC3FF81FF007C00000'));
SetPt(HotSpot,8,8);
END;
WITH pv_cursor_info.ci_cursor[1] DO
BEGIN
StuffHex(@Data,CONCAT('07C01FF03FF85FF44FE487C283828102',
'838287C24FE45FF43FF81FF007C00000'));
StuffHex(@Mask,CONCAT('07C01FF03FF87FFC7FFCFFFEFFFEFFFE',
'FFFEFFFE7FFC7FFC3FF81FF007C00000'));
SetPt(HotSpot,8,8);
END;
WITH pv_cursor_info.ci_cursor[2] DO
BEGIN
StuffHex(@Data,CONCAT('07C019F021F841FC41FC81FE81FEFFFE',
'FF02FF027F047F043F081F3007C00000'));
StuffHex(@Mask,CONCAT('07C01FF03FF87FFC7FFCFFFEFFFEFFFE',
'FFFEFFFE7FFC7FFC3FF81FF007C00000'));
SetPt(HotSpot,8,8);
END;
WITH pv_cursor_info.ci_cursor[3] DO
BEGIN
StuffHex(@Data,CONCAT('07C018302008701C783CFC7EFEFEFFFE',
'FEFEFC7E783C701C2008183007C00000'));
StuffHex(@Mask,CONCAT('07C01FF03FF87FFC7FFCFFFEFFFEFFFE',
'FFFEFFFE7FFC7FFC3FF81FF007C00000'));
SetPt(HotSpot,8,8);
END;
END;
WITH pv_cursor_info DO
BEGIN
IF ci_count = MAXINT THEN
ci_count := 0
ELSE
ci_count := ci_count + 1;
SetCursor(ci_cursor[ci_count MOD pc_max_cursor]);
END; { WITH pv_cursor_info }
END; { ------------ my_Rotate_Cursor ------------ }
{
Routine ..... show_Progress_Message
Purpose ..... Display progress information to the caller
Input ....... the_message - progress message to display
Output ...... (none)
Notes ....... Currently, progress information can only go to the
MPW Shell. As such, if the shell is not active, then no progress information
will be sent.
}
PROCEDURE show_Progress_Message (the_message : Str255);
BEGIN { ------------ show_Progress_Message ------------ }
IF pv_show_progress THEN { caller s wnats progress and Shell exists
}
BEGIN
WRITELN(the_message);
END;
END; { ------------ show_Progress_Message ------------ }
{
Routine ..... myWrite
Purpose ..... Write a phrase to an open text file (no CR on end)
Input ....... the_file_ref - open text file reference
the_message - phrase to write to file
Output ...... the_error - error result
}
PROCEDURE myWrite ( the_file_ref : INTEGER;
the_message : Str255;
VAR the_error : INTEGER);
VAR
byte_count : LONGINT; { no. bytes to write to file }
BEGIN { ------------ myWrite ------------ }
IF the_message <> '' THEN
BEGIN
byte_count := LENGTH(the_message);
the_error := FSWrite(the_file_ref,
byte_count, Ptr( ORD4(@the_message) + 1) );
END;
END; { ------------ myWrite ------------ }
{
Routine ..... myWriteLn
Purpose ..... Write a phrase to an open text file (with a CR on end)
Input ....... the_file_ref - open text file reference
the_message - phrase to write to file
Output ...... the_error - error result
}
PROCEDURE myWriteLn ( the_file_ref : INTEGER;
the_message : Str255;
VAR the_error : INTEGER);
CONST
k_ascii_cr = CHR(13); { message terminator (good old ASCII CR)
}
VAR
byte_count : LONGINT; { no. bytes to write to file }
BEGIN { ------------ myWriteLn ------------ }
the_error := NoErr; { assume all will go well }
{ write the message to the file }
IF the_message <> '' THEN
BEGIN
byte_count := LENGTH(the_message);
the_error := FSWrite(the_file_ref,
byte_count, Ptr( ORD4(@the_message) + 1) );
END;
{ write the message terminator to the file also }
IF the_error = NoErr THEN
BEGIN
the_message := '?';
the_message[1] := k_ascii_cr;
byte_count := LENGTH(the_message);
the_error := FSWrite(the_file_ref,
byte_count, Ptr( ORD4(@the_message) + 1) );
END;
END; { ------------ myWriteLn ------------ }
{
Routine ..... get_System_DateTime
Purpose ..... Return the current system date and time as a phrase
Input ....... (none)
Output ...... the_date_and_time - date and time phrase
}
PROCEDURE get_System_DateTime (VAR the_date_and_time : Str255);
VAR
mac_clock : LONGINT; { raw Macintosh clock info }
mac_date : Str255; { Macintosh date phrase }
mac_time : Str255; { Macintosh time phrase }
BEGIN { ------------ get_System_DateTime ------------ }
GetDateTime(mac_clock);
IUDateString(mac_clock,LongDate,mac_date);
IUTimeString(mac_clock,TRUE{WantSeconds},mac_time);
the_date_and_time := CONCAT(mac_date,' -- ',mac_time);
END; { ------------ get_System_DateTime ------------ }
{
Routine ..... byte2Hex
Purpose ..... Convert an unsigned BYTE value to a hex string
Input ....... the_byte - value to convert
Output ...... the_hex - hex string equivalent to value
}
PROCEDURE byte2Hex (the_byte : CHAR; VAR the_hex : pt_hex_byte);
VAR
hex_digits : STRING[16]; { list of hex digits $0..$F }
BEGIN { ------------ byte2Hex ------------ }
hex_digits := '0123456789ABCDEF';
the_hex := '??';
the_hex[1] := hex_digits[(ORD(the_byte) DIV 16) + 1];
{ msb }
the_hex[2] := hex_digits[(ORD(the_byte) MOD 16) + 1];
{ lsb }
END; { ------------ byte2Hex ------------ }
{
Routine ..... word2Hex
Purpose ..... Convert an unsigned WORD value to a hex string
Input ....... the_word - value to convert
Output ...... the_hex - hex string equivalent to value
}
PROCEDURE word2Hex (the_word : INTEGER; VAR the_hex : pt_hex_word);
TYPE
t_trix = PACKED ARRAY [0..1] OF CHAR; { [0] = msb, [1] = lsb }
VAR
trix : t_trix; { word to byte converter }
hex_byte : pt_hex_byte; { byte as a hex string }
BEGIN { ------------ word2Hex ------------ }
the_hex := '????';
trix := t_trix(the_word);
byte2Hex(trix[0],hex_byte); { msb }
the_hex[1] := hex_byte[1];
the_hex[2] := hex_byte[2];
byte2Hex(trix[1],hex_byte); { lsb }
the_hex[3] := hex_byte[1];
the_hex[4] := hex_byte[2];
END; { ------------ word2Hex ------------ }
{
Routine ..... long2Hex
Purpose ..... Convert an unsigned LONG value to a hex string
Input ....... the_long - value to convert
Output ...... the_hex - hex string equivalent to value
}
PROCEDURE long2Hex (the_long : LONGINT; VAR the_hex : pt_hex_long);
TYPE
t_trix = PACKED ARRAY [0..1] OF INTEGER; { [0] = msw, [1] = lsw
}
VAR
trix : t_trix; { word to byte converter }
hex_word : pt_hex_word; { word as a hex string }
BEGIN { ------------ long2Hex ------------ }
the_hex := '????????';
trix := t_trix(the_long);
word2Hex(trix[0],hex_word); { msw }
the_hex[1] := hex_word[1];
the_hex[2] := hex_word[2];
the_hex[3] := hex_word[3];
the_hex[4] := hex_word[4];
word2Hex(trix[1],hex_word); { lsw }
the_hex[5] := hex_word[1];
the_hex[6] := hex_word[2];
the_hex[7] := hex_word[3];
the_hex[8] := hex_word[4];
END; { ------------ long2Hex ------------ }
{
Routine ..... get_Hex_Data
Purpose ..... Convert the bytes in a buffer to a hex string
Input ....... the_data - pointer to data to convert
the_data_length - length of data in bytes
Output ...... the_hex_data - hex string equivalent to the data
bytes
Notes ....... If the data contains too many bytes, then only those
bytes that fit in the output string are converted.
}
PROCEDURE get_Hex_Data ( the_data : Ptr;
the_data_length : INTEGER;
VAR the_hex_data : Str255);
VAR
data_offset : LONGINT; { data byte offset }
data_ptr : Ptr; { pointer to a single data byte }
data_char : CHAR; { data character }
data_hex : pt_hex_byte;
{ data character as a hex string }
BEGIN { ------------ get_Hex_Data ------------ }
the_hex_data := ''; { start from scr |