diff options
Diffstat (limited to 'Scripting/Perl/EmRPC.pm')
-rw-r--r-- | Scripting/Perl/EmRPC.pm | 1285 |
1 files changed, 1285 insertions, 0 deletions
diff --git a/Scripting/Perl/EmRPC.pm b/Scripting/Perl/EmRPC.pm new file mode 100644 index 0000000..d02433d --- /dev/null +++ b/Scripting/Perl/EmRPC.pm @@ -0,0 +1,1285 @@ +######################################################################## +# +# File: EmRPC.pm +# +# Purpose: Low-level functions for using RPC with the Palm OS +# Emulator. +# +# Description: This file contains base functions for using RPC: +# +# OpenConnection +# Opens a socket to the Emulator +# +# CloseConnection +# Closes the socket +# +# DoRPC +# Full-service RPC packet sending and receiving, +# including marshalling and unmarshalling of +# parameters. +# +# ReadBlock +# Read up to 256 bytes from the remote device's +# memory. +# +# WriteBlock +# Write up to 256 bytes to the remote device's +# memory. +# +# ReadString +# Read a C string from the remote device's memory. +# +# PrintString +# Debugging utility. Prints a Perl string +# (block of arbitrary data) as a hex dump. +# +######################################################################## + +package EmRPC; + +use vars qw(@ISA @EXPORT); + +use Exporter; +$VERSION = 1.00; +@ISA = qw(Exporter); + +@EXPORT = qw( + OpenConnection CloseConnection + DoRPC + ReadBlock WriteBlock + ReadString PrintString +); + +use IO::Socket; +use IO::File; + +use constant slkSocketDebugger => 0; # Debugger Socket +use constant slkSocketConsole => 1; # Console Socket +use constant slkSocketRemoteUI => 2; # Remote UI Socket +use constant slkSocketDLP => 3; # Desktop Link Socket +use constant slkSocketFirstDynamic => 4; # first dynamic socket ID +use constant slkSocketPoserRPC => 14; + +use constant slkPktTypeSystem => 0; # System packets +use constant slkPktTypeUnused1 => 1; # used to be: Connection Manager packets +use constant slkPktTypePAD => 2; # PAD Protocol packets +use constant slkPktTypeLoopBackTest => 3; # Loop-back test packets + + +######################################################################## +# +# FUNCTION: OpenConnection +# +# DESCRIPTION: Open a socket-based connection to Poser. +# +# PARAMETERS: Either "[<host>][:[<port>]]" (defaults are localhost and +# 6415) to connect to an IPv4 socket, or a filename (with +# at least one "/") to open a file (eg a device file), or +# "@<fd>" to reopen an existing fd. +# +# "#<socket>" can be added to any of these, and causes the +# given SLK socket to be used instead of slkSocketPoserRPC. +# +# The older form with two parameters, (<port> <host>), is +# equivalent to "<host>:<port>". +# +# RETURNED: Nothing. Dies if fail to connect. +# +######################################################################## + +sub OpenConnection +{ + # Are we already connected? + return if defined $sock; + + # Rewrite the old two parameter (<port> <host>) form as <host>:<port> + local ($_) = join ":", reverse @_; + $_ = ":$_" unless /\D/; # Handle two parameters with <host> omitted + + my $sname = (s/#(.*)//)? $1 : "PoserRPC"; + $sname = "slkSocket\u$sname" unless $sname =~ /slkSocket/; + $sock_slkSocket = eval $sname; + die "invalid SLK socket '$sname'\n" if $sock_slkSocket =~ /\D/; + + if (/^@(\d+)/) + { + my $fd = $1; + $sock = new_from_fd IO::Handle ($fd, "r+") + or die "cannot reopen fd $fd: $!\n"; + } + elsif (m"/") + { + $sock = new IO::File ($_, O_RDWR) + or die "cannot open $_: $!\n"; + } + else + { + my ($remote, $port) = split /:/, $_; + $remote = "localhost" unless $remote; + $port = 6415 unless $port; + + $sock = new IO::Socket::INET( PeerAddr => $remote, + PeerPort => $port, + Proto => 'tcp') + or die "cannot connect to $_: $@\n"; + } + + $sock->autoflush (1); +} + + +######################################################################## +# +# FUNCTION: CloseConnection +# +# DESCRIPTION: Close the socket connection to Poser. +# +# PARAMETERS: None. +# +# RETURNED: Nothing. +# +######################################################################## + +sub CloseConnection +{ + close ($sock); + undef $sock; +} + + +######################################################################## +# +# FUNCTION: DoRPC +# +# DESCRIPTION: Performs full, round-trip RPC service. +# +# PARAMETERS: Trap word of function to call. +# Format string describing parameters. +# Parameters to pass in the RPC call. +# +# The format string contains a series of format +# descriptors. Descriptors must be seperated by +# some sort of delimiter, which can be a space, a +# common, a colon, or any combination of those. Each +# descriptor has the following format: +# +# <type><optional size><optional "*"> +# +# The "type" describes the parameter in the format +# expected by the Palm OS. The RPC routines will +# convert the Perl variable corresponding to the +# parameter into the described type. The following +# types are supported: +# +# int: integer +# Err: 2 byte integer +# Coord: 2 byte integer; +# LocalID: 4 byte integer +# HostErr: 4 byte integer +# string: C string +# rptr: Pointer to something back on +# the emulated device +# point: Palm OS PointType +# rect: Palm OS RectangleType +# block: Block of arbitrary data +# +# Some format types can accept a size specifier +# after them. This size specifier is used when +# a default parameter size cannot be implied, or +# when you want to override the default parameter +# size. The following describes how the size +# specifier is handled for each parameter type: +# +# int: +# Length specifier must be supplied, and +# must be one of 8, 16, or 32. +# +# string: +# Default length is the value as returned +# by Perl's "length" function plus one. +# You can override this value by including +# your own length specifier. +# +# block: +# Default length is the value as returned +# by Perl's "length" function. You can +# override this value by including your +# own length specifier. +# +# all others: +# Any specified size is ignored. +# +# In general, integer types are passed by value, and +# all other types are passed by reference. That is +# after all the parameters are marhsalled, sent to +# the emulator, and unmarshalled, the "pass by value" +# parameters are pushed directly onto the emulated +# stack, and "pass by reference" parameters have +# their addresses pushed onto the stack. You can +# can change this behavior in one way: if you way +# an integer to be passed by reference, then you +# can append a "*" to its format specifier. +# +# Examples: +# +# "int16" +# Pass a 2 byte integer +# +# "int32 string" +# Pass a 4 byte integer, followed by +# a C-string +# +# "block32" +# Pass a 32 byte buffer, filling in +# its contents as much as possible +# with the given data +# +# "int16 in32 int32*" +# Pass a 2 byte integer, followed by +# a 4 byte integer, followed by a +# 4 byte integer passed by reference. +# +# RETURNED: List containing: +# Register D0 +# Register A0 +# Full parameter list. If any parameters were +# "pass by reference", you'll receive the +# updated parameters. If parameters are +# "pass by value", you'll get them back just +# the same way you provided them. +# +######################################################################## + +sub DoRPC +{ + my ($trap_word, $format, @parameters) = @_; + + my ($slkSocket) = $sock_slkSocket; + my ($slkPktType) = slkPktTypeSystem; + my ($send_body) = EmRPC::MakeRPCBody ($trap_word, $format, @parameters); + + my ($packet) = MakePacket($slkSocket, $slkSocket, $slkPktType, $send_body); + + SendPacket($packet); + + my ($header, $body, $footer) = ReceivePacket(); + + EmRPC::UnmakeRPCBody ($body, $format); +} + + +######################################################################## +# +# FUNCTION: ReturnValue +# +# DESCRIPTION: . +# +# PARAMETERS: . +# +# RETURNED: . +# +######################################################################## + +sub ReturnValue +{ + my ($format, $D0, $A0, @parameters) = @_; + + my ($type, $size, $by_ref) = GetFormat ($format, 0); + + my ($result); + + if ($type eq "int") + { + return $D0; + } + elsif ($type eq "string") + { + return ($A0, ReadString ($A0)); + } + elsif ($type eq "rptr") + { + return $A0; + } + + die "Unexpected type \"$type\" in EmRPC::ReturnValue, stopped"; +} + + +######################################################################## +# +# FUNCTION: ReadBlock +# +# DESCRIPTION: Read a range of memory from the remote device. +# +# PARAMETERS: address of remote device to start reading from. +# number of bytes to read (256 max). +# +# RETURNED: A Perl string containing the result. +# +######################################################################## + +$sysPktReadMemCmd = 0x01; +$sysPktReadMemRsp = 0x81; + + # typedef struct SysPktReadMemCmdType { + # _sysPktBodyCommon; // Common Body header + # void* address; // Address to read + # Word numBytes; // # of bytes to read + # } SysPktReadMemCmdType; + # typedef SysPktReadMemCmdType* SysPktReadMemCmdPtr; + # + # typedef struct SysPktReadMemRspType { + # _sysPktBodyCommon; // Common Body header + # // Byte data[?]; // variable size + # } SysPktReadMemRspType; + # typedef SysPktReadMemRspType* SysPktReadMemRspPtr; + +sub ReadBlock +{ + my ($address, $num_bytes) = @_; + + my ($slkSocket) = $sock_slkSocket; + my ($slkPktType) = slkPktTypeSystem; + my ($send_body) = pack ("cxNn", $sysPktReadMemCmd, $address, $num_bytes); + + my ($packet) = MakePacket($slkSocket, $slkSocket, $slkPktType, $send_body); + + SendPacket($packet); + + my ($header, $body, $footer) = ReceivePacket(); + + unpack ("xx a$num_bytes", $body); +} + + +######################################################################## +# +# FUNCTION: WriteBlock +# +# DESCRIPTION: Write a range of bytes to the remote device. +# +# PARAMETERS: address to start writing to. +# a Perl string containing the stuff to write. +# +# RETURNED: nothing +# +######################################################################## + +$sysPktWriteMemCmd = 0x02; +$sysPktWriteMemRsp = 0x82; + + # typedef struct SysPktWriteMemCmdType { + # _sysPktBodyCommon; // Common Body header + # void* address; // Address to write + # Word numBytes; // # of bytes to write + # // Byte data[?]; // variable size data + # } SysPktWriteMemCmdType; + # typedef SysPktWriteMemCmdType* SysPktWriteMemCmdPtr; + # + # typedef struct SysPktWriteMemRspType { + # _sysPktBodyCommon; // Common Body header + # } SysPktWriteMemRspType; + # typedef SysPktWriteMemRspType* SysPktWriteMemRspPtr; + +sub WriteBlock +{ + my ($address, $data) = @_; + + my ($slkSocket) = $sock_slkSocket; + my ($slkPktType) = slkPktTypeSystem; + my ($send_body) = pack ("cxNn", $sysPktWriteMemCmd, $address, length ($data)) . $data; + + my ($packet) = MakePacket($slkSocket, $slkSocket, $slkPktType, $send_body); + + SendPacket($packet); + + ReceivePacket(); # receive the results, but we don't need to do anything with them +} + + +######################################################################## +# +# FUNCTION: SendPacket +# +# DESCRIPTION: Send a fully-built packet to Poser. The socket +# connection to Poser should already have been +# established +# +# PARAMETERS: The packet to be sent. +# +# RETURNED: Nothing. +# +######################################################################## + +sub SendPacket +{ + my ($packet) = @_; + + print $sock $packet; +} + + +######################################################################## +# +# FUNCTION: ReceivePacket +# +# DESCRIPTION: Receive a packet from Poser. +# +# PARAMETERS: None. +# +# RETURNED: The packet header, body, and footer as an array. +# +######################################################################## + +sub ReceivePacket +{ + my ($header, $body, $footer); + + my ($header_length) = 10; + sysread($sock, $header, $header_length); + + my ($body_length) = GetBodySize($header); + sysread($sock, $body, $body_length); + + my ($footer_length) = 2; + sysread($sock, $footer, $footer_length); + + ($header, $body, $footer); +} + + +######################################################################## +# +# FUNCTION: MakePacket +# +# DESCRIPTION: Builds up a complete packet for sending to Poser +# including the header, body, and footer. +# +# PARAMETERS: $src - the source SLP socket. Generally something +# like slkSocketDebugger or slkSocketConsole. +# +# $dest - the destination SLP socket. +# +# $type - the type of packet. Generally something +# like slkPktTypeSystem or slkPktTypePAD. +# +# $body - the body of the packet. +# +# RETURNED: The built packet as a Perl string. The header and +# footer checksums will be calculated and filled in. +# +######################################################################## + + # struct SlkPktHeaderType + # { + # Word signature1; // X first 2 bytes of signature + # Byte signature2; // X 3 and final byte of signature + # Byte dest; // -> destination socket Id + # Byte src; // -> src socket Id + # Byte type; // -> packet type + # Word bodySize; // X size of body + # Byte transID; // -> transaction Id + # // if 0 specified, it will be replaced + # SlkPktHeaderChecksum checksum; // X check sum of header + # }; + # + # struct SlkPktFooterType + # { + # Word crc16; // header and body crc + # }; + +$header_template = "H6CCCnCC"; # 6 Hex digits, 3 unsigned chars, a B.E. short, 2 unsigned chars +$footer_template = "n"; # a B.E. short + +$signature = "BEEFED"; + +sub MakePacket +{ + my ($src, $dest, $type, $body) = @_; + + if (not defined($transID)) + { + $transID = 0; + } + + ++$transID; + + my ($bodySize) = length ($body); + my ($header_checksum) = CalcHeaderChecksum ($signature, $dest, $src, $type, $bodySize, $transID); + + my ($header) = pack ($header_template, $signature, $dest, $src, $type, $bodySize, $transID, $header_checksum); + +# my ($footer_checksum) = CalcFooterChecksum ($header, &body); + my ($footer_checksum) = 0; + my ($footer) = pack ($footer_template, $footer_checksum); + + $header . $body . $footer; +} + + +######################################################################## +# +# FUNCTION: CalcHeaderChecksum +# +# DESCRIPTION: Calculate that checksum value for the packet header. +# +# PARAMETERS: The components of the header. +# +# RETURNED: The checksum that should be placed in the SLP +# packet header. +# +######################################################################## + +sub CalcHeaderChecksum +{ + my ($signature, $dest, $src, $type, $bodySize, $transID) = @_; + + my ($checksum, $temp_buffer); + + $checksum = 0; + + $temp_buffer = pack ($header_template, $signature, $dest, $src, $type, $bodySize, $transID, 0); + @bytes = unpack("C8", $temp_buffer); + $checksum = $bytes[0] + $bytes[1] + $bytes[2] + $bytes[3] + $bytes[4] + + $bytes[5] + $bytes[6] + $bytes[7]; + + $checksum % 256; +} + + +######################################################################## +# +# FUNCTION: CalcFooterChecksum +# +# DESCRIPTION: Calculate the checksum value for the packet footer. +# +# PARAMETERS: The header and body. +# +# RETURNED: The checksum that should be placed in the SLP +# packet footer. +# +######################################################################## + +sub CalcFooterChecksum +{ + my ($header, $body) = @_; + + my ($checksum, $temp_buffer); + + $temp_buffer = $header . $body; + + $checksum = unpack("%16c*", $temp_buffer); # Wrong kind of checksum! +} + + +######################################################################## +# +# FUNCTION: MakeRPCBody +# +# DESCRIPTION: Create the body of an RPC packet, suitable for +# being passed off to MakePacket. +# +# PARAMETERS: The "trap word" of the trap that needs to be called +# (as defined by the constants in SysTraps.pm) and +# the parameters of the RPC call, as created by the +# MakeParam function. +# +# RETURNED: The body of the packet as a string. +# +######################################################################## + + # struct SysPktRPCType + # { + # _sysPktBodyCommon; // Common Body header + # Word trapWord; // which trap to execute + # DWord resultD0; // result from D0 placed here + # DWord resultA0; // result from A0 placed here + # Word numParams; // how many parameters follow + # // Following is a variable length array ofSlkRPCParamInfo's + # SysPktRPCParamType param[1]; + # }; + +$rpc_header_template = "CxH4NNn"; # unsigned byte, filler, 4 hex digits, 2 B.E. longs, B.E. short +$sysPktRPCCmd = 0x0A; +$sysPktRPCRsp = 0x8A; + +sub MakeRPCBody +{ + my ($trapword, $format, @param_list) = @_; + + my ($rpc_header) = pack ($rpc_header_template, $sysPktRPCCmd, $trapword, 0, 0, $#param_list + 1); + my ($rpc_body) = join ("", $rpc_header, Marshal($format, @param_list)); + + $rpc_body; +} + +sub UnmakeRPCBody +{ + my ($body, $format) = @_; + + my ($cmd, $trap_word, $D0, $A0, $num_params, $packed_parms) = unpack ("$rpc_header_template a*", $body); + my (@parms) = Unmarshal($packed_parms, $format); + + return ($D0, $A0, @parms); +} + + +$rpc2_header_template = "CxH4NNN"; # unsigned byte, filler, 4 hex digits, 3 B.E. longs +$sysPktRPC2Cmd = 0x20; +$sysPktRPC2Rsp = 0xA0; + +sub MakeRPC2Body +{ + my ($trapword, $reg_list, @param_list) = @_; + + my ($rpc_header) = pack ($rpc_header_template, $sysPktRPCCmd, $trapword, 0, 0, 0); + my ($param_count) = pack ("n", $#param_list + 1); + my ($rpc_body) = join ("", $rpc_header, $reg_list, $param_count, reverse @param_list); + + $rpc_body; +} + + +######################################################################## +# +# FUNCTION: PackRegList +# +# DESCRIPTION: Pack a list of register values into the format +# needed by an RPC2 packet. +# +# PARAMETERS: An associative array, where each key contains Ax +# or Dx, and the value contains the register value. +# +# RETURNED: The packed registers as a string. +# +######################################################################## + +sub PackRegList +{ + my (%reg_list) = @_; + + my ($dreg_bits, $areg_bits, $dregs, $aregs); + + $dreg_bits = 0; + $areg_bits = 0; + $dregs = ""; + $aregs = ""; + + foreach $key (sort keys %reg_list) + { + my($reg_space) = substr($key, 0, 1); + my($bit_to_set) = (1 << (ord(substr($key, 1, 1)) - ord("0"))); + my($value) = $reg_list{$key}; + + if ($reg_space eq "D") + { + $dreg_bits |= $bit_to_set; + $dregs .= pack ("N", $value); + } + else + { + $areg_bits |= $bit_to_set; + $aregs .= pack ("N", $value); + } + } + + my ($result) = join ("", pack("CC", $dreg_bits, $areg_bits), $dregs, $aregs); +} + + +######################################################################## +# +# FUNCTION: MakeParam +# +# DESCRIPTION: Create a parameter array element, suitable for being +# added to other parameter array elements and -- +# eventually -- to an RPC packet body. +# +# PARAMETERS: $data - the data to be added. +# $data_len - the length of the data to be added. If +# greater than zero, then we assume $data to be +# an integer. If equal to zero, then we assume +# data to be a string where the length of the +# string is determined by the length () function. +# If less than zero, then data is assumed to be +# a buffer with a length of -$data. +# $by_ref - zero if the parameter is to be treated as +# pass-by-value. Non-zero if it's pass-by-ref. +# +# RETURNED: A parameter string that can be appended to a longer +# string of parameters. If the length of the string +# would otherwise be odd, a padding byte is added. +# +######################################################################## + + # struct SysPktRPCParamInfo + # { + # Byte byRef; // true if param is by reference + # Byte size; // # of Bytes of paramData (must be even) + # Word data[1]; // variable length array of paramData + # }; + +sub ToParamBlock +{ + my ($data, $data_len) = @_; + + die "Undefined \$data, stopped" unless defined($data); + die "\$data_len is negative, stopped" if ($data_len < 0); + + ## If data_len == 0, determine the length using the length () function. + ## Else, use the given length. + + if ($data_len == 0) + { + $data_len = length ($data); + } + else + { + $data = pack ("a$data_len", $data); + } + + ## Pack up the data. + + my ($param) = pack ("CC", 1, $data_len) . $data; + + ## Make sure the packed data is an even number of bytes long. + + if (($data_len % 2) != 0) + { + $param .= "\0"; + } + + $param; +} + + +sub FromParamBlock +{ + my ($param, $data_len) = @_; + + die "Undefined \$param, stopped" unless defined($param); + die "\$data_len is negative, stopped" if ($data_len < 0); + + ## Just ignore the $data_len and use what's in the parameter block. + + $data_len = unpack ("xC", $param); + + unpack ("xxa$data_len", $param); +} + + +sub ToParamInt +{ + my ($data, $data_len, $by_ref) = @_; + + die "Undefined \$data, stopped" unless defined($data); + + my ($format); + + if ($data_len == 8) + { + $format = ("CCCx"); + $data_len = 1; + } + elsif ($data_len == 16 || $data_len == 0) + { + $format = ("CCn"); + $data_len = 2; + } + elsif ($data_len == 32) + { + $format = ("CCN"); + $data_len = 4; + } + else + { + die "\$data_len not 8, 16, or 32, stopped"; + } + + ## Pack up the data. + + pack ($format, $by_ref, $data_len, $data); +} + + +sub FromParamInt +{ + my ($param, $data_len) = @_; + + die "Undefined \$param, stopped" unless defined($param); + + my ($format); + + if ($data_len == 8) + { + $format = ("xxCx"); + } + elsif ($data_len == 16 || $data_len == 0) + { + $format = ("xxn"); + } + elsif ($data_len == 32) + { + $format = ("xxN"); + } + else + { + die "\$data_len not 8, 16, or 32, stopped"; + } + + unpack ($format, $param); +} + + +sub ToParamPoint +{ + my ($point) = @_; + my ($param); + + if (defined $point->{x}) + { + $param = pack ("CCnn", 1, 4, $point->{x}, $point->{y}); + } + else + { + $param = pack ("CCxxxx", 1, 4); + } + + $param; +} + + +sub FromParamPoint +{ + my ($param) = @_; + + die "Undefined \$param, stopped" unless defined($param); + + my (@coords) = unpack ("xxnn", $param); + + {x => $coords[0], + y => $coords[1]}; +} + + +sub ToParamRect +{ + my ($rect) = @_; + my ($param); + + if (defined $rect->{height}) + { + $param = pack ("CCnnnn", 1, 8, $rect->{left}, $rect->{top}, $rect->{width}, $rect->{height}); + } + elsif (defined $rect->{bottom}) + { + $param = pack ("CCnnnn", 1, 8, $rect->{left}, $rect->{top}, $rect->{right} - $rect->{left}, $rect->{bottom} - $rect->{top}); + } + else + { + $param = pack ("CCxxxxxxxx", 1, 8); + } + + $param; +} + + +sub FromParamRect +{ + my ($param) = @_; + + die "Undefined \$param, stopped" unless defined($param); + + my (@coords) = unpack ("xxnnnn", $param); + + {left => $coords[0], + top => $coords[1], + width => $coords[2], + height => $coords[3], + right => $coords[0] + $coords[2], + bottom => $coords[1] + $coords[3]}; +} + + +sub ToParamString +{ + my ($data, $data_len) = @_; + + die "Undefined \$data, stopped" unless defined($data); + die "\$data_len is negative, stopped" if ($data_len < 0); + + ## If $data_len == 0, determine the length using the length () function. + + if ($data_len == 0) + { + $data_len = length ($data) + 1; # Add 1 to get 1 byte of NULL padding + } + + ## Pack up the data. + + my ($param) = pack ("CCa$data_len", 1, $data_len, $data); + + ## Make sure the packed data is an even number of bytes long. + + if (($data_len % 2) != 0) + { + $param .= "\0"; + } + + $param; +} + + +sub FromParamString +{ + my ($param) = @_; + + unpack ("xxA*", $param); +} + + +######################################################################## +# +# FUNCTION: UnpackHeader +# +# DESCRIPTION: Disassemble a packet header into its consituent +# parts. +# +# PARAMETERS: The packet header as received from Poser +# +# RETURNED: The signature, destination port, source port, +# packet type, body size, transaction ID, and +# checksum as an array. +# +######################################################################## + +sub UnpackHeader +{ + my($header) = @_; + + my ($signature, $dest, $src, $type, $bodySize, $transID, $checksum) + = unpack ($header_template, $header); + + ($signature, $dest, $src, $type, $bodySize, $transID, $checksum); +} + + +######################################################################## +# +# FUNCTION: GetBodySize +# +# DESCRIPTION: Utility function to extract the packet body size +# field from the packet header. +# +# PARAMETERS: The packet header as received from Poser. +# +# RETURNED: The size of the body following the header. +# +######################################################################## + +sub GetBodySize +{ + my($header) = @_; + + my ($signature, $dest, $srs, $type, $bodySize, $transID, $checksum) + = UnpackHeader ($header); + + $bodySize; +} + + +sub SkipWhite +{ + my ($format, $format_index) = @_; + + while () + { + last if ($format_index >= length ($format)); + + my ($char) = substr ($format, $format_index, 1); + last unless ($char eq " " || $char eq "," || $char eq ":"); + + $format_index += 1; + } + + $format_index +} + + +sub GetType +{ + my ($format, $format_index) = @_; + my ($type) = ""; + + $format_index = SkipWhite ($format, $format_index); + + while () + { + last if ($format_index >= length ($format)); + + my ($char) = substr ($format, $format_index, 1); + last if (($char lt "a" || $char gt "z") && ($char lt "A" || $char gt "Z")); + + $type .= $char; + $format_index += 1; + } + + die "Unknown type (\"$type\" @ $format_index), stopped" + unless ($type eq "int" || + $type eq "Err" || + $type eq "Coord" || + $type eq "LocalID" || + $type eq "HostErr" || + $type eq "string" || + $type eq "rptr" || + $type eq "point" || + $type eq "rect" || + $type eq "block"); + + return ($type, $format_index); +} + + +sub GetSize +{ + my ($format, $format_index) = @_; + my ($size) = 0; + + while () + { + last if ($format_index >= length ($format)); + + my ($char) = substr ($format, $format_index, 1); + last if ($char lt "0" || $char gt "9"); + + $size = $size * 10 + $char; + $format_index += 1; + } + + return ($size, $format_index); +} + + +sub GetByRef +{ + my ($format, $format_index) = @_; + my ($by_ref) = 0; + + if (substr ($format, $format_index, 1) eq "*") + { + $by_ref = 1; + } + + if ($by_ref) + { + $format_index += 1; + } + + return ($by_ref, $format_index); +} + + +sub GetFormat +{ + my ($format, $format_index) = @_; + my ($type, $size, $by_ref) = (" ", 0, 0); + + ($type, $format_index) = GetType ($format, $format_index); + ($size, $format_index) = GetSize ($format, $format_index); + ($by_ref, $format_index) = GetByRef ($format, $format_index); + + ## Deal with aliases + + if ($type eq "LocalID" or $type eq "HostErr") + { + $type = "int"; + $size = 32; + } + elsif ($type eq "Err" or $type eq "Coord") + { + $type = "int"; + $size = 16; + } + + return ($type, $size, $by_ref, $format_index); +} + + +sub Marshal +{ + my ($format, @parameters) = @_; + my (@result); + + my ($format_index) = 0; + my ($parameter_index) = 0; + + while ($format_index < length ($format)) + { + my ($parm); + + my ($type, $size); + ($type, $size, $by_ref, $format_index) = GetFormat ($format, $format_index); + + if ($type eq "int") + { + $parm = EmRPC::ToParamInt($parameters[$parameter_index], $size, $by_ref); + } + elsif ($type eq "rptr") + { + $parm = EmRPC::ToParamInt($parameters[$parameter_index], 32, 0); + } + elsif ($type eq "point") + { + $parm = EmRPC::ToParamPoint($parameters[$parameter_index], $size); + } + elsif ($type eq "rect") + { + $parm = EmRPC::ToParamRect($parameters[$parameter_index], $size); + } + elsif ($type eq "string") + { + $parm = EmRPC::ToParamString($parameters[$parameter_index], $size); + } + elsif ($type eq "block") + { + $parm = EmRPC::ToParamBlock($parameters[$parameter_index], $size); + } + else + { + die "Unexpected type \"$type\" in EmRPC::Marshal, stopped"; + } + + push (@result, $parm); + + $parameter_index += 1; + } + + return join ("", reverse @result); +} + + +sub BreakApartParameters +{ + my ($packed_parms) = @_; + my (@result) = 0; + + my ($offset) = 0; + + while ($offset < length ($packed_parms)) + { + # Get the size field. + + my ($size) = unpack ("x$offset" . "xC", $packed_parms); + + # Add in the lengths of the byRef and size fields. + + $size += 2; + + # Make sure the field is word-aligned. + + if (($size % 2) != 0) + { + $size += 1; + } + + # Get the SysPktRPCParamInfo. + + my ($parm) = unpack ("x$offset a$size", $packed_parms); + + push (@result, $parm); + + $offset += $size; + } + + return @result; +} + + +sub Unmarshal +{ + my ($packed_parms, $format) = @_; + my (@result); + + my ($format_index) = 0; + my ($parameter_index) = 0; + + my (@parameters) = reverse BreakApartParameters($packed_parms); + + while ($format_index < length ($format)) + { + my ($parm); + + my ($type, $size); + ($type, $size, $by_ref, $format_index) = GetFormat ($format, $format_index); + + if ($type eq "int") + { + $parm = EmRPC::FromParamInt($parameters[$parameter_index], $size); + } + elsif ($type eq "rptr") + { + $parm = EmRPC::FromParamInt($parameters[$parameter_index], 32); + } + elsif ($type eq "point") + { + $parm = EmRPC::FromParamPoint($parameters[$parameter_index]); + } + elsif ($type eq "rect") + { + $parm = EmRPC::FromParamRect($parameters[$parameter_index]); + } + elsif ($type eq "string") + { + $parm = EmRPC::FromParamString($parameters[$parameter_index]); + } + elsif ($type eq "block") + { + $parm = EmRPC::FromParamBlock($parameters[$parameter_index], $size); + } + else + { + die "Unexpected type \"$type\" in EmRPC::Unmarshal, stopped"; + } + + push (@result, $parm); + + $parameter_index += 1; + } + + return @result; +} + + +sub ReadString +{ + my ($address) = @_; + + my ($block) = EmRPC::ReadBlock($address, 128); + + $block =~ /^([^\000]*)/; + return $1; +} + + +sub PrintString +{ + my($string) = @_; + + foreach $ii (0..length ($string) - 1) + { + my($ch) = substr($string, $ii, 1); + + printf "0x%02X, ", ord($ch); + + if ($ii % 8 == 7) + { + print "\n"; + } + } + + printf "\n"; + + $string; +} + +1; |