sub read_strz($) {
    local *code = $_[0];
    $code =~ s/^([^\000]*)\000//s or die "No zero in $code";
    $text = $1;
    $text =~ s/\001/\0n/gs;
    $text =~ s/\002/\0c/gs;
    $text =~ s/([\001-\003\005\010-\037])/sprintf("\0%03o", ord($1))/egs;
    $text =~ s/\004(.)/sprintf("\0*\003%d\002*\0", ord($1))/egs;
    $text =~ s/\006(.)/sprintf("\0o\003%d\002o\0", ord($1))/egs;
    $text =~ s/\007(.)/sprintf("\0'\003%d\002'\0", ord($1))/egs;
    $text =~ s/([\200-\377])/chr(ord($1)-128).chr(32)/egs;
    $text =~ s/\{/ö/gs;
    $text =~ s/\}/Ö/gs;
    $text =~ s/\\/ä/gs;
    $text =~ s/\|/Ä/gs;
    $text =~ s/\[/ü/gs;
    $text =~ s/\]/Ü/gs;
    $text =~ s/~/ß/g;
    $text =~ s/\000/\\/gs;
    return $text;
}


@opcodes = ();
@verbs = ("0", "open", "close", "give", "turnon", 
	  "turnoff", "puton", "takeoff", "8",
	  "push", "pull", "use", "read",
	  "goto", "take", "whatis", "16",
	  "17", "18", "19", "20");
$verbs[250] = "run_ui";
$verbs[253] = "run";
$verbs[254] = "stop";
$verbs[255] = "any";


sub BYTE()  { 1 }
sub WORD()  { 2 }
sub ACTOR() { BYTE }
sub ROOM()  { BYTE }
sub JUMP()    { 3 }
sub VERB()  { 4 }
sub OBJ()   { 5 }
sub IDX()   { 6 }
sub BIDX()  { 7 }
sub SCRIPT(){ 8 }
sub IIIDX()   { IDX+256 }
sub IIBYTE()  { BYTE+256 }
sub IIWORD()  { WORD+256 }
sub IIACTOR() { ACTOR+256 }
sub IIOBJ()   { OBJ+256 }
sub IISCRIPT(){ SCRIPT+256 }
sub IIROOM()  { ROOM+256 }
sub IIVERB()  { VERB+256 }
sub IIDX()    { IDX+512 }
sub IBYTE()   { BYTE+512 }
sub IWORD()   { WORD+512 }
sub IACTOR()  { ACTOR+512 }
sub IOBJ()    { OBJ+512 }
sub ISCRIPT() { SCRIPT+512 }
sub IROOM()   { ROOM+512 }
sub IVERB()   { VERB+512 }
sub STRZ()    { 10 }
sub ACTORSET(){ 11 }
sub BYTEARR() { 12 }
sub WORDARR() { 13 }
sub RES()     { 14 }
sub VERBOPS()   { 15 }
sub IVERBOPS()   { 16 }
sub DOSENTENCE()   { 17 }
sub IDOSENTENCE()   { 17+512 }
sub BARRZ()   { 18 }

sub opc_m {
    my $mask = shift @_;
    my $opc = shift @_;
    my $descr = shift @_;
    my @params = @_;
    my $i;
    for ($i=0; $i < @params; $i++) {
	if ($params[$i] > 512) {
	    $params[$i] -= 256;
	    opc_m($mask >> 1, $opc | $mask, $descr, @params);
	    $params[$i] -= 256;
	    $mask >>=1;
	}
    }
    $opcodes[$opc] = [ $descr, @params ];
}

sub opc {
    opc_m(0x80, @_);
}

opc(0x00, "stopObjectCode");
opc(0x01, "putActor", IACTOR, IBYTE, IBYTE);
opc(0x02, "startMusic", IBYTE);
opc(0x03, "getActorRoom", IDX, IACTOR);
opc(0x04, "isGreater", IDX, IWORD, JUMP);
opc(0x05, "drawObjectAt", IOBJ, IBYTE, IBYTE);
opc(0x06, "getActorElevation", IDX, IACTOR);
opc(0x07, "setStatus1", IOBJ);
opc(0x08, "isEqual", IDX, IWORD, JUMP);
opc(0x09, "faceActorToObj", IACTOR, IWORD);
opc(0x0a, "move", IIIDX, IWORD);
opc(0x0b, "setObjPreposition", IOBJ, IBYTE);
opc(0x0c, "resourceRoutines", IBYTE, RES);
opc(0x0d, "walkActorToActor", IACTOR, IACTOR, BYTE);
opc(0x0e, "putActorAtObject", IACTOR, IOBJ);
opc(0x0f, "isStatus1", IOBJ, JUMP);
opc(0x10, "getObjectOwner", IDX, IWORD);
opc(0x11, "animateActor", IBYTE, IBYTE);
opc(0x12, "panCameraTo", IBYTE);
opc(0x13, "actorSet", IACTOR, IBYTE, ACTORSET);
opc(0x14, "say", IACTOR, STRZ);
opc(0x15, "getActorByCoord", IDX, IBYTE, IBYTE);
opc(0x16, "getRandomNr", IDX, IBYTE);
opc(0x17, "setStatus2", IOBJ);
opc(0x18, "jump", JUMP);
opc(0x19, "doSentence", IDOSENTENCE, IOBJ, IOBJ, IBYTE);
opc(0x1a, "move", IDX, IWORD);
opc(0x1b, "setBit", BIDX, IBYTE, IBYTE);
opc(0x1c, "startSound", IBYTE);
opc(0x1d, "isNotObjectClass", IOBJ, IBYTE, JUMP);
opc(0x1e, "walkActorTo", IACTOR, IBYTE, IBYTE);
opc(0x1f, "isStatus2", IOBJ, JUMP);
opc(0x20, "stopMusic");
opc(0x22, "doSaveGame", IDX, IBYTE);
opc(0x23, "getActorY", IDX, IACTOR);
opc(0x24, "loadRoomWithEGO", IOBJ, IROOM, BYTE, BYTE);
opc(0x26, "setVarRange", IDX, BYTEARR);
opc(0x2a, "add", IIIDX, IWORD);
opc(0x27, "setStatus3", IOBJ);
opc(0x28, "isNotEqualZero", IDX, JUMP);
opc(0x29, "setOwner", IOBJ, IACTOR);
opc(0x2a, "moveWord", IDX, IWORD);
opc(0x2b, "delayFromVar", BYTE);
opc(0x2e, "delay", BYTE, BYTE, BYTE);
opc(0x2f, "isStatus3", IOBJ, JUMP);
opc(0x30, "setWalkboxFlag", IBYTE, BYTE);
opc(0x31, "getBit", IDX, BIDX, IBYTE);
opc(0x32, "setCameraAt", IBYTE);
opc(0x33, "roomOps", IBYTE, IBYTE, BYTE);
opc(0x34, "getDist", IDX, IWORD, IWORD);
opc(0x35, "findObject", IDX, IBYTE, IBYTE);
opc(0x36, "walkActorToObj", IACTOR, IOBJ);
opc(0x37, "setStatus4", IOBJ);
opc(0x38, "isLess", IDX, IWORD, JUMP);
opc(0x3a, "sub", IDX, IWORD);
opc(0x3b, "waitActor", IACTOR);
opc(0x3c, "stopSound", IBYTE);
opc(0x3d, "setActorElevation", IACTOR, IBYTE);
opc(0x3f, "isStatus4", IOBJ, JUMP);
opc(0x40, "cutScene");
opc(0x42, "startScript", SCRIPT);
opc(0x43, "getActorX", IDX, IBYTE);
opc(0x44, "isLessEqual", IDX, IWORD, JUMP);
opc(0x46, "incr", IDX);
opc(0x47, "clearStatus1", IOBJ);
opc(0x48, "isNotEqual", IDX, IWORD, JUMP);
opc(0x4a, "chainScript", ISCRIPT);
opc(0x4c, "stopTopSentence");
opc(0x4f, "isNotStatus1", IOBJ, JUMP);
opc(0x50, "pickupObject", IOBJ);
opc(0x52, "actorFollowCamera??", IACTOR);
opc(0x54, "setObjectName", IWORD, STRZ);
opc(0x56, "getActorWalkState", IDX, IACTOR);
opc(0x57, "clearStatus2", IOBJ);
opc(0x58, "onBreakCutScene", BYTE, JUMP);
opc(0x5a, "add", IDX, IWORD);
opc(0x5c, "nop");
opc(0x5f, "isNotStatus2", IOBJ, JUMP);
opc(0x60, "cursorCommand", IWORD);
opc(0x62, "stopScript", ISCRIPT);
opc(0x63, "getActorFacing", IDX, IACTOR);
opc(0x66, "getNearestActor", IDX, IWORD);
opc(0x67, "clearStatus3", IOBJ);
opc(0x68, "getScriptRunning", IDX, ISCRIPT);
opc(0x6a, "sub", IIIDX, IWORD);
opc(0x6c, "getObjectUnk4", IDX, IOBJ);
opc(0x6e, "nop");
opc(0x6f, "isNotStatus3", IOBJ, JUMP);
opc(0x70, "lights", IBYTE, BYTE, BYTE);
opc(0x71, "getActorCostume", IDX, IACTOR);
opc(0x72, "loadRoom", IBYTE);
opc(0x77, "clearStatus4", IOBJ);
opc(0x78, "isGreaterEqual", IDX, IWORD, JUMP);
opc(0x7a, "verbOps", VERBOPS);
opc(0x7b, "getActorWalkbox", IDX, IACTOR);
opc(0xfa, "verbOps", IVERBOPS);
opc(0x7c, "getSoundRunning", IDX, IBYTE);
opc(0x7f, "isNotStatus4", IOBJ, JUMP);
opc(0x80, "yield");
opc(0x98, "quitPauseRestart");
opc(0xa0, "stopCurrentScript");
opc(0xa6, "setVarRange", IDX, WORDARR);
opc(0xa8, "isEqualZero", IDX, JUMP);
opc(0xac, "buildSentence");
opc(0xae, "wait");
opc(0xc0, "endCutScene");
opc(0xc6, "decr", IDX);
opc(0xcc, "createRooms", BYTE, BARRZ);
opc(0xd8, "printEgo", STRZ);
opc(0xee, "nop");
opc(0x2d, "putActorInRoom", IACTOR, IBYTE);

sub parse_script_to_str($) {
    my ($script) = @_;
    my $scrbackup = $script;
    my $len = length $script;
    my $parsed = "";
    @usedvars = ();
    while ($script) {
	$offset = $len - length($script);
	$parsed .= sprintf ("    %03x: ", $offset);
	my $opc = $opcodes[ord(substr($script, 0, 1))];
	if (! $opc) {
	    $parsed .= sprintf ("Unknown Opcode %02x\n", ord(substr($script,0,1)));
	    last;
	}
	$script = substr $script, 1;

	my ($descr, @params) = @{$opc};
	$parsed .= "$descr(";
	$comma = "";
	while (@params) {
	    $_ = shift @params;
	    $parsed .= $comma;
	    $comma = ", ";
	    if ($_ == BYTE) {
		$parsed .= ord(substr($script, 0, 1));
		$script = substr $script, 1;
	    } elsif ($_ == WORD) {
		$parsed .= unpack "v", substr($script, 0, 2);
		$script = substr $script, 2;
	    } elsif ($_ == VERB) {
		my $v = ord(substr($script, 0, 1));
		$parsed .= $verbs[$v] ? $verbs[$v] : "$v";
		$script = substr $script, 1;
	    } elsif ($_ == IDX) {
		$parsed .= "\003".ord(substr($script, 0, 1))."\002";
		push @usedvars, ord(substr($script, 0, 1));
		$script = substr $script, 1;
	    } elsif ($_ == SCRIPT) {
		$parsed .= "\005".ord(substr($script, 0, 1))."\002";
		push @usedvars, ord(substr($script, 0, 1));
		$script = substr $script, 1;
	    } elsif ($_ == BIDX) {
		my $word = unpack "v", substr($script, 0, 2);
		$parsed .= "\004$word\002";
		push @usedvars, int($word / 16);
		$script = substr $script, 2;
	    } elsif ($_ == OBJ) {
		$parsed .= "\001".(unpack "v", substr($script, 0, 2))."\002";
		$script = substr $script, 2;
	    } elsif ($_ == JUMP) {
		my $joff = unpack("v", substr($script, 0, 2));
		$joff -= 65536 if ($joff > 32767);
		$script = substr $script, 2;
		$parsed .= sprintf "->%04x", $len - length($script) + $joff;
	    } elsif ($_ >= IIBYTE && $_ < IBYTE) {
		$parsed .= sprintf "*\003%d\002",ord(substr($script, 0, 1)) & 0xff;
		push @usedvars, ord(substr($script, 0, 1));
		$script = substr $script, 1;
	    } elsif ($_ == STRZ) {
		$text = read_strz(\$script);
		$parsed .= "\"$text\"";
	    } elsif ($_ == ACTORSET) {
		$aset = ord(substr($script, 0, 1));
		$script = substr $script, 1;
		if ($aset == 1) {
		    $parsed .= "walksound";
		} elsif ($aset == 2) {
		    $parsed .= "palette_";
		    $parsed .= ord(substr($script, 0, 1));
		    $script = substr $script, 1;
		} elsif ($aset == 3) {
		    $parsed .= "setname, ";
		    $text = read_strz(\$script);
		    $parsed .= "\"$text\"";
		} elsif ($aset == 4) {
		    $parsed .= "costume";
		} elsif ($aset == 5) {
		    $parsed .= "talkcolor";
		} else {
		    $parsed .= "$aset";
		}
	    } elsif ($_ == RES) {
		$restype = ord(substr($script, 0, 1));
		$script = substr $script, 1;
		$parsed .= ($restype & 0x0f);
		$parsed .= "+";
		$restype &= 0xf0;
		if ($restype == 0x20) {
		    $parsed .= "costume";
		} elsif ($restype == 0x30) {
		    $parsed .= "room";
		} elsif ($restype == 0x50) {
		    $parsed .= "script";
		} elsif ($restype == 0x60) {
		    $parsed .= "music";
		} else {
		    $parsed .= sprintf "%02x", $restype;
		}

	    } elsif ($_ == BARRZ) {
		$parsed .= "["; $c = "";
		while (ord($script)) {
		    $parsed .=  $c . ord($script);
		    $c = ",";
		    $script = substr $script,1;
		}
		$script = substr $script,1;
		$parsed .= "]";
	    } elsif ($_ == BYTEARR || $_ == WORDARR) {
		$count = ord(substr($script, 0, 1));
		$script = substr $script, 1;
		$parsed .= "[";
		while ($count-- > 0) {
		    if ($_ == BYTEARR) {
			$parsed .= (ord(substr($script, 0, 1))) & 0xff;
			$script = substr $script, 1;
		    } else {
			$parsed .= unpack "v", substr($script, 0, 2);
			$script = substr $script, 2;
		    }
		    $parsed .= "," if ($count != 0);
		}
		$parsed .= "]";
	    } elsif ($_ == VERBOPS || $_ == IVERBOPS) {
		$subopc = ord(substr($script, 0, 1));
		if ($subopc == 0) {
		    $parsed .= "clear";
		    $script = substr $script, 1;
		    @params = (($_ == VERBOPS ? BYTE : IIBYTE), @params);
		} elsif ($subopc == -1) {
		    $parsed .= "setenabled";
		    $script = substr $script, 1;
		    @params = (BYTE, BYTE, @params);
		    $b1 = (ord(substr($script, 0, 1)) & 0xff);
		    $script = substr $script, 1;
		    $b2 = (ord(substr($script, 0, 1)) & 0xff);
		    $script = substr $script, 1;
		} else {
		    $comma = "";
		    @params = (VERB, BYTE, BYTE, ($_ == VERBOPS ? BYTE : IIBYTE), BYTE, STRZ, @params);
		}
	    } elsif ($_ == DOSENTENCE) {
		$subopc = ord(substr($script, 0, 1));
		if ($subopc == 252) {
		    $parsed .= "stop_sentence";
		    $script = substr $script, 1;
		    @params = ();
		} elsif ($subopc == 251) {
		    $parsed .= "clear_sentence";
		    $script = substr $script, 1;
		    @params = ();
		} else {
		    $comma = "";
		    @params = (VERB, @params);
		}
	    } else {
		$parsed .= "param $_??";
	    }
	}
	$parsed .= (")\n        ");
	$newoffset = $len - length($script);
	for ($i = $offset; $i < $newoffset; $i++) {
	    $parsed .= sprintf(" %02x", ord(substr($scrbackup, $i, 1)) & 0xff);
	}
	$parsed .= ("\n");
    }
    return $parsed;
}

sub parse_script($) {
    $_ = parse_script_to_str($_[0]);
    s/\001(\d*)\002/linkobj($1)/eg;
    s/\003(\d*)\002/linkobj($1)/eg;
    s/\004(\d*)\002/linkobj($1)/eg;
    s/\005(\d*)\002/linkobj($1)/eg;
}
