home *** CD-ROM | disk | FTP | other *** search
/ Acorn User 10 / AU_CD10.iso / Updates / Perl / Non-RPC / !Perl / riscos / RISCOS / Module.pm < prev    next >
Encoding:
Text File  |  1998-07-26  |  23.8 KB  |  958 lines

  1. package RISCOS::Module::Command;
  2.  
  3. sub new {
  4.     my $proto = shift;
  5.     my $class = ref($proto) || $proto;
  6.     my $self  = {};
  7.     my ($module, $offset) = @_;
  8.  
  9.     $self->{'__NAME'} = RISCOS::Module::getmodtext ($module, $offset);
  10.     return wantarray ? () : undef
  11.       unless defined ($self->{'__NAME'}) && length $self->{'__NAME'};
  12.  
  13.     # This is now fileswitch proof:
  14.     $offset = ($offset + length ($self->{'__NAME'}) + 4) & ~3;
  15.     my ($code, $min, $gs, $max, $flags, $syntax, $help)
  16.       = unpack 'ICaC2I2', substr $module, $offset;
  17.  
  18.     return undef unless defined $help;
  19.  
  20.     $self->{'__MIN'} = $min;
  21.     $self->{'__MAX'} = $max;
  22.     $self->{'__GS'} = $gs;
  23.  
  24.     $self->{'__CODE'} = $code
  25.       if (RISCOS::Module::isvalid_not_zero (length ($module), $code) & 7) == 1;
  26.     $syntax = RISCOS::Module::getmodtext ($module, $syntax);
  27.     $self->{'__SYNTAX'} = $syntax if defined $syntax;
  28.     
  29.     my $_flags = [];
  30.     push @$_flags, 'filing system command' if ($flags & 0x80);
  31.     push @$_flags, 'status command' if ($flags & 0x40);
  32.     if ($flags & 0x20) {
  33.     push @$_flags, 'help is code';
  34.     $self->{'__HELP'} = $help
  35.       if (RISCOS::Module::isvalid_not_zero (length ($module), $help) & 7)
  36.            == 1;
  37.     } else {
  38.     $help = RISCOS::Module::getmodtext ($module, $help);
  39.     $self->{'__HELP'} = $help if defined $help;
  40.     }
  41.     
  42.     $self->{'__FLAGS'} = $_flags if @$_flags;
  43.       
  44.     bless ($self, $class);
  45.     return $self unless wantarray;
  46.     ($self, $offset + 16, $self->{'__NAME'})
  47. }
  48.  
  49. sub Name {
  50.     my $self = shift;
  51.     $self->{'__NAME'};
  52. }
  53.  
  54. sub Min {
  55.     my $self = shift;
  56.     $self->{'__MIN'};
  57. }
  58.  
  59. sub Max {
  60.     my $self = shift;
  61.     $self->{'__MAX'};
  62. }
  63.  
  64. sub GS_Flags {
  65.     my $self = shift;
  66.     return $self->{'__GS'} unless wantarray;
  67.     split //, unpack 'b*', $self->{'__GS'};
  68. }
  69.  
  70. sub Code {
  71.     my $self = shift;
  72.     $self->{'__CODE'};
  73. }
  74.  
  75. sub Syntax {
  76.     my $self = shift;
  77.     $self->{'__SYNTAX'};
  78. }
  79.  
  80. sub Help {
  81.     my $self = shift;
  82.     $self->{'__HELP'};
  83. }
  84.  
  85. sub Flags {
  86.     my $self = shift;
  87.     return wantarray ? () : undef
  88.       unless defined $self->{'__FLAGS'};
  89.     return @{$self->{'__FLAGS'}} if wantarray;
  90.     join ', ', @{$self->{'__FLAGS'}};
  91. }
  92.  
  93. sub Dump {
  94.     my $self = shift;
  95.     my @lines = ("Name:\t\t" . $self->Name()
  96.          . ($self->Flags() ? "\t(" . $self->Flags() . ')' : ''));
  97.     push @lines, "Syntax:\t\t" . $self->Syntax if defined $self->Syntax;
  98.     push @lines, "Help:\t\t" . $self->Help if defined $self->Help;
  99.     push @lines, "Parameters:\t" . (($self->Min == $self->Max)
  100.                      ? $self->Min
  101.                      : $self->Min . ' - ' . $self->Max);
  102.     push @lines, "GS Trans map:\t" . join ' ', $self->GS_Flags if $self->Max;
  103.     return @lines if wantarray;
  104.     join "\n", @lines, '';
  105. }
  106.  
  107. package RISCOS::Module;
  108.  
  109. use RISCOS::SWI;
  110. use RISCOS::ValidateAddr;
  111. use RISCOS::File 0.02;
  112. require Exporter;
  113. #use SelfLoader;
  114. use Carp;
  115. use strict;
  116. use vars qw (@ISA @EXPORT_OK $VERSION $os_mods $code_mask $work_mask
  117. $unsqueeze_code @offsets);
  118.  
  119. @ISA = qw(Exporter);
  120. @EXPORT_OK = qw(rm_private_word rm_workspace rm_code_addr rm_grab
  121. rmensure rm_unsqueeze split_help_string modules modules_only);
  122. $VERSION = 0.02;
  123.  
  124. $code_mask = ®mask([0,1],[3]);
  125. $work_mask = ®mask([0,1],[4]);
  126.  
  127. # Hmm. Magic numbers
  128. # Bits 0,1
  129. # 0 is invalid
  130. # 1 is in module
  131. # 2 is exactly at end
  132. # 3 is in module but top bit set
  133. # Bit 4 => Not word aligned
  134.  
  135. @offsets = (    # 1 (word aligned in module) is always valid.
  136.   ['start',     0, \&start_valid],
  137.   ['init',     4],    # Not [1,2] as we are checking after unsqueeze
  138.   ['final',     8, [3,7]],
  139.   ['service',     12],
  140.   ['title',     16, [5]],
  141.   ['help',     20, [0,4,5]],
  142.   ['command',     24, [5]],    # fileswitch has a non-word aligned table !!
  143.   ['SWIchunk',     28, \&swichunk_valid],        # Not a pointer :-)
  144.   ['SWIhandler', 32, [0,1,4]],
  145.   ['SWItable',     36, [0,1,4,5]],
  146.   ['SWIdecode',  40, [0,1,4]],
  147.   ['tokenfile',     44, [0,1,4,5]]
  148. );
  149.  
  150.  
  151. $unsqueeze_code = # A little bit of raw ARM code never hurt anyone
  152.  
  153. '8-) û ã?Îãå,Oâ?‘è
  154.  @à    ‘Jà€⇨à⇨o•âVá` ± ⇧àqŷà
  155. P á@ ã  á0àã°[â)ºÕä
  156. QâªQã
  157. ÕäÕä€áÕä€áTãÕ €0◰à0ŷäíÿÿê°Kà°⇧â0◰â0ŷäQâûÿÿÊæÿÿê\Qã0◰°0ŷ´âÿÿº®QâºÕäŴáÕä€á0◰à0ŷäÙÿÿê\QâÕäŴá0◰à0ŷäÓÿÿêTã ° áÀ á  á@ ãÊÿÿê° áP•âMo•â@ áµè§èUáûÿÿºð á
  158. ‘⇨à€Hâ    Zá5Ú`zå0â    SâºzåŴáA†çêSâºzåŴáœçzåDŴáê@°á
  159. zåzå€ázå€ázåL€á&2 á    SâºzåŴáQ†ç0(éÜÿÿêSâºzåŴáœçzåTŴá0(éÓÿÿêP°á0(    Ðÿÿ
  160. zåzå€ázå€ázå\€á0(éÇÿÿê]ãÚ
  161. `Ià‘ á
  162.  ⇨à¶è§èÐ]âûÿÿʽÿÿêð árcc 4.00
  163.       ';
  164.  
  165. $os_mods = SWINumberFromString('XOS_Module');
  166. #__DATA__
  167.  
  168. sub new {
  169.     my $proto = shift;
  170.     my $class = ref($proto) || $proto;
  171.     return undef unless my $code = rm_unsqueeze (shift);
  172.  
  173.     my $self  = {};
  174.     my $length = length $code;
  175.     my $skip = 0;
  176.  
  177.     foreach my $entry (@offsets)
  178.     {
  179.     my $offset = $$entry[1];
  180.  
  181.     if ($skip) {
  182.         $offset = 0;
  183.     } else {
  184.         # Translate the offset in the header into an offset in the module
  185.         $offset = getmodoffset ($code, $offset);
  186.  
  187.         my @acceptable = ();
  188.         my $status;
  189.  
  190.         if (ref $$entry[2] eq 'CODE')
  191.         {
  192.         # Only the SWI chunk ends up in here
  193.         @acceptable = (0,1);
  194.         $status = &{$$entry[2]} ($offset);
  195.         }
  196.         else
  197.         {
  198.         if (ref $$entry[2] eq 'ARRAY')
  199.         {
  200.             @acceptable = @{$$entry[2]};
  201.         }
  202.         $status = isvalid ($length, $offset);
  203.         }
  204.  
  205.         if ($status != 1)
  206.         {
  207.         my $fail = 1;
  208.         foreach (@acceptable) {
  209.             $fail = 0 if ($_ == $status)
  210.         }
  211.         if ($fail) {
  212.             carp sprintf "Invalid $$entry[0] module entry point of &%X",
  213.               $offset if $^W;
  214.             return undef;
  215.         }
  216.         }
  217.         if (($status & 3) == 0)
  218.         {
  219.         $offset = undef;
  220.         $skip = 1;    # Skip rest of module headers
  221.         }
  222.     }
  223.  
  224.     $self->{"___$$entry[0]"} = $offset;
  225.     }
  226.     $self->{'__TITLE'} = getmodtext ($code, $self->{'___title'});
  227.  
  228.     ($self->{'__NAME'}, $self->{'__VERSION'}, $self->{'__DATE'},
  229.       $self->{'__COMMENT'})
  230.       = split_help_string (getmodtext ($code, $self->{'___help'}));
  231.     delete $self->{'__COMMENT'} unless length $self->{'__COMMENT'};
  232.     
  233.     if (my $swi_off = $self->{'___SWItable'}) {
  234.     my $length = length ($self->{'__SWI_PREFIX'} =
  235.                        getmodtext ($code, $swi_off));
  236.     my @swis;
  237.     my $swi;
  238.     while ($length =
  239.          length ($swi = getmodtext ($code, $swi_off += $length + 1)))
  240.     {
  241.         push @swis, $swi;
  242.     }
  243.     $self->{'__SWIS'} = [@swis];
  244.     }
  245.  
  246.     if (my $command_off = $self->{'___command'}) {
  247.     my ($commands, $command_hash, $command, $name) = ([], {});
  248.     while (($command, $command_off, $name)
  249.     # Don't know why RISCOS::Module::Command->new( $code, $command_off)) {
  250.     # is SelfLoader-unfriendly
  251.     #  = RISCOS::Module::Command::new('RISCOS::Module::Command', $code, $command_off)) {
  252.        = RISCOS::Module::Command->new($code, $command_off)) {
  253.         push @$commands, $command;
  254.         $command_hash->{$name} = $command;
  255.     }
  256.     $self->{'__COMMANDS'} = $commands;
  257.     $self->{'__COMMAND_INDEX'} = $command_hash;
  258.     }
  259.  
  260.    my $token = getmodtext ($code, $self->{'___tokenfile'});
  261.     $self->{'__TOKEN'} = $token if $token;
  262.  
  263.     $self->{'__LENGTH'} = $length;
  264.     
  265.     bless ($self, $class);
  266.  
  267.     return ($self, $code) if wantarray;
  268.     $self;
  269. }
  270.  
  271. sub grab_from_os_heap ($) {
  272.     # This relies on the private word pointing at an OS_Heap block, and
  273.     # Acorn not re-writing OS_Heap. But apperntly a dodgy API means that it is
  274.     # virtually impossible for them _not_ to have the block length at addr-4
  275.     return undef unless defined (my $addr = shift);
  276.     return '' unless $addr;    # Private word == 0 means no workspace
  277.  
  278.     return '' unless validate_addr ($addr-4, $addr);
  279.     # Hey, this beauty worked first time!
  280.     my $pointer = pack 'I', $addr - 4;
  281.     my $len = (unpack 'I', unpack 'P4', $pointer) - 4;
  282.     # Validate that we can read the word before, and then read it!
  283.  
  284.     return '' unless validate_addr ($addr, $addr + $len);
  285.  
  286.     $pointer = pack 'I', $addr;
  287.  
  288.     unpack "P$len", $pointer;    # Who said perl was safe?
  289. }
  290.  
  291. sub rm_private_word ($) {
  292.     return undef unless my $name = shift;
  293.     my $addr = 'x'x4;
  294.  
  295.     return undef unless swix ($os_mods, $work_mask, 18, $name, $addr);
  296.  
  297.     return unpack 'I', $addr;
  298. }
  299.  
  300. sub rm_code_addr ($) {
  301.     return undef unless my $name = shift;
  302.     my $addr = 'x'x4;
  303.  
  304.     return undef unless swix ($os_mods, $code_mask, 18, $name, $addr);
  305.  
  306.     return unpack 'I', $addr;
  307. }
  308.  
  309. sub rm_workspace ($) { grab_from_os_heap (&rm_private_word) }
  310. sub rm_grab ($) { grab_from_os_heap (&rm_code_addr) }
  311.  
  312. sub start_valid {
  313.     1;    # It's always valid (treated as an ARM instruction if not an offset)
  314. }
  315.  
  316. sub swichunk_valid {
  317.     !($_[0] & 0xFF00003F)    # Is it a multiple of 64 ?
  318. }
  319.  
  320. sub isvalid ($;@) {
  321.     return undef unless defined (my $length = shift);
  322.     my @result;
  323.  
  324.     foreach (@_)
  325.     {
  326.     my $result;
  327.     if (defined)
  328.     {
  329.         $result = 0;
  330.         my $top = ($_ & 0x80000000);
  331.         $_ &= 0x7FFFFFFF if $top;
  332.         if ($_ >= 0 && $_ < $length)
  333.         {
  334.         $result = $top ? 3 : 1;    # Inside module.
  335.         }
  336.         elsif ($top && $_ == $length)
  337.         {
  338.         $result = 2;        # At end of module.
  339.         }
  340.         $result |= 4 if $_ & 3;    # Not a multiple of 4
  341.     }
  342.     push @result, $result;
  343.     }
  344.     return $result[0] unless wantarray;
  345.     @result;
  346. }
  347.  
  348. sub isvalid_not_zero ($;@) {
  349.     return undef unless defined (my $length = shift);
  350.     my @result = @_;
  351.  
  352.     foreach (@result) {
  353.     $_-- unless $_;        # 0 -> -1;
  354.     }
  355.     
  356.     isvalid ($length, @result);
  357. }
  358.     
  359.  
  360. sub grab {
  361.     my $proto = shift;
  362.     new ($proto, &rm_grab);    # Pass on parameter
  363. }
  364.  
  365. sub load {
  366.     my ($proto, $file) = @_;
  367.  
  368.     my $code = RISCOS::File::load ($file);
  369.     unless (defined $code) {
  370.       carp "Could not load file '$file'" if $^W;
  371.       return undef;
  372.     }
  373.     new ($proto, $code);
  374. }
  375.  
  376. sub getmodoffset ($$) {
  377.     my ($module, $offset) = @_;
  378.     return undef unless defined $offset;
  379.     unpack 'I', substr $module, $offset, 4;
  380. }
  381.  
  382. sub getmodtext ($$) {
  383.     my ($module, $offset) = @_;
  384.     # Must be inside module
  385.     return undef unless defined $module and defined $offset
  386.             and (isvalid_not_zero (length ($module), $offset) & 3) 
  387.                   == 1;
  388.     (substr $module, $offset) =~ /^([^\0]*)/s;
  389.     $1;
  390. }
  391.  
  392. sub Title {
  393.     my $self = shift;
  394.     $self->{'__TITLE'};
  395. }
  396.  
  397. sub Name {
  398.     my $self = shift;
  399.     $self->{'__NAME'};
  400. }
  401. sub Version {
  402.     my $self = shift;
  403.     $self->{'__VERSION'};
  404. }
  405. sub Date {
  406.     my $self = shift;
  407.     $self->{'__DATE'};
  408. }
  409. sub Comment {
  410.     my $self = shift;
  411.     $self->{'__COMMENT'};
  412. }
  413.  
  414. sub Length {
  415.     my $self = shift;
  416.     $self->{'__LENGTH'};
  417. }
  418.  
  419. sub Start {
  420.     my $self = shift;
  421.     my $offset = $self->{'___start'};
  422.     return undef unless $offset;
  423.     return $offset if (isvalid ($self->{'__LENGTH'}, $offset) & 7) == 1;
  424.     require ARM;
  425.     return ARM::disassemble ($offset) if defined \&ARM::disassemble;
  426.     sprintf "&%08X", $offset;    # Failed to get disassembler.
  427. }
  428.  
  429. sub Init {
  430.     my $self = shift;
  431.     $self->{'___init'} ? $self->{'___init'} : undef;
  432. }
  433. sub Final {
  434.     my $self = shift;
  435.     $self->{'___final'} ? $self->{'___final'} : undef;
  436. }
  437. sub Service {
  438.     my $self = shift;
  439.     $self->{'___service'} ? $self->{'___service'} : undef;
  440. }
  441. sub SWIchunk {
  442.     my $self = shift;      
  443.     $self->{'___SWIchunk'} ? $self->{'___SWIchunk'} : undef;
  444. }
  445. sub SWIhandler {
  446.     my $self = shift;
  447.     $self->{'___SWIhandler'} ? $self->{'___SWIhandler'} : undef;
  448. }
  449. sub SWIdecode {
  450.     my $self = shift;
  451.     $self->{'___SWIdecode'} ? $self->{'___SWIdecode'} : undef;
  452. }
  453.  
  454. sub TokenFile {
  455.     my $self = shift;
  456.     $self->{'__TOKEN'};
  457. }
  458.  
  459. sub CommandTable {
  460.     my $self = shift;
  461.     $self->{'__COMMANDS'};
  462. }
  463. sub CommandHash {
  464.     my $self = shift;
  465.     $self->{'__COMMAND_INDEX'};
  466. }
  467. sub Command {
  468.     my $self = shift;
  469.     defined ($self->{'__COMMAND_INDEX'}) ? $self->{'__COMMAND_INDEX'}->{$_[0]}
  470.                      : undef;
  471. }
  472.  
  473. sub SWIPrefix {
  474.     my $self = shift;
  475.     $self->{'__SWI_PREFIX'};
  476. }
  477. sub SWITable {
  478.     my $self = shift;
  479.     $self->{'__SWIS'};
  480. }
  481.  
  482. sub Dump {
  483.     my $self = shift;
  484.     my @lines;
  485.     
  486.     foreach my $thing (qw(Title Version Date Comment Start Init Final Service
  487.     TokenFile SWIhandler SWIdecode)) {
  488.     my $value;
  489.     if (eval "defined (\$value = \$self->$thing)") {
  490.         my $line = "$thing:\t";
  491.         $line .= "\t" if length $line < 8;
  492.         push @lines, "$line$value";
  493.     }
  494.     }
  495.     push @lines, '';
  496.     my $table;
  497.     if ($table = $self->SWITable and @$table) {
  498.     my $prefix = $self->SWIPrefix;
  499.     my $swi = $self->SWIchunk;
  500.     push @lines, "SWIs:";
  501.     foreach (@$table) {
  502.         push @lines, sprintf "&%06X: ${prefix}_$_", $swi++;
  503.     }
  504.     push @lines, '';
  505.     }
  506.     if ($table = $self->CommandTable and @$table) {
  507.     foreach (@$table) {
  508.         push @lines, $_->Dump, '';
  509.     }
  510.     }
  511.     return join "\n", @lines unless wantarray;
  512.     pop @lines;    # Loose final ''
  513.     @lines;
  514. }
  515.  
  516. sub split_help_string ($) {
  517.     $_[0] =~ /([\S ]*)\t+([0-9.a-zß]+)\s*\((.*?)\)\s*(.*)/;
  518.     ($1, $2, $3, $4);
  519. }
  520.  
  521. sub rmensure ($;$$) {
  522.     my ($name, $path, $version) = @_;
  523.     $version += 0;    # Default version is 0
  524.     $path = "System:Modules.$name" unless defined $path;
  525.  
  526.     my $word = 'xxxx';
  527.     # Hack to get the address of the xxxx
  528.     my $addr = unpack 'I', pack 'p', $word;
  529.     my $command = "RMEnsure $name $version RMLoad $path";
  530.     kernelswi ('OS_CLI', $command);
  531.  
  532.     # Darren Salt's truely evil rmensure idea...
  533.     $command = "RMEnsure $name $version MemoryA 10_$addr 0 { > Null: }";
  534.     # Execute the MemoryA command if RMEnsure fails, to alter the flag
  535.  
  536.     return undef unless defined kernelswi ('OS_CLI', $command);
  537.     ('xxxx' eq $word) ? 1 : 0;
  538. }
  539.  
  540. sub rm_unsqueeze ($) {
  541.     my $module = shift;
  542.     return undef unless defined $module;
  543.     my $result;
  544.     my $init = getmodoffset ($module, 4);
  545.     my $length = length $module;
  546.     my $status = isvalid ($length, $init);
  547.  
  548.     return $module if ($status == 1);    # It's not squeezed
  549.     return undef unless $status == 2;    # Check - is it exactly at end?
  550.  
  551.     my ($present, $place);
  552.  
  553.     foreach $place (undef, 'Patch:Modules.Unsqueeze',
  554.             'ADFS::4.$.Utilities.Patches.!Patch.Modules.UnSqueeze')
  555.     {
  556.     $present = rmensure ('Unsqueeze', $place, 1.23);
  557.         croak "Unexpected error when checking for UnSqueeze module: $^E"
  558.     unless (defined $present);
  559.     last if $present;
  560.     }
  561.  
  562.     unless ($present)
  563.     {
  564.     my $error = 'Could not load the UnSqueeze module, which is needed to '
  565.             . 'unsqueeze compressed modules. A copy of this module '
  566.             . 'is found in the !Patch application supplied by Acorn.';
  567.     my $patch = $ENV{'Patch$Path'};
  568.     if (defined $patch)
  569.     {
  570.         $patch =~ s/\.$//;
  571.         $error .= " That's strange. Patch seems to be $patch but the module"
  572.               . ' UnSqueeze could not be loaded from its Modules '
  573.               . "subdirectory. Most odd.\n";
  574.     }
  575.     die $error
  576.     }
  577.  
  578.     # Righto. Got the Unsqueeze module.
  579.     my $convert = RISCOS::Filespec::convert();
  580.     RISCOS::Filespec::convert_on();
  581.     my $tmpfile = 'mod000';
  582.     my $text = ' temporary file to unsqueeze module: ';
  583.     ++$tmpfile while -f "/tmp/$tmpfile";
  584.     my $tmpout = $tmpfile;
  585.     ++$tmpout;    # Move beyond input file
  586.     ++$tmpout while -f "/tmp/$tmpout";
  587.     $tmpfile = "/tmp/$tmpfile";
  588.     $tmpout = "/tmp/$tmpout";
  589.     open MOD, ">$tmpfile"
  590.       or croak "Couldn't open$text$!";
  591.  
  592.     local $\;    # Don't want output record separator.
  593.     undef $\;    # N has a nasty habit of setting this to "\n" with -l flag
  594.     my $offset = getmodoffset ($module, 20);
  595.     $offset += (length (getmodtext ($module, $offset)) + 8) & -4;
  596.     $length -= $offset;
  597.     substr ($unsqueeze_code, 0, 4)
  598.       = (getmodoffset ($module, 16) != 52)
  599.     ? pack 'I', 0x180 # increase this if it fails to unsqueeze large modules
  600.     : substr ($module, 48, 4);
  601.  
  602.     print MOD pack ('I', (0xEA000000 | (($length + 15) >> 2))),
  603.           substr ($module, $offset), $unsqueeze_code
  604.       or warn "Couldn't write$text$!";
  605.  
  606.     close MOD
  607.       or croak "Couldn't close$text$!";
  608.  
  609.     RISCOS::File::settype (0xFF8, $tmpfile)
  610.       or croak "Couldn't settype$text$!";
  611.  
  612.     if (system 'UnSqueeze ' . RISCOS::Filespec::riscosify ($tmpfile) . ' '
  613.            . RISCOS::Filespec::riscosify ($tmpout))
  614.     {
  615.       warn "Failed to run unsqueeze command\n";
  616.     }
  617.     else
  618.     {
  619.     if (open MOD, "<$tmpout")
  620.     {
  621.         local $/; undef $/; $result = <MOD>;
  622.         close MOD
  623.     }
  624.     else
  625.     {
  626.         warn "Couldn't open unsqueeze output file: $!";
  627.     }
  628.     }
  629.  
  630.     unlink $tmpfile, $tmpout;
  631.     RISCOS::Filespec::convert($convert);
  632.     $result;
  633. }
  634.  
  635. sub modules {
  636.     my ($previous, $mod, $inst) = (-1, 0, 0);
  637.     my ($postfix, $with, $without) = '';
  638.     my @result;
  639.     
  640.     while (defined $postfix) {
  641.     my $base;
  642.     
  643.     my $result = kernelswi ($os_mods, 12, $mod, $inst);
  644.     if (defined $result) {
  645.         ($mod, $inst, $base, $postfix) = unpack 'x4I3x4p', $result;
  646.     
  647.         my $module = grab_from_os_heap ($base);
  648.     
  649.         push @result, ($mod == $previous) ? $with : $without
  650.           if defined $with;
  651.         $without = getmodtext ($module, getmodoffset ($module, 16));
  652.         $with = "$without%$postfix";
  653.         $without = $with if $inst;
  654.         $previous = $mod;
  655.     } else {
  656.         $mod++; $inst = 0; undef $postfix;
  657.         push @result, $without;
  658.     }
  659.    }
  660.    
  661.    @result;
  662. }
  663.  
  664. sub modules_only {
  665.     my $mod = 0;
  666.     my (@result, $base, $result);
  667.     
  668.     while (defined ($result = kernelswi ($os_mods, 12, $mod++, 0))) {
  669.     $base = unpack 'x12I', $result;
  670.     
  671.     my $module = grab_from_os_heap ($base);
  672.     
  673.     push @result, getmodtext ($module, getmodoffset ($module, 16));
  674.     }
  675.     
  676.     @result;
  677. }
  678.  
  679. $os_mods;
  680. __END__
  681.  
  682. =head1 NAME
  683.  
  684. RISCOS::Module -- manipulate relocatable modules
  685.  
  686. =head1 SYNOPSIS
  687.  
  688.     use RISCOS::Module;
  689.  
  690.     $mod = grab RISCOS::Module $mod;
  691.     print scalar $mod->Dump if $mod;
  692.  
  693.     $cooked = rm_unsqueeze $raw;
  694.  
  695.     $code    = rm_grab $mod;
  696.     $workspace    = rm_workspace $mod;
  697.  
  698. =head1 DESCRIPTION
  699.  
  700. C<RISCOS::Module> provides a class to hold details about a relocatable module,
  701. and a variety of subroutines to manipulate entire relocatable modules and their
  702. workspace from disc and the C<RMA>.
  703.  
  704. =head2 Subroutines
  705.  
  706. =over 4
  707.  
  708. =item rm_private_word <module_name>
  709.  
  710. Returns the contents of a module's private word. Typically this points to the
  711. modules workspace.
  712.  
  713. =item rm_workspace <module_name>
  714.  
  715. Returns the contents of the module workspace (by assuming that the module's
  716. private word points to an area of workspace in the RMA, where the length of
  717. workspace is stored in the preceding word)
  718.  
  719. =item rm_code_addr <module_name>
  720.  
  721. Returns the address of the module's code.
  722.  
  723. =item rm_grab <module_name>
  724.  
  725. Returns the module's code.
  726.  
  727. =item rmensure <module> [, <path> [,<version>]]
  728.  
  729. Emulates the C<RMEnsure> command. If a copy of I<module> of version I<version>
  730. or higher is not present will attempt to C<RMLoad> a module from I<path>.
  731. I<version> defaults to 0, I<path> defaults to 'C<System:Modules.I<module>>'.
  732.  
  733. Returns undefined if an error occurs, 0 if the module is still absent after the
  734. C<RMLoad> command, 1 if the module is present (before or after the command is
  735. run).
  736.  
  737. The method used is a perl conversion of an idea by Darren Salt 
  738. <F<arcsalt@spuddy.mew.co.uk>>.
  739.  
  740. =item rm_unsqueeze <module>
  741.  
  742. Returns the "unsqeezed" version of the B<module> passed in. The entire module
  743. code should be in the scalar, not the name of a module. "unsqeezing" requires
  744. the 'C<UNSqeeze>' module and use of a disc file. (The 'C<UNSqueeze>' module
  745. found in C<!Patch> - C<rm_unsqueeze> will look there and in C<!System.Modules>
  746. for this module).
  747.  
  748. =item split_help_string <string>
  749.  
  750. Splits a module help string into I<Name>, I<Version>, I<Date> and I<Comment>.
  751. Returns an array with these four elements.
  752.  
  753. =item modules
  754.  
  755. Unlike C<modules_only> this will include multiply instantiated modules multiple
  756. times - I<i.e.>: 
  757.  
  758.     FileCore%ADFS
  759.     FileCore%Base
  760.  
  761. =item modules_only
  762.  
  763. Returns a list of the names of all modules in the C<RMA>. Multiply instantiated
  764. modules are only returned once in the list.
  765.  
  766. =back
  767.  
  768. =head2 Methods
  769.  
  770. The C<RISCOS::Module> class provides the following methods:
  771.  
  772. =over 4
  773.  
  774. =item new <module>
  775.  
  776. Creates a new C<RISCOS::Module> object from the module B<code> passed in. C<new>
  777. automatically calls C<rm_unsqueeze>. Returns the object.
  778.  
  779. =item grab <module_name>
  780.  
  781. Grabs the named module from the C<RMA> and calls C<new> with it.
  782.  
  783. =item load <filename>
  784.  
  785. Loads the file using C<RISCOS::File::load> and calls C<new>. Hence I<filename>
  786. can be a filename, a reference to a filehandle, or a reference to a scalar which
  787. is used as the file's contents.
  788.  
  789.  
  790. =item Title
  791.  
  792. Returns the module's title (as found from the C<title> offset).
  793.  
  794. =item Name
  795.  
  796. Returns the module's name (obtained by splitting the help string).
  797.  
  798. =item Version
  799.  
  800. Returns the module's version.
  801.  
  802. =item Date
  803.  
  804. Returns the module's date.
  805.  
  806. =item Comment
  807.  
  808. Returns the module's comment (any text in the help string after the date).
  809.  
  810. =item Length
  811.  
  812. Returns the module's length.
  813.  
  814. =item Start
  815.  
  816. Returns the module's start offset. If the start offset is non-zero but invalid
  817. (not a multiple of four within the module) it is disassembled and the
  818. instruction returned. Returns undefined if the start offset is zero.
  819.  
  820. =item Init
  821.  
  822. Returns the module's initialisation code offset, or undefined if there is no
  823. initialisation code.
  824.  
  825. =item Final
  826.  
  827. Returns the module's finalisation code offset, or undefined if there is no
  828. finalisation code.
  829.  
  830. =item Service
  831.  
  832. Returns the module's service call handler offset, or undefined if there is no
  833. service call code.
  834.  
  835. =item TokenFile
  836.  
  837. Returns the name of the textfile containing tokens used in the module's command
  838. text.
  839.  
  840. =item CommandTable
  841.  
  842. Returns a reference to an array of C<RISCOS::Module::Command> objects (see
  843. below) that describe the C<*> commands provided by the module. If the module
  844. provides no commands, C<CommandTable> returns undefined.
  845.  
  846. =item CommandHash
  847.  
  848. Returns a reference to an array of C<RISCOS::Module::Command> objects, keyed by
  849. command name.  If the module provides no commands, C<CommandHash> returns
  850. undefined.
  851.  
  852. =item Command <name>
  853.  
  854. Looks up I<name> in the hash of C<*> commands provided by the module. Returns a
  855. C<RISCOS::Module::Command> object if found, else undefined.
  856.  
  857. =item SWIchunk
  858.  
  859. Returns the module's C<SWI> chunk number, or undefined if the module does not
  860. provide C<SWI>s (using the module header entries).
  861.  
  862. =item SWIhandler
  863.  
  864. Returns the module's C<SWI> handler code offset, or undefined if there is no
  865. C<SWI> handler code.
  866.  
  867. =item SWIdecode
  868.  
  869. Returns the module's C<SWI> decoding code offset, or undefined if there is no
  870. C<SWI> decoding code.
  871.  
  872. =item SWIPrefix
  873.  
  874. Returns the module's C<SWI> prefix, or undefined if the module does not provide
  875. C<SWI>s. 
  876.  
  877. =item SWITable
  878.  
  879. Returns a reference to an array of C<SWI> names provided by the module. These
  880. are not prefixed by the C<SWI> prefix - for example the WindowManager will
  881. return
  882.  
  883.     ['Initialise', 'CreateWindow', 'CreateIcon',
  884.  
  885. I<etc.>
  886.  
  887. =item Dump
  888.  
  889. Returns a text dump of the module. In array context returns a list of lines. In
  890. scalar context joins these with "\n";
  891.  
  892. =back
  893.  
  894. =head2 RISCOS::Module::Command
  895.  
  896. The C<RISCOS::Module::Command> class provides the following methods:
  897.  
  898. =over 4
  899.  
  900. =item new <module_data>, <offset>
  901.  
  902. Creates a new Command object from the command table entry stored at I<offset> in
  903. the module data supplied. Returns this object in scalar context - in list
  904. context returns C<(object, new-offset, name).
  905.  
  906. =item Name
  907.  
  908. Returns the command's name.
  909.  
  910. =item Min
  911.  
  912. Returns the minimum number of parameters to the command.
  913.  
  914. =item Max
  915.  
  916.  
  917. Returns the maximum number of parameters to the command.
  918.  
  919.  
  920. =item GS_Flags
  921.  
  922. Returns the command's C<GSTrans> flags. In scalar context returns a byte
  923. corresponding to the byte in the command table. For any bit set that number
  924. parameter will be passed to C<GSTrans> before the command is called. In array
  925. context expands this byte and returns a list with 8 elements, each either 0 or
  926. 1. Element 0 refers to parameter 0.
  927.  
  928. =item Code
  929.  
  930. Returns the offset to the command's code.
  931.  
  932. =item Syntax
  933.  
  934. Returns the command's syntax text.
  935.  
  936. =item Help
  937.  
  938. Returns the command's help text, or the offset to the command's help code.
  939.  
  940. =item Flags
  941.  
  942. Returns the command's flags. In array context returns a list of text strings.
  943. In scalar context joins these with ', '
  944.  
  945. =item Dump
  946.  
  947. Returns a text dump of the command. In array context returns a list of lines. In
  948. scalar context joins these with "\n";
  949.  
  950. =back
  951.  
  952. =head1 BUGS
  953.  
  954. None known. However, running C<new> on all modules in my C<RMA> found a couple
  955. that I fixed (I<e.g.> C<Fileswitch> has a non-word aligned command table, which
  956. is legal, but caught me out), so there may still be some.
  957.  
  958. =head1 AUTHOR
  959.  
  960. Nicholas Clark <F<nick@unfortu.net>>
  961.