package RISCOS::Module::Command;

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self  = {};
    my ($module, $offset) = @_;

    $self->{'__NAME'} = RISCOS::Module::getmodtext ($module, $offset);
    return wantarray ? () : undef
      unless defined ($self->{'__NAME'}) && length $self->{'__NAME'};

    # This is now fileswitch proof:
    $offset = ($offset + length ($self->{'__NAME'}) + 4) & ~3;
    my ($code, $min, $gs, $max, $flags, $syntax, $help)
      = unpack 'ICaC2I2', substr $module, $offset;

    return undef unless defined $help;

    $self->{'__MIN'} = $min;
    $self->{'__MAX'} = $max;
    $self->{'__GS'} = $gs;

    $self->{'__CODE'} = $code
      if (RISCOS::Module::isvalid_not_zero (length ($module), $code) & 7) == 1;
    $syntax = RISCOS::Module::getmodtext ($module, $syntax);
    $self->{'__SYNTAX'} = $syntax if defined $syntax;
    
    my $_flags = [];
    push @$_flags, 'filing system command' if ($flags & 0x80);
    push @$_flags, 'status command' if ($flags & 0x40);
    if ($flags & 0x20) {
	push @$_flags, 'help is code';
	$self->{'__HELP'} = $help
	  if (RISCOS::Module::isvalid_not_zero (length ($module), $help) & 7)
	       == 1;
    } else {
	$help = RISCOS::Module::getmodtext ($module, $help);
	$self->{'__HELP'} = $help if defined $help;
    }
    
    $self->{'__FLAGS'} = $_flags if @$_flags;
      
    bless ($self, $class);
    return $self unless wantarray;
    ($self, $offset + 16, $self->{'__NAME'})
}

sub Name {
    my $self = shift;
    $self->{'__NAME'};
}

sub Min {
    my $self = shift;
    $self->{'__MIN'};
}

sub Max {
    my $self = shift;
    $self->{'__MAX'};
}

sub GS_Flags {
    my $self = shift;
    return $self->{'__GS'} unless wantarray;
    split //, unpack 'b*', $self->{'__GS'};
}

sub Code {
    my $self = shift;
    $self->{'__CODE'};
}

sub Syntax {
    my $self = shift;
    $self->{'__SYNTAX'};
}

sub Help {
    my $self = shift;
    $self->{'__HELP'};
}

sub Flags {
    my $self = shift;
    return wantarray ? () : undef
      unless defined $self->{'__FLAGS'};
    return @{$self->{'__FLAGS'}} if wantarray;
    join ', ', @{$self->{'__FLAGS'}};
}

sub Dump {
    my $self = shift;
    my @lines = ("Name:\t\t" . $self->Name()
		 . ($self->Flags() ? "\t(" . $self->Flags() . ')' : ''));
    push @lines, "Syntax:\t\t" . $self->Syntax if defined $self->Syntax;
    push @lines, "Help:\t\t" . $self->Help if defined $self->Help;
    push @lines, "Parameters:\t" . (($self->Min == $self->Max)
				     ? $self->Min
				     : $self->Min . ' - ' . $self->Max);
    push @lines, "GS Trans map:\t" . join ' ', $self->GS_Flags if $self->Max;
    return @lines if wantarray;
    join "\n", @lines, '';
}

package RISCOS::Module;

use RISCOS::SWI;
use RISCOS::ValidateAddr;
use RISCOS::File 0.02;
require Exporter;
#use SelfLoader;
use Carp;
use strict;
use vars qw (@ISA @EXPORT_OK $VERSION $os_mods $code_mask $work_mask
$unsqueeze_code @offsets);

@ISA = qw(Exporter);
@EXPORT_OK = qw(rm_private_word rm_workspace rm_code_addr rm_grab
rmensure rm_unsqueeze split_help_string modules modules_only);
$VERSION = 0.02;

$code_mask = &regmask([0,1],[3]);
$work_mask = &regmask([0,1],[4]);

# Hmm. Magic numbers
# Bits 0,1
# 0 is invalid
# 1 is in module
# 2 is exactly at end
# 3 is in module but top bit set
# Bit 4 => Not word aligned

@offsets = (	# 1 (word aligned in module) is always valid.
  ['start',	 0, \&start_valid],
  ['init',	 4],	# Not [1,2] as we are checking after unsqueeze
  ['final',	 8, [3,7]],
  ['service',	 12],
  ['title',	 16, [5]],
  ['help',	 20, [0,4,5]],
  ['command',	 24, [5]],	# fileswitch has a non-word aligned table !!
  ['SWIchunk',	 28, \&swichunk_valid],		# Not a pointer :-)
  ['SWIhandler', 32, [0,1,4]],
  ['SWItable',	 36, [0,1,4,5]],
  ['SWIdecode',  40, [0,1,4]],
  ['tokenfile',	 44, [0,1,4,5]]
);


$unsqueeze_code = # A little bit of raw ARM code never hurt anyone

'8-) ы г?Оге,Oв?ђи
 @а	ђJаЂ‰а‰oЏвVб` ±‹аq†а
P б@ г  б0аг°[в)єХд
QвЄQг
ХдХдЂбХдЂбTгХЂ0ѓа0†дняяк°Kа°‹в0ѓв0†дQвыяяКжяяк\Qг0ѓ°0†ґвяяє®QвєХдЃбХдЂб0ѓа0†дЩяяк\QвХдЃб0ѓа0†дУяякTг° бА б  б@ гКяяк° бPЏвMoЏв@ бµи§иUбыяяєр бђ‰аЂHв	Zб5Ъ`zе0в	SвєzеЃбAњзкSвєzеЃб›зzеDЃбк@°б
zеzеЂбzеЂбzеLЂб&2 б	SвєzеЃбQњз0(йЬяякSвєzеЃб›зzеTЃб0(йУяякP°б0(	Ряя
zеzеЂбzеЂбzе\Ђб0(йЗяяк]гЪ`Iађ б ‰а¶и§иР]выяяКЅяякр бrcc 4.00
      ';

$os_mods = SWINumberFromString('XOS_Module');
#__DATA__

sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    return undef unless my $code = rm_unsqueeze (shift);

    my $self  = {};
    my $length = length $code;
    my $skip = 0;

    foreach my $entry (@offsets)
    {
	my $offset = $$entry[1];

	if ($skip) {
	    $offset = 0;
	} else {
	    # Translate the offset in the header into an offset in the module
	    $offset = getmodoffset ($code, $offset);

	    my @acceptable = ();
	    my $status;

	    if (ref $$entry[2] eq 'CODE')
	    {
		# Only the SWI chunk ends up in here
		@acceptable = (0,1);
		$status = &{$$entry[2]} ($offset);
	    }
	    else
	    {
		if (ref $$entry[2] eq 'ARRAY')
		{
		    @acceptable = @{$$entry[2]};
		}
		$status = isvalid ($length, $offset);
	    }

	    if ($status != 1)
	    {
		my $fail = 1;
		foreach (@acceptable) {
		    $fail = 0 if ($_ == $status)
		}
		if ($fail) {
		    carp sprintf "Invalid $$entry[0] module entry point of &%X",
		      $offset if $^W;
		    return undef;
		}
	    }
	    if (($status & 3) == 0)
	    {
		$offset = undef;
		$skip = 1;	# Skip rest of module headers
	    }
	}

	$self->{"___$$entry[0]"} = $offset;
    }
    $self->{'__TITLE'} = getmodtext ($code, $self->{'___title'});

    ($self->{'__NAME'}, $self->{'__VERSION'}, $self->{'__DATE'},
      $self->{'__COMMENT'})
      = split_help_string (getmodtext ($code, $self->{'___help'}));
    delete $self->{'__COMMENT'} unless length $self->{'__COMMENT'};
    
    if (my $swi_off = $self->{'___SWItable'}) {
	my $length = length ($self->{'__SWI_PREFIX'} =
				       getmodtext ($code, $swi_off));
	my @swis;
	my $swi;
	while ($length =
		 length ($swi = getmodtext ($code, $swi_off += $length + 1)))
	{
	    push @swis, $swi;
	}
	$self->{'__SWIS'} = [@swis];
    }

    if (my $command_off = $self->{'___command'}) {
	my ($commands, $command_hash, $command, $name) = ([], {});
	while (($command, $command_off, $name)
	# Don't know why RISCOS::Module::Command->new( $code, $command_off)) {
	# is SelfLoader-unfriendly
	#  = RISCOS::Module::Command::new('RISCOS::Module::Command', $code, $command_off)) {
	   = RISCOS::Module::Command->new($code, $command_off)) {
	    push @$commands, $command;
	    $command_hash->{$name} = $command;
	}
	$self->{'__COMMANDS'} = $commands;
	$self->{'__COMMAND_INDEX'} = $command_hash;
    }

   my $token = getmodtext ($code, $self->{'___tokenfile'});
    $self->{'__TOKEN'} = $token if $token;

    $self->{'__LENGTH'} = $length;
    
    bless ($self, $class);

    return ($self, $code) if wantarray;
    $self;
}

sub grab_from_os_heap ($) {
    # This relies on the private word pointing at an OS_Heap block, and
    # Acorn not re-writing OS_Heap. But apperntly a dodgy API means that it is
    # virtually impossible for them _not_ to have the block length at addr-4
    return undef unless defined (my $addr = shift);
    return '' unless $addr;	# Private word == 0 means no workspace

    return '' unless validate_addr ($addr-4, $addr);
    # Hey, this beauty worked first time!
    my $pointer = pack 'I', $addr - 4;
    my $len = (unpack 'I', unpack 'P4', $pointer) - 4;
    # Validate that we can read the word before, and then read it!

    return '' unless validate_addr ($addr, $addr + $len);

    $pointer = pack 'I', $addr;

    unpack "P$len", $pointer;	# Who said perl was safe?
}

sub rm_private_word ($) {
    return undef unless my $name = shift;
    my $addr = 'x'x4;

    return undef unless swix ($os_mods, $work_mask, 18, $name, $addr);

    return unpack 'I', $addr;
}

sub rm_code_addr ($) {
    return undef unless my $name = shift;
    my $addr = 'x'x4;

    return undef unless swix ($os_mods, $code_mask, 18, $name, $addr);

    return unpack 'I', $addr;
}

sub rm_workspace ($) { grab_from_os_heap (&rm_private_word) }
sub rm_grab ($) { grab_from_os_heap (&rm_code_addr) }

sub start_valid {
    1;	# It's always valid (treated as an ARM instruction if not an offset)
}

sub swichunk_valid {
    !($_[0] & 0xFF00003F)	# Is it a multiple of 64 ?
}

sub isvalid ($;@) {
    return undef unless defined (my $length = shift);
    my @result;

    foreach (@_)
    {
	my $result;
	if (defined)
	{
	    $result = 0;
	    my $top = ($_ & 0x80000000);
	    $_ &= 0x7FFFFFFF if $top;
	    if ($_ >= 0 && $_ < $length)
	    {
		$result = $top ? 3 : 1;	# Inside module.
	    }
	    elsif ($top && $_ == $length)
	    {
		$result = 2;		# At end of module.
	    }
	    $result |= 4 if $_ & 3;	# Not a multiple of 4
	}
	push @result, $result;
    }
    return $result[0] unless wantarray;
    @result;
}

sub isvalid_not_zero ($;@) {
    return undef unless defined (my $length = shift);
    my @result = @_;

    foreach (@result) {
	$_-- unless $_;		# 0 -> -1;
    }
    
    isvalid ($length, @result);
}
    

sub grab {
    my $proto = shift;
    new ($proto, &rm_grab);	# Pass on parameter
}

sub load {
    my ($proto, $file) = @_;

    my $code = RISCOS::File::load ($file);
    unless (defined $code) {
      carp "Could not load file '$file'" if $^W;
      return undef;
    }
    new ($proto, $code);
}

sub getmodoffset ($$) {
    my ($module, $offset) = @_;
    return undef unless defined $offset;
    unpack 'I', substr $module, $offset, 4;
}

sub getmodtext ($$) {
    my ($module, $offset) = @_;
    # Must be inside module
    return undef unless defined $module and defined $offset
			and (isvalid_not_zero (length ($module), $offset) & 3) 
			      == 1;
    (substr $module, $offset) =~ /^([^\0]*)/s;
    $1;
}

sub Title {
    my $self = shift;
    $self->{'__TITLE'};
}

sub Name {
    my $self = shift;
    $self->{'__NAME'};
}
sub Version {
    my $self = shift;
    $self->{'__VERSION'};
}
sub Date {
    my $self = shift;
    $self->{'__DATE'};
}
sub Comment {
    my $self = shift;
    $self->{'__COMMENT'};
}

sub Length {
    my $self = shift;
    $self->{'__LENGTH'};
}

sub Start {
    my $self = shift;
    my $offset = $self->{'___start'};
    return undef unless $offset;
    return $offset if (isvalid ($self->{'__LENGTH'}, $offset) & 7) == 1;
    require ARM;
    return ARM::disassemble ($offset) if defined \&ARM::disassemble;
    sprintf "&%08X", $offset;	# Failed to get disassembler.
}

sub Init {
    my $self = shift;
    $self->{'___init'} ? $self->{'___init'} : undef;
}
sub Final {
    my $self = shift;
    $self->{'___final'} ? $self->{'___final'} : undef;
}
sub Service {
    my $self = shift;
    $self->{'___service'} ? $self->{'___service'} : undef;
}
sub SWIchunk {
    my $self = shift;      
    $self->{'___SWIchunk'} ? $self->{'___SWIchunk'} : undef;
}
sub SWIhandler {
    my $self = shift;
    $self->{'___SWIhandler'} ? $self->{'___SWIhandler'} : undef;
}
sub SWIdecode {
    my $self = shift;
    $self->{'___SWIdecode'} ? $self->{'___SWIdecode'} : undef;
}

sub TokenFile {
    my $self = shift;
    $self->{'__TOKEN'};
}

sub CommandTable {
    my $self = shift;
    $self->{'__COMMANDS'};
}
sub CommandHash {
    my $self = shift;
    $self->{'__COMMAND_INDEX'};
}
sub Command {
    my $self = shift;
    defined ($self->{'__COMMAND_INDEX'}) ? $self->{'__COMMAND_INDEX'}->{$_[0]}
					 : undef;
}

sub SWIPrefix {
    my $self = shift;
    $self->{'__SWI_PREFIX'};
}
sub SWITable {
    my $self = shift;
    $self->{'__SWIS'};
}

sub Dump {
    my $self = shift;
    my @lines;
    
    foreach my $thing (qw(Title Version Date Comment Start Init Final Service
    TokenFile SWIhandler SWIdecode)) {
	my $value;
	if (eval "defined (\$value = \$self->$thing)") {
	    my $line = "$thing:\t";
	    $line .= "\t" if length $line < 8;
	    push @lines, "$line$value";
	}
    }
    push @lines, '';
    my $table;
    if ($table = $self->SWITable and @$table) {
	my $prefix = $self->SWIPrefix;
	my $swi = $self->SWIchunk;
	push @lines, "SWIs:";
	foreach (@$table) {
	    push @lines, sprintf "&%06X: ${prefix}_$_", $swi++;
	}
	push @lines, '';
    }
    if ($table = $self->CommandTable and @$table) {
	foreach (@$table) {
	    push @lines, $_->Dump, '';
	}
    }
    return join "\n", @lines unless wantarray;
    pop @lines;	# Loose final ''
    @lines;
}

sub split_help_string ($) {
    $_[0] =~ /([\S ]*)\t+([0-9.a-zЯ]+)\s*\((.*?)\)\s*(.*)/;
    ($1, $2, $3, $4);
}

sub rmensure ($;$$) {
    my ($name, $path, $version) = @_;
    $version += 0;	# Default version is 0
    $path = "System:Modules.$name" unless defined $path;

    my $word = 'xxxx';
    # Hack to get the address of the xxxx
    my $addr = unpack 'I', pack 'p', $word;
    my $command = "RMEnsure $name $version RMLoad $path";
    kernelswi ('OS_CLI', $command);

    # Darren Salt's truely evil rmensure idea...
    $command = "RMEnsure $name $version MemoryA 10_$addr 0 { > Null: }";
    # Execute the MemoryA command if RMEnsure fails, to alter the flag

    return undef unless defined kernelswi ('OS_CLI', $command);
    ('xxxx' eq $word) ? 1 : 0;
}

sub rm_unsqueeze ($) {
    my $module = shift;
    return undef unless defined $module;
    my $result;
    my $init = getmodoffset ($module, 4);
    my $length = length $module;
    my $status = isvalid ($length, $init);

    return $module if ($status == 1);	# It's not squeezed
    return undef unless $status == 2;	# Check - is it exactly at end?

    my ($present, $place);

    foreach $place (undef, 'Patch:Modules.Unsqueeze',
		    'ADFS::4.$.Utilities.Patches.!Patch.Modules.UnSqueeze')
    {
	$present = rmensure ('Unsqueeze', $place, 1.23);
	    croak "Unexpected error when checking for UnSqueeze module: $^E"
	unless (defined $present);
	last if $present;
    }

    unless ($present)
    {
	my $error = 'Could not load the UnSqueeze module, which is needed to '
		    . 'unsqueeze compressed modules. A copy of this module '
		    . 'is found in the !Patch application supplied by Acorn.';
	my $patch = $ENV{'Patch$Path'};
	if (defined $patch)
	{
	    $patch =~ s/\.$//;
	    $error .= " That's strange. Patch seems to be $patch but the module"
		      . ' UnSqueeze could not be loaded from its Modules '
		      . "subdirectory. Most odd.\n";
	}
	die $error
    }

    # Righto. Got the Unsqueeze module.
    my $convert = RISCOS::Filespec::convert();
    RISCOS::Filespec::convert_on();
    my $tmpfile = 'mod000';
    my $text = ' temporary file to unsqueeze module: ';
    ++$tmpfile while -f "/tmp/$tmpfile";
    my $tmpout = $tmpfile;
    ++$tmpout;	# Move beyond input file
    ++$tmpout while -f "/tmp/$tmpout";
    $tmpfile = "/tmp/$tmpfile";
    $tmpout = "/tmp/$tmpout";
    open MOD, ">$tmpfile"
      or croak "Couldn't open$text$!";

    local $\;	# Don't want output record separator.
    undef $\;	# N has a nasty habit of setting this to "\n" with -l flag
    my $offset = getmodoffset ($module, 20);
    $offset += (length (getmodtext ($module, $offset)) + 8) & -4;
    $length -= $offset;
    substr ($unsqueeze_code, 0, 4)
      = (getmodoffset ($module, 16) != 52)
	? pack 'I', 0x180 # increase this if it fails to unsqueeze large modules
	: substr ($module, 48, 4);

    print MOD pack ('I', (0xEA000000 | (($length + 15) >> 2))),
	      substr ($module, $offset), $unsqueeze_code
      or warn "Couldn't write$text$!";

    close MOD
      or croak "Couldn't close$text$!";

    RISCOS::File::settype (0xFF8, $tmpfile)
      or croak "Couldn't settype$text$!";

    if (system 'UnSqueeze ' . RISCOS::Filespec::riscosify ($tmpfile) . ' '
	       . RISCOS::Filespec::riscosify ($tmpout))
    {
      warn "Failed to run unsqueeze command\n";
    }
    else
    {
	if (open MOD, "<$tmpout")
	{
	    local $/; undef $/; $result = <MOD>;
	    close MOD
	}
	else
	{
	    warn "Couldn't open unsqueeze output file: $!";
	}
    }

    unlink $tmpfile, $tmpout;
    RISCOS::Filespec::convert($convert);
    $result;
}

sub modules {
    my ($previous, $mod, $inst) = (-1, 0, 0);
    my ($postfix, $with, $without) = '';
    my @result;
    
    while (defined $postfix) {
	my $base;
	
	my $result = kernelswi ($os_mods, 12, $mod, $inst);
	if (defined $result) {
	    ($mod, $inst, $base, $postfix) = unpack 'x4I3x4p', $result;
	
	    my $module = grab_from_os_heap ($base);
	
	    push @result, ($mod == $previous) ? $with : $without
	      if defined $with;
	    $without = getmodtext ($module, getmodoffset ($module, 16));
	    $with = "$without%$postfix";
	    $without = $with if $inst;
	    $previous = $mod;
	} else {
	    $mod++; $inst = 0; undef $postfix;
	    push @result, $without;
	}
   }
   
   @result;
}

sub modules_only {
    my $mod = 0;
    my (@result, $base, $result);
    
    while (defined ($result = kernelswi ($os_mods, 12, $mod++, 0))) {
	$base = unpack 'x12I', $result;
	
	my $module = grab_from_os_heap ($base);
	
	push @result, getmodtext ($module, getmodoffset ($module, 16));
    }
    
    @result;
}

$os_mods;
__END__

=head1 NAME

RISCOS::Module -- manipulate relocatable modules

=head1 SYNOPSIS

    use RISCOS::Module;

    $mod = grab RISCOS::Module $mod;
    print scalar $mod->Dump if $mod;

    $cooked = rm_unsqueeze $raw;

    $code	= rm_grab $mod;
    $workspace	= rm_workspace $mod;

=head1 DESCRIPTION

C<RISCOS::Module> provides a class to hold details about a relocatable module,
and a variety of subroutines to manipulate entire relocatable modules and their
workspace from disc and the C<RMA>.

=head2 Subroutines

=over 4

=item rm_private_word <module_name>

Returns the contents of a module's private word. Typically this points to the
modules workspace.

=item rm_workspace <module_name>

Returns the contents of the module workspace (by assuming that the module's
private word points to an area of workspace in the RMA, where the length of
workspace is stored in the preceding word)

=item rm_code_addr <module_name>

Returns the address of the module's code.

=item rm_grab <module_name>

Returns the module's code.

=item rmensure <module> [, <path> [,<version>]]

Emulates the C<RMEnsure> command. If a copy of I<module> of version I<version>
or higher is not present will attempt to C<RMLoad> a module from I<path>.
I<version> defaults to 0, I<path> defaults to 'C<System:Modules.I<module>>'.

Returns undefined if an error occurs, 0 if the module is still absent after the
C<RMLoad> command, 1 if the module is present (before or after the command is
run).

The method used is a perl conversion of an idea by Darren Salt 
<F<arcsalt@spuddy.mew.co.uk>>.

=item rm_unsqueeze <module>

Returns the "unsqeezed" version of the B<module> passed in. The entire module
code should be in the scalar, not the name of a module. "unsqeezing" requires
the 'C<UNSqeeze>' module and use of a disc file. (The 'C<UNSqueeze>' module
found in C<!Patch> - C<rm_unsqueeze> will look there and in C<!System.Modules>
for this module).

=item split_help_string <string>

Splits a module help string into I<Name>, I<Version>, I<Date> and I<Comment>.
Returns an array with these four elements.

=item modules

Unlike C<modules_only> this will include multiply instantiated modules multiple
times - I<i.e.>: 

    FileCore%ADFS
    FileCore%Base

=item modules_only

Returns a list of the names of all modules in the C<RMA>. Multiply instantiated
modules are only returned once in the list.

=back

=head2 Methods

The C<RISCOS::Module> class provides the following methods:

=over 4

=item new <module>

Creates a new C<RISCOS::Module> object from the module B<code> passed in. C<new>
automatically calls C<rm_unsqueeze>. Returns the object.

=item grab <module_name>

Grabs the named module from the C<RMA> and calls C<new> with it.

=item load <filename>

Loads the file using C<RISCOS::File::load> and calls C<new>. Hence I<filename>
can be a filename, a reference to a filehandle, or a reference to a scalar which
is used as the file's contents.


=item Title

Returns the module's title (as found from the C<title> offset).

=item Name

Returns the module's name (obtained by splitting the help string).

=item Version

Returns the module's version.

=item Date

Returns the module's date.

=item Comment

Returns the module's comment (any text in the help string after the date).

=item Length

Returns the module's length.

=item Start

Returns the module's start offset. If the start offset is non-zero but invalid
(not a multiple of four within the module) it is disassembled and the
instruction returned. Returns undefined if the start offset is zero.

=item Init

Returns the module's initialisation code offset, or undefined if there is no
initialisation code.

=item Final

Returns the module's finalisation code offset, or undefined if there is no
finalisation code.

=item Service

Returns the module's service call handler offset, or undefined if there is no
service call code.

=item TokenFile

Returns the name of the textfile containing tokens used in the module's command
text.

=item CommandTable

Returns a reference to an array of C<RISCOS::Module::Command> objects (see
below) that describe the C<*> commands provided by the module. If the module
provides no commands, C<CommandTable> returns undefined.

=item CommandHash

Returns a reference to an array of C<RISCOS::Module::Command> objects, keyed by
command name.  If the module provides no commands, C<CommandHash> returns
undefined.

=item Command <name>

Looks up I<name> in the hash of C<*> commands provided by the module. Returns a
C<RISCOS::Module::Command> object if found, else undefined.

=item SWIchunk

Returns the module's C<SWI> chunk number, or undefined if the module does not
provide C<SWI>s (using the module header entries).

=item SWIhandler

Returns the module's C<SWI> handler code offset, or undefined if there is no
C<SWI> handler code.

=item SWIdecode

Returns the module's C<SWI> decoding code offset, or undefined if there is no
C<SWI> decoding code.

=item SWIPrefix

Returns the module's C<SWI> prefix, or undefined if the module does not provide
C<SWI>s. 

=item SWITable

Returns a reference to an array of C<SWI> names provided by the module. These
are not prefixed by the C<SWI> prefix - for example the WindowManager will
return

    ['Initialise', 'CreateWindow', 'CreateIcon',

I<etc.>

=item Dump

Returns a text dump of the module. In array context returns a list of lines. In
scalar context joins these with "\n";

=back

=head2 RISCOS::Module::Command

The C<RISCOS::Module::Command> class provides the following methods:

=over 4

=item new <module_data>, <offset>

Creates a new Command object from the command table entry stored at I<offset> in
the module data supplied. Returns this object in scalar context - in list
context returns C<(object, new-offset, name).

=item Name

Returns the command's name.

=item Min

Returns the minimum number of parameters to the command.

=item Max


Returns the maximum number of parameters to the command.


=item GS_Flags

Returns the command's C<GSTrans> flags. In scalar context returns a byte
corresponding to the byte in the command table. For any bit set that number
parameter will be passed to C<GSTrans> before the command is called. In array
context expands this byte and returns a list with 8 elements, each either 0 or
1. Element 0 refers to parameter 0.

=item Code

Returns the offset to the command's code.

=item Syntax

Returns the command's syntax text.

=item Help

Returns the command's help text, or the offset to the command's help code.

=item Flags

Returns the command's flags. In array context returns a list of text strings.
In scalar context joins these with ', '

=item Dump

Returns a text dump of the command. In array context returns a list of lines. In
scalar context joins these with "\n";

=back

=head1 BUGS

None known. However, running C<new> on all modules in my C<RMA> found a couple
that I fixed (I<e.g.> C<Fileswitch> has a non-word aligned command table, which
is legal, but caught me out), so there may still be some.

=head1 AUTHOR

Nicholas Clark <F<nick@unfortu.net>>
