├── DebugConfig.php ├── DisIFPS.php ├── IFPSOpcodes.php ├── IFPSTypes.php ├── LICENSE ├── readme.md ├── DebugMode.php ├── IFPSGlobals.php ├── IFPSDisassembler.php └── IFPS.php /DebugConfig.php: -------------------------------------------------------------------------------- 1 | [out]\r\n"; 8 | echo "file: compiled PascalScript file to disassemble\r\n"; 9 | echo "out: file to output to; if not passed, disassembly will be written to standard output\r\n"; 10 | die(); 11 | } 12 | 13 | $disasm = (new IFPSDisassembler(IFPS::LoadFile($argv[1])))->Disassemble(); 14 | if ($argc < 3) { 15 | echo $disasm."\r\n"; 16 | } else file_put_contents($argv[2],$disasm); -------------------------------------------------------------------------------- /IFPSOpcodes.php: -------------------------------------------------------------------------------- 1 | [disassembly.txt]` - if an output path is not given, the output will be written to standard output. 23 | 24 | Feel free to submit pull requests; I will look at them when I get time. 25 | -------------------------------------------------------------------------------- /DebugMode.php: -------------------------------------------------------------------------------- 1 | "Global","index"=>$a); 56 | $a -= 0x60000000; 57 | if (($a == -1) && (!self::$isVoid)) return (object)array("type"=>"RetVal","index"=>$a); 58 | if ($a >= 0) return (object)array("type"=>"Var","index"=>$a); 59 | return (object)array("type"=>"Arg","index"=>((-$a) - (!self::$isVoid))); 60 | } 61 | 62 | public static function ParseFDecl($f) { 63 | $ret = (object) array(); 64 | $offset = 0; 65 | if (substr($f,0,4) == "dll:") { 66 | $f = explode("\x00",$f,3); 67 | $ret->type = 0; 68 | $ret->dll = substr($f[0],4); 69 | $ret->func = $f[1]; 70 | $restlen = strlen($f[2]); 71 | $ret->CallingConv = current(unpack('C',substr($f[2],$offset++,1))); 72 | switch ($ret->CallingConv) { 73 | case 0: 74 | $ret->CallingConv = "register"; 75 | break; 76 | case 1: 77 | $ret->CallingConv = "pascal"; 78 | break; 79 | case 2: 80 | $ret->CallingConv = "cdecl"; 81 | break; 82 | case 3: 83 | $ret->CallingConv = "stdcall"; 84 | break; 85 | default: 86 | throw new Exception("Unhandled calling convention ".$ret->CallingConv); 87 | } 88 | $ret->DelayLoad = (bool) current(unpack('C',substr($f[2],$offset++,1))); 89 | $ret->LoadWithAlteredSearchPath = (bool) current(unpack('C',substr($f[2],$offset++,1))); 90 | $ret->isVoid = !( (bool) ( current(unpack('C',substr($f[2],$offset++,1))) ) ); 91 | $f = $f[2]; 92 | } elseif (substr($f,0,6) == "class:") { 93 | $f = substr($f,6); 94 | if ($f == "+") { 95 | $ret->type = 1; 96 | $ret->classname = "Class"; 97 | $ret->func = "CastToType"; 98 | $ret->isVoid = 0; 99 | $ret->CallingConv = "pascal"; 100 | $ret->params = array((object) array("modeIn"=> false),(object) array("modeIn"=> false)); 101 | return $ret; 102 | } elseif ($f == "-") { 103 | $ret->type = 1; 104 | $ret->classname = "Class"; 105 | $ret->func = "SetNil"; 106 | $ret->isVoid = 0; 107 | $ret->CallingConv = "pascal"; 108 | $ret->params = array((object) array("modeIn"=> false)); 109 | return $ret; 110 | } 111 | $f = explode("|",$f); 112 | $ret->type = 1; 113 | $ret->classname = $f[0]; 114 | $ret->func = $f[1]; 115 | if (substr($ret->func,-1) == "@") { 116 | $ret->isProperty = true; 117 | $ret->func = substr($ret->func,0,-1); 118 | } else $ret->isProperty = false; 119 | try { 120 | $restlen = strlen($f[2]); 121 | } catch (Exception $e) { 122 | var_dump($f); 123 | throw $e; 124 | } 125 | $ret->CallingConv = current(unpack('C',substr($f[2],$offset++,1))); 126 | switch ($ret->CallingConv) { 127 | case 0: 128 | $ret->CallingConv = "register"; 129 | break; 130 | case 1: 131 | $ret->CallingConv = "pascal"; 132 | break; 133 | case 2: 134 | $ret->CallingConv = "cdecl"; 135 | break; 136 | case 3: 137 | $ret->CallingConv = "stdcall"; 138 | break; 139 | default: 140 | throw new Exception("Unhandled calling convention ".$ret->CallingConv); 141 | } 142 | $ret->isVoid = !( (bool) ( current(unpack('C',substr($f[2],$offset++,1))) ) ); 143 | $f = $f[2]; 144 | } else { 145 | $restlen = strlen($f); 146 | $ret->type = 2; 147 | $ret->isVoid = !( (bool) ( current(unpack('C',substr($f,$offset++,1))) ) ); 148 | } 149 | $ret->params = array(); 150 | while ($offset < $restlen) { 151 | $p = current(unpack('C',substr($f,$offset++,1))); 152 | $ret->params[] = (object) array("modeIn"=> (bool)$p); 153 | $offset++; 154 | } 155 | return $ret; 156 | } 157 | } -------------------------------------------------------------------------------- /IFPSDisassembler.php: -------------------------------------------------------------------------------- 1 | ifps = $IFPSObj; 12 | } 13 | 14 | public function DumpType($t) { 15 | switch ($t->BaseType) { 16 | case IFPSTypes::S64: 17 | return "S64"; 18 | case IFPSTypes::U8: 19 | return "U8"; 20 | case IFPSTypes::S8: 21 | return "S8"; 22 | case IFPSTypes::U16: 23 | return "U16"; 24 | case IFPSTypes::S16: 25 | return "S16"; 26 | case IFPSTypes::U32: 27 | return "U32"; 28 | case IFPSTypes::S32: 29 | return "S32"; 30 | case IFPSTypes::Single: 31 | return "Single"; 32 | case IFPSTypes::Double: 33 | return "Double"; 34 | case IFPSTypes::Currency: 35 | return "Currency"; 36 | case IFPSTypes::Extended: 37 | return "Extended"; 38 | case IFPSTypes::String: 39 | return "String"; 40 | case IFPSTypes::Pointer: 41 | return "Pointer"; 42 | case IFPSTypes::PChar: 43 | return "PChar"; 44 | case IFPSTypes::Variant: 45 | return "Variant"; 46 | case IFPSTypes::Char: 47 | return "Char"; 48 | case IFPSTypes::UnicodeString: 49 | return "UnicodeString"; 50 | case IFPSTypes::WideString: 51 | return "WideString"; 52 | case IFPSTypes::WideChar: 53 | return "WideChar"; 54 | case IFPSTypes::_Class: 55 | return "Class"; 56 | case IFPSTypes::ProcPtr: 57 | return "ProcPtr"; 58 | case IFPSTypes::_Interface: 59 | return "Interface"; 60 | case IFPSTypes::Set: 61 | return "Set"; 62 | case IFPSTypes::StaticArray: 63 | return $this->DumpType($t->ArrayType)."[".$t->Size."]"; 64 | case IFPSTypes::_Array: 65 | return $this->DumpType($t->ArrayType)."[]"; 66 | case IFPSTypes::Record; 67 | $ret = "Record <"; 68 | foreach ($t->FFieldTypes as $ft) { 69 | $ret .= $this->DumpType($ft).","; 70 | } 71 | return substr($ret,0,-1).">"; 72 | default: 73 | return "Unknown type 0x".dechex($t->BaseType); 74 | } 75 | } 76 | 77 | public function Disassemble() { 78 | return implode("\r\n",array($this->DumpTypes(),$this->DumpVars(),$this->DumpDisasm())); 79 | } 80 | 81 | public function DumpTypes() { 82 | $ret = ""; 83 | $i = 0; 84 | foreach ($this->ifps->types as $t) { 85 | $ret .= "Types[".($i++)."] = "; 86 | if (property_exists($t,"ExportName")) $ret .= $t->ExportName." = "; 87 | $ret .= $this->DumpType($t)."\r\n"; 88 | } 89 | return $ret; 90 | } 91 | 92 | public function DumpVars() { 93 | $ret = ""; 94 | $i = 0; 95 | foreach ($this->ifps->vars->globals as $v) { 96 | $ret .= "Vars[".($i++)."].Type = "; 97 | if (property_exists($v->type,"ExportName")) $ret .= $v->type->ExportName." = "; 98 | $ret .= $this->DumpType($v->type)."\r\n"; 99 | } 100 | return $ret; 101 | } 102 | 103 | public function DumpOperand($o) { 104 | if (!is_object($o)) { 105 | if (is_integer($o)) return "0x".dechex($o); 106 | return $o; 107 | } 108 | switch ($o->otype) { 109 | case 0: 110 | return $o->var->type.($o->var->index != -1 ? $o->var->index : ''); 111 | case 1: 112 | $ret = "( ".$this->DumpType($o->type)." "; 113 | switch ($o->type->BaseType) { 114 | case IFPSTypes::String: 115 | case IFPSTypes::UnicodeString: 116 | case IFPSTypes::WideString: 117 | case IFPSTypes::PChar: 118 | $ret .= '"'.str_replace('"','\"',$o->value).'"'; 119 | break; 120 | case IFPSTypes::Char: 121 | case IFPSTypes::WideChar: 122 | $ret .= "'".str_replace("'","\\'",$o->value)."'"; 123 | break; 124 | case IFPSTypes::ProcPtr: 125 | if (!array_key_exists($o->value,$this->ifps->funcs)) $ret .= "func_".dechex($o->value); 126 | else { 127 | $f = $this->ifps->funcs[$o->value]; 128 | if ($f->Flags & 1) { 129 | if ($f->Flags & 2) { 130 | switch ($f->FDecl->type) { 131 | case 0: 132 | $ret .= $f->FDecl->dll."!".$f->FDecl->func; 133 | break; 134 | case 1: 135 | $ret .= $f->FDecl->classname."->".$f->FDecl->func; 136 | break; 137 | case 2: 138 | if ($f->Name != "") $ret .= $f->Name; 139 | else $ret .= "func_".dechex($o->value); 140 | break; 141 | } 142 | } else { 143 | if ($f->Name != "") $ret .= $f->Name; 144 | else $ret .= "func_".dechex($o->value); 145 | } 146 | } else { 147 | if ($f->Flags & 2) $ret .= $f->FExportName; 148 | else $ret .= "func_".dechex($o->value); 149 | } 150 | } 151 | break; 152 | default: 153 | $ret .= $o->value; 154 | break; 155 | } 156 | $ret .= " )"; 157 | return $ret; 158 | case 2: 159 | return $o->var->type.($o->var->index != -1 ? $o->var->index : '')."[".$o->index."]"; 160 | case 3: 161 | return $o->var->type.($o->var->index != -1 ? $o->var->index : '')."[".$o->index->type.($o->index->index != -1 ? $o->index->index : '')."]"; 162 | default: 163 | return "UnknownOType".$o->otype; 164 | } 165 | } 166 | 167 | public function DumpOperands($o,$sep = ", ") { 168 | $ret = array(); 169 | foreach ($o as $op) $ret[] = $this->DumpOperand($op); 170 | return implode($sep,$ret); 171 | } 172 | 173 | public function DisasmFunc($bytecode) { 174 | $ret = ""; 175 | foreach ($bytecode as $inst) { 176 | if ($inst->jumptarget) $ret .= "\tloc_".dechex($inst->offset).":\r\n"; 177 | $ret .= "\t\t"; 178 | switch ($inst->opcode) { 179 | case IFPSOpcodes::Assign: 180 | $ret .= "assign ".$this->DumpOperands($inst->operands); 181 | break; 182 | case IFPSOpcodes::Calculate: 183 | $ret .= "calculate ".$this->DumpOperands($inst->operands," "); 184 | break; 185 | case IFPSOpcodes::Push: 186 | $ret .= "push ".$this->DumpOperands($inst->operands); 187 | break; 188 | case IFPSOpcodes::PushVar: 189 | $ret .= "pushvar ".$this->DumpOperands($inst->operands); 190 | break; 191 | case IFPSOpcodes::Pop: 192 | $ret .= "pop"; 193 | break; 194 | case IFPSOpcodes::Call: 195 | $ret .= "call "; 196 | if (!array_key_exists($inst->operands[0],$this->ifps->funcs)) $ret .= "func_".dechex($inst->operands[0]); 197 | else { 198 | $f = $this->ifps->funcs[$inst->operands[0]]; 199 | if ($f->Flags & 1) { 200 | if ($f->Flags & 2) { 201 | switch ($f->FDecl->type) { 202 | case 0: 203 | $ret .= $f->FDecl->dll."!".$f->FDecl->func; 204 | break; 205 | case 1: 206 | $ret .= $f->FDecl->classname."->".$f->FDecl->func; 207 | break; 208 | case 2: 209 | if ($f->Name != "") $ret .= $f->Name; 210 | else $ret .= "func_".dechex($inst->operands[0]); 211 | break; 212 | } 213 | } else { 214 | if ($f->Name != "") $ret .= $f->Name; 215 | else $ret .= "func_".dechex($inst->operands[0]); 216 | } 217 | } else { 218 | if ($f->Flags & 2) $ret .= $f->FExportName; 219 | else $ret .= "func_".dechex($inst->operands[0]); 220 | } 221 | } 222 | break; 223 | case IFPSOpcodes::Jump: 224 | $ret .= "jump loc_".dechex($inst->operands[0]); 225 | break; 226 | case IFPSOpcodes::JumpTrue: 227 | $ret .= "jumptrue ".$this->DumpOperand($inst->operands[1]).", loc_".dechex($inst->operands[0]); 228 | break; 229 | case IFPSOpcodes::JumpFalse: 230 | $ret .= "jumpfalse ".$this->DumpOperand($inst->operands[1]).", loc_".dechex($inst->operands[0]); 231 | break; 232 | case IFPSOpcodes::Ret: 233 | $ret .= "ret"; 234 | break; 235 | case IFPSOpcodes::SetStackType: 236 | $ret .= "setstacktype ".$inst->operands[0]->type.$inst->operands[0].index." ".$inst->operands[1]; 237 | break; 238 | case IFPSOpcodes::PushType: 239 | $ret .= "pushtype "; 240 | if (!array_key_exists($inst->operands[0],$this->ifps->types)) $ret .= "type_".dechex($inst->operands[0]); 241 | else { 242 | $t = $this->ifps->types[$inst->operands[0]]; 243 | if (property_exists($t,"ExportName")) $ret .= $t->ExportName; 244 | else $ret .= $this->DumpType($t); 245 | } 246 | break; 247 | case IFPSOpcodes::Compare: 248 | $ret .= "compare ".$this->DumpOperand($inst->operands[0]). 249 | ", ".$this->DumpOperand($inst->operands[1]). 250 | " ".$inst->operands[2]. 251 | " "; 252 | if ($inst->operands[2] == "is") { 253 | // if the comparison type is "is", then operand3 is a type, so dump it accordingly 254 | $oval = $inst->operands[3]->value; 255 | if (!array_key_exists($oval,$this->ifps->types)) $ret .= "type_".dechex($oval); 256 | else { 257 | $t = $this->ifps->types[$oval]; 258 | if (property_exists($t,"ExportName")) $ret .= $t->ExportName; 259 | else $ret .= $this->DumpType($t); 260 | } 261 | } else { 262 | // otherwise, just dump the operand. 263 | $ret .= $this->DumpOperand($inst->operands[3]); 264 | } 265 | break; 266 | case IFPSOpcodes::CallVar: 267 | $ret .= "callvar ".$this->DumpOperands($inst->operands); 268 | break; 269 | case IFPSOpcodes::SetPtr: 270 | $ret .= "setptr ".$this->DumpOperands($inst->operands); 271 | break; 272 | case IFPSOpcodes::LogicalNot: 273 | $ret .= "logicalnot ".$this->DumpOperands($inst->operands); 274 | break; 275 | case IFPSOpcodes::Neg: 276 | $ret .= "neg ".$this->DumpOperands($inst->operands); 277 | break; 278 | case IFPSOpcodes::SetFlag: 279 | $ret .= "setflag ".($inst->operands[1]?"not ":"").$this->DumpOperand($inst->operands[0]); 280 | break; 281 | case IFPSOpcodes::JumpFlag: 282 | $ret .= "jumpflag loc_".dechex($inst->operands[0]); 283 | break; 284 | case IFPSOpcodes::PushEH: 285 | $ret .= "pusheh ".$this->DumpOperands($inst->operands); 286 | break; 287 | case IFPSOpcodes::PopEH: 288 | $ret .= "popeh ".$this->DumpOperands($inst->operands); 289 | break; 290 | case IFPSOpcodes::Not: 291 | $ret .= "not ".$this->DumpOperands($inst->operands); 292 | break; 293 | case IFPSOpcodes::SetCopyPointer: 294 | $ret .= "setcopypointer ".$this->DumpOperands($inst->operands); 295 | break; 296 | case IFPSOpcodes::Inc: 297 | $ret .= "inc ".$this->DumpOperands($inst->operands); 298 | break; 299 | case IFPSOpcodes::Dec: 300 | $ret .= "dec ".$this->DumpOperands($inst->operands); 301 | break; 302 | case IFPSOpcodes::PopJump: 303 | $ret .= "popjump loc_".dechex($inst->operands[0]); 304 | break; 305 | case IFPSOpcodes::PopPopJump: 306 | $ret .= "poppopjump loc_".dechex($inst->operands[0]); 307 | break; 308 | case IFPSOpcodes::Nop: 309 | $ret .= "nop"; 310 | break; 311 | default: 312 | throw new Exception("Unknown opcode: 0x".dechex($inst->opcode)); 313 | } 314 | if (in_array($inst->opcode,array(IFPSOpcodes::Push,IFPSOpcodes::PushVar,IFPSOpcodes::Pop,IFPSOpcodes::PushType,IFPSOpcodes::PopJump,IFPSOpcodes::PopPopJump))) 315 | $ret .= " ; StackCount = ".$inst->stackcount; 316 | $ret .= "\r\n"; 317 | } 318 | return $ret; 319 | } 320 | 321 | public function DumpDisasm() { 322 | $ret = ""; 323 | $i = 0; 324 | foreach ($this->ifps->funcs as $f) { 325 | $isVoid = false; 326 | $ret .= "Functions[".($i++)."] = "; 327 | if ($f->Flags & 1) { 328 | $ret .= "external "; 329 | if ($f->Flags & 2) { 330 | switch ($f->FDecl->type) { 331 | case 0: 332 | $ret .= $f->FDecl->CallingConv." "; 333 | if ($f->FDecl->DelayLoad) $ret .= "delayload "; 334 | if ($f->FDecl->LoadWithAlteredSearchPath) $ret .= "loadwithalteredsearchpath "; 335 | if ($f->FDecl->isVoid) $ret .= "void "; 336 | else $ret .= "returnsval "; 337 | $ret .= $f->FDecl->dll."!".$f->FDecl->func."("; 338 | $pi = 1; 339 | $args = ""; 340 | foreach ($f->FDecl->params as $fp) { 341 | if ($fp->modeIn) $args .= "in "; 342 | else $args .= "out "; 343 | $args .= "Arg".($pi++).","; 344 | } 345 | $args = substr($args,0,-1); 346 | $ret .= $args.")\r\n"; 347 | break; 348 | case 1: 349 | $ret .= $f->FDecl->CallingConv." "; 350 | if ($f->FDecl->isVoid) $ret .= "void "; 351 | else $ret .= "returnsval "; 352 | $ret .= $f->FDecl->classname."->".$f->FDecl->func."("; 353 | $pi = 1; 354 | $args = ""; 355 | foreach ($f->FDecl->params as $fp) { 356 | if ($fp->modeIn) $args .= "in "; 357 | else $args .= "out "; 358 | $args .= "Arg".($pi++).","; 359 | } 360 | $args = substr($args,0,-1); 361 | $ret .= $args.")\r\n"; 362 | break; 363 | case 2: 364 | if ($f->FDecl->isVoid) $ret .= "void "; 365 | else $ret .= "returnsval "; 366 | if ($f->Name != "") $ret .= $f->Name; 367 | else $ret .= "func_".dechex($inst->operands[0]); 368 | $pi = 1; 369 | $args = ""; 370 | $ret .= '('; 371 | foreach ($f->FDecl->params as $fp) { 372 | if ($fp->modeIn) $args .= "in "; 373 | else $args .= "out "; 374 | $args .= "Arg".($pi++).","; 375 | } 376 | $args = substr($args,0,-1); 377 | $ret .= $args.")\r\n"; 378 | break; 379 | } 380 | } else { 381 | if ($f->Name != "") $ret .= $f->Name; 382 | else $ret .= "func_".dechex($inst->operands[0])."()\r\n"; 383 | } 384 | } else { 385 | if ($f->Flags & 2) { 386 | $ret .= "exported "; 387 | if ($f->FExportDecl->isVoid) $ret .= "void "; 388 | else { 389 | if (property_exists($f->FExportDecl->ReturnType,"ExportName")) $ret .= $f->FExportDecl->ReturnType->ExportName; 390 | else $ret .= $this->DumpType($f->FExportDecl->ReturnType); 391 | $ret .= " "; 392 | } 393 | $ret .= $f->FExportName."("; 394 | $pi = 1; 395 | $args = ""; 396 | foreach ($f->FExportDecl->params as $fp) { 397 | if ($fp->modeIn) $args .= "in "; 398 | else $args .= "out "; 399 | if (property_exists($fp->type,"ExportName")) $args .= $fp->type->ExportName; 400 | else $args .= $this->DumpType($fp->type); 401 | $args .= " Arg".($pi++).","; 402 | } 403 | $args = substr($args,0,-1); 404 | $ret .= $args.")\r\n"; 405 | } else { 406 | $ret .= "func_".dechex($inst->operands[0])."()\r\n"; 407 | } 408 | } 409 | 410 | if (property_exists($f,"FData")) $ret .= $this->DisasmFunc($f->FData); 411 | $ret .= "\r\n"; 412 | } 413 | return $ret; 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /IFPS.php: -------------------------------------------------------------------------------- 1 | array(),'exported'=>array()); 15 | 16 | const LowestSupportedVersion = 12; 17 | const HighestSupportedVersion = 23; 18 | 19 | private function __construct() { } 20 | 21 | public static function LoadFile($file) { 22 | return self::Load(file_get_contents($file)); 23 | } 24 | 25 | public static function Load($data) { 26 | $origdata = $data; 27 | $obj = new static(); 28 | // get the header 29 | $obj->LoadHeader($data); 30 | $data = substr($data,28); 31 | if (DEBUG) file_put_contents("CodeCurrParse.bin",$data); 32 | // load types 33 | $data = substr($data,$obj->LoadTypes($data)); 34 | if (DEBUG) file_put_contents("CodeCurrParse.bin",$data); 35 | // load functions 36 | $data = substr($data,$obj->LoadFuncs($data,$origdata)); 37 | if (DEBUG) file_put_contents("CodeCurrParse.bin",$data); 38 | // load global / exported vars 39 | $obj->vars = (object)$obj->vars; 40 | $data = substr($data,$obj->LoadVars($data)); 41 | if (DEBUG) file_put_contents("CodeCurrParse.bin",$data); 42 | if (($obj->header->funcs < $obj->header->entrypoint) && ($obj->header->entrypoint != 0xffffffff)) { 43 | // invalid entrypoint 44 | // this is a hard fail in the original loader, I don't see an issue here? 45 | } 46 | return $obj; 47 | } 48 | 49 | private function LoadHeader($data) { 50 | if (strlen($data) < 28) throw new Exception("Reached end of file"); 51 | $header = (object) unpack("Vmagic/Vversion/Vtypes/Vfuncs/Vvars/Ventrypoint/Vimportsize",$data); 52 | $header->magic = substr($data,0,4); 53 | if ($header->magic != "IFPS") { 54 | throw new Exception("Got incorrect magic: 0x".$hexmagic); 55 | } 56 | if (($header->version < self::LowestSupportedVersion) || ($header->version > self::HighestSupportedVersion)) { 57 | throw new Exception("This IFPS file uses unsupported version ".$header->version); 58 | } 59 | $this->header = $header; 60 | } 61 | 62 | private function LoadTypes($data) { 63 | $offset = 0; 64 | $datalen = strlen($data); 65 | for ($i = 0; $i < $this->header->types; $i++) { 66 | if (DEBUG) echo "[TYPE] Offset: 0x".dechex($offset)."\r\n"; 67 | if ($datalen < $offset + 1) throw new Exception("Reached end of file"); 68 | $basetype = current(unpack('C',substr($data,$offset,1))); 69 | $offset++; 70 | $fe = false; 71 | if ($basetype & 0x80) { 72 | $fe = true; 73 | $basetype -= 0x80; 74 | } 75 | switch ($basetype) { 76 | case IFPSTypes::S64: 77 | case IFPSTypes::U8: 78 | case IFPSTypes::S8: 79 | case IFPSTypes::U16: 80 | case IFPSTypes::S16: 81 | case IFPSTypes::U32: 82 | case IFPSTypes::S32: 83 | case IFPSTypes::Single: 84 | case IFPSTypes::Double: 85 | case IFPSTypes::Currency: 86 | case IFPSTypes::Extended: 87 | case IFPSTypes::String: 88 | case IFPSTypes::Pointer: 89 | case IFPSTypes::PChar: 90 | case IFPSTypes::Variant: 91 | case IFPSTypes::Char: 92 | case IFPSTypes::UnicodeString: 93 | case IFPSTypes::WideString: 94 | case IFPSTypes::WideChar: 95 | $curr = (object) array("BaseType" => $basetype); 96 | break; 97 | case IFPSTypes::_Class: 98 | $curr = (object) array("BaseType" => $basetype); 99 | $d = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 100 | if ($d > 255) throw new Exception("Obtained unexpected data: expected <= 0xff, got 0x".dechex($d)); 101 | else if ($datalen < $offset + $d) throw new Exception("Reached end of file"); 102 | $curr->FCN = substr($data,$offset,$d); 103 | $offset += $d; 104 | break; 105 | case IFPSTypes::ProcPtr: 106 | $curr = (object) array("BaseType" => $basetype); 107 | $d = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 108 | if ($d > 255) throw new Exception("Obtained unexpected data: expected <= 0xff, got 0x".dechex($d)); 109 | else if ($datalen < $offset + $d) throw new Exception("Reached end of file"); 110 | $curr->FParamInfo = substr($data,$offset,$d); 111 | $offset += $d; 112 | break; 113 | case IFPSTypes::_Interface: 114 | $curr = (object) array("BaseType" => $basetype); 115 | if ($datalen < $offset + 16) throw new Exception("Reached end of file"); 116 | $guid = (object) unpack("VD1/vD2/vD3",substr($data,$offset,8)); 117 | $offset += 8; 118 | $guid->D4 = array(); 119 | for ($gi = 0; $gi < 8; $gi++) { 120 | $guid->D4[] = current(unpack('C',substr($data,$offset,1))); 121 | $offset++; 122 | } 123 | $curr->FGUID = $guid; 124 | break; 125 | case IFPSTypes::Set: 126 | $curr = (object) array("BaseType" => $basetype); 127 | $d = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 128 | if ($d > 256) throw new Exception("Type mismatch: expected <= 0x100, got 0x".dechex($d)); 129 | $curr->aBitSize = $d; 130 | $curr->aByteSize = $d >> 3; 131 | if ($d & 7) $curr->aByteSize++; 132 | break; 133 | case IFPSTypes::StaticArray: 134 | $curr = (object) array("BaseType" => $basetype); 135 | $d = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 136 | if ($d >= count($this->types)) throw new Exception("Type mismatch; type offset greater than currently known about types"); 137 | $curr->ArrayType = $this->types[$d]; 138 | $d = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 139 | if ($d > 0x3fffffff) /* 0xffffffff / 4 */ throw new Exception("Reached end of file"); 140 | $curr->Size = $d; 141 | if ($this->header->version > 22) { 142 | $d = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 143 | $curr->StartOffset = $d; 144 | } 145 | break; 146 | case IFPSTypes::_Array: 147 | $curr = (object) array("BaseType" => $basetype); 148 | $d = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 149 | if ($d >= count($this->types)) throw new Exception("Type offset out of range"); 150 | $curr->ArrayType = $this->types[$d]; 151 | break; 152 | case IFPSTypes::Record; 153 | $curr = (object) array("BaseType" => $basetype); 154 | $curr->FFieldTypes = array(); 155 | $fieldtypenum = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 156 | for ($d = 0; $d < $fieldtypenum; $d++) { 157 | $l2 = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 158 | if ($l2 >= count($this->types)) throw new Exception("Type offset out of range"); 159 | $curr->FFieldTypes[] = $this->types[$l2]; 160 | } 161 | break; 162 | default: 163 | throw new Exception("Invalid type 0x".dechex($basetype)." (offset: 0x".dechex(28 + $offset).")"); 164 | break; 165 | } 166 | if ($fe) { 167 | $d = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 168 | if ($d > 0x40000000) throw new Exception("Invalid type"); 169 | else if ($datalen < $offset + $d) throw new Exception("Reached end of file"); 170 | $curr->ExportName = substr($data,$offset,$d); 171 | $offset += $d; 172 | $curr->ExportNameHash = IFPSGlobals::MakeHash($curr->ExportName); 173 | } 174 | switch ($basetype) { 175 | case IFPSTypes::Variant: 176 | $curr->FRealSize = 16; // sizeof(Variant) 177 | break; 178 | case IFPSTypes::Char: 179 | case IFPSTypes::S8: 180 | case IFPSTypes::U8: 181 | $curr->FRealSize = 1; 182 | break; 183 | case IFPSTypes::WideChar: 184 | case IFPSTypes::S16: 185 | case IFPSTypes::U16: 186 | $curr->FRealSize = 2; 187 | break; 188 | case IFPSTypes::WideString: 189 | case IFPSTypes::UnicodeString: 190 | case IFPSTypes::_Interface: 191 | case IFPSTypes::_Class: 192 | case IFPSTypes::PChar: 193 | case IFPSTypes::String: 194 | $curr->FRealSize = 4; 195 | break; 196 | case IFPSTypes::Single: 197 | case IFPSTypes::S32: 198 | case IFPSTypes::U32: 199 | $curr->FRealSize = 4; 200 | break; 201 | case IFPSTypes::ProcPtr: 202 | $curr->FRealSize = 2*4 + 4; 203 | break; 204 | case IFPSTypes::Currency: 205 | $curr->FRealSize = 8; // sizeof(Currency) 206 | break; 207 | case IFPSTypes::Pointer: 208 | $curr->FRealSize = 2*4 + 4; 209 | break; 210 | case IFPSTypes::Double: 211 | case IFPSTypes::S64: 212 | $curr->FRealSize = 8; 213 | break; 214 | case IFPSTypes::Extended: 215 | $curr->FRealSize = 10; // sizeof(Extended) 216 | break; 217 | case IFPSTypes::ReturnAddress: 218 | $curr->FRealSize = 28; // sizeof(TBTReturnAddress) 219 | break; 220 | default: 221 | $curr->FRealSize = 0; 222 | break; 223 | } 224 | $this->types[] = $curr; 225 | if ($this->header->version >= 21) { 226 | // load attributes 227 | $curr->Attributes = $this->LoadAttributes($data,$datalen,$offset); 228 | $this->types[$i] = $curr; 229 | } 230 | } 231 | 232 | return $offset; 233 | } 234 | 235 | private function LoadAttributes($data,$datalen,&$offset) { 236 | $ret = array(); 237 | $attribcount = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 238 | for ($ai = 0; $ai < $attribcount; $ai++) { 239 | if (DEBUG) echo "[ATT] Count: ".$attribcount." - Offset: 0x".dechex($offset)."\r\n"; 240 | $namelen = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 241 | if ($datalen < $offset + $namelen) throw new Exception("Reached end of file"); 242 | $name = substr($data,$offset,$namelen); 243 | $offset += $namelen; 244 | $fieldcount = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 245 | $att = (object)array(); 246 | $att->AttribType = $name; 247 | $att->AttribTypeHash = IFPSGlobals::MakeHash($name); 248 | for ($fi = 0; $fi < $fieldcount; $fi++) { 249 | $typeno = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 250 | if ($typeno > count($this->types)) throw new Exception("Type offset out of range"); 251 | $varp = $this->types[$typeno]; 252 | switch ($varp->BaseType) { 253 | case IFPSTypes::Set: 254 | if ($datalen < $offset + $varp->aByteSize) throw new Exception("Reached end of file"); 255 | $att->Data = substr($data,$offst,$varp->aByteSize); 256 | $offset += $varp->aByteSize; 257 | break; 258 | case IFPSTypes::S8: 259 | case IFPSTypes::Char: 260 | case IFPSTypes::U8: 261 | if ($datalen < $offset + 1) throw new Exception("Reached end of file"); 262 | switch ($varp->BaseType) { 263 | case IFPSTypes::S8: 264 | $att->Data = current(unpack('c',substr($data,$offset,1))); 265 | break; 266 | case IFPSTypes::U8: 267 | $att->Data = current(unpack('C',substr($data,$offset,1))); 268 | break; 269 | case IFPSTypes::Char: 270 | $att->Data = substr($data,$offset,1); 271 | break; 272 | } 273 | $offset += 1; 274 | break; 275 | case IFPSTypes::S16: 276 | case IFPSTypes::WideChar: 277 | case IFPSTypes::U16: 278 | if ($datalen < $offset + 2) throw new Exception("Reached end of file"); 279 | switch ($varp->BaseType) { 280 | case IFPSTypes::S16: 281 | $att->Data = current(unpack('v',substr($data,$offset,2))); 282 | // no pack() format char for signed little endian int16, so convert from unsigned to signed ourselves 283 | if ($att->Data >= 0x8000) $att->Data -= 0x10000; 284 | break; 285 | case IFPSTypes::U16: 286 | $att->Data = current(unpack('v',substr($data,$offset,2))); 287 | break; 288 | case IFPSTypes::WideChar: 289 | $att->Data = mb_convert_encoding(substr($data,$offset,2),'utf-8','utf-16le'); 290 | break; 291 | } 292 | $offset += 2; 293 | break; 294 | case IFPSTypes::S32: 295 | case IFPSTypes::U32: 296 | $att->Data = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 297 | if ($varp->BaseType == IFPSTypes::S32) { 298 | // no pack() format char for signed little endian int32, so convert from unsigned to signed ourselves 299 | if ($att->Data >= 0x80000000) $att->Data -= 0x100000000; 300 | } 301 | break; 302 | case IFPSTypes::ProcPtr: 303 | $att->Data = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 304 | if ($att->Data == 0) { 305 | $att->Ptr = $att->Self = null; 306 | } 307 | break; 308 | case IFPSTypes::S64: 309 | if ($datalen < $offset + 8) throw new Exception("Reached end of file"); 310 | $att->Data = current(unpack('P',substr($data,$offset,8))); 311 | if ($att->Data >= 0x8000000000000000) $att->Data -= 0x10000000000000000; 312 | $offset += 8; 313 | break; 314 | case IFPSTypes::Single: 315 | $att->Data = IFPSGlobals::hex2float(str_pad(dechex(IFPSGlobals::ReadUInt32($data,$datalen,$offset)),8,'0',STR_PAD_LEFT)); 316 | break; 317 | case IFPSTypes::Double: 318 | if ($datalen < $offset + 8) throw new Exception("Reached end of file"); 319 | if (IFPSGlobals::isLittleEndian()) { 320 | $att->Data = current(unpack('d',substr($data,$offset,8))); 321 | } else { 322 | // swap endianness, yayhaxx 323 | $att->Data = current(unpack('d',pack('Q',current(unpack('P',substr($data,$offset,8)))))); 324 | } 325 | $offset += 8; 326 | break; 327 | case IFPSTypes::Extended: 328 | // this probably won't work, but still. 329 | if ($datalen < $offset + 10) throw new Exception("Reached end of file"); 330 | $att->Data = IFPSGlobals::hex_extended2float(current(unpack('H*',substr($data,$offset,10)))); 331 | $offset += 10; 332 | break; 333 | case IFPSTypes::Currency: 334 | if ($datalen < $offset + 8) throw new Exception("Reached end of file"); 335 | $att->Data = current(unpack('P',substr($data,$offset,8))) / 10000; 336 | $offset += 8; 337 | break; 338 | case IFPSTypes::PChar: 339 | case IFPSTypes::String: 340 | $namelen = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 341 | if ($datalen < $offset + $namelen) throw new Exception("Reached end of file"); 342 | $att->Data = substr($data,$offset,$namelen); 343 | $offset += $namelen; 344 | break; 345 | case IFPSTypes::WideString: 346 | case IFPSTypes::UnicodeString: 347 | $namelen = IFPSGlobals::ReadUInt32($data,$datalen,$offset) * 2; 348 | if ($datalen < $offset + $namelen) throw new Exception("Reached end of file"); 349 | $att->Data = mb_convert_encoding(substr($data,$offset,$namelen),'utf-8','utf-16le'); 350 | $offset += $namelen; 351 | break; 352 | default: 353 | throw new Exception("Invalid type"); 354 | break; 355 | } 356 | $ret[] = $att; 357 | } 358 | } 359 | return $ret; 360 | } 361 | 362 | private function LoadFuncs($data,$origdata) { 363 | $offset = 0; 364 | $datalen = strlen($data); 365 | $origdatalen = strlen($origdata); 366 | for ($i = 0; $i < $this->header->funcs; $i++) { 367 | if (DEBUG) echo "[FUNC] Offset: 0x".dechex($offset)."\r\n"; 368 | if ($datalen < $offset + 1) throw new Exception("Reached end of file"); 369 | $curr = (object) array('Flags' => current(unpack('C',substr($data,$offset,1))) ); 370 | $offset++; 371 | if ($curr->Flags & 1) { 372 | // external (imported) 373 | if ($datalen < $offset + 1) throw new Exception("Reached end of file"); 374 | $namelen = current(unpack('C',substr($data,$offset,1))); 375 | $offset++; 376 | if ($datalen < $offset + $namelen) throw new Exception("Reached end of file"); 377 | $curr->Name = substr($data,$offset,$namelen); 378 | $offset += $namelen; 379 | if (($curr->Flags & 3) == 3) { 380 | $l2 = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 381 | if ($datalen < $offset + $l2) throw new Exception("Reached end of file"); 382 | $curr->FDecl = IFPSGlobals::ParseFDecl(substr($data,$offset,$l2)); 383 | $offset += $l2; 384 | } 385 | } else { 386 | // VM bytecode function 387 | $l2 = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 388 | $l3 = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 389 | if (($origdatalen < $l2 + $l3) || (!$l3)) throw new Exception("Reached end of file"); 390 | $curr->FData = substr($origdata,$l2,$l3); 391 | $curr->FLength = $l3; 392 | IFPSGlobals::$isVoid = false; 393 | if ($curr->Flags & 2) { 394 | // exported function 395 | $l3 = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 396 | if ($datalen < $offset + $l3) throw new Exception("Reached end of file"); 397 | $curr->FExportName = substr($data,$offset,$l3); 398 | $offset += $l3; 399 | $l3 = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 400 | if ($datalen < $offset + $l3) throw new Exception("Reached end of file"); 401 | $curr->FExportDecl = $this->ParseExportDecl(substr($data,$offset,$l3)); 402 | IFPSGlobals::$isVoid = $curr->FExportDecl->isVoid; 403 | $offset += $l3; 404 | $curr->FExportNameHash = IFPSGlobals::MakeHash($curr->FExportName); 405 | } 406 | $curr->FData = $this->ParseBytecode($curr->FData); 407 | } 408 | if ($curr->Flags & 4) { 409 | $curr->Attributes = $this->LoadAttributes($data,$datalen,$offset); 410 | } 411 | $this->funcs[] = $curr; 412 | } 413 | return $offset; 414 | } 415 | 416 | private function LoadVars($data) { 417 | $offset = 0; 418 | $datalen = strlen($data); 419 | for ($i = 0; $i < $this->header->vars; $i++) { 420 | if (DEBUG) echo "[VAR] Offset: 0x".dechex($offset)."\r\n"; 421 | if ($datalen < $offset + 5) throw new Exception("Reached end of file"); 422 | $rec = (object) unpack("VTypeNo/CFlags",substr($data,$offset,5)); 423 | $offset += 5; 424 | if (($rec->TypeNo > $this->header->types) || (!array_key_exists($rec->TypeNo,$this->types))) throw new Exception("Invalid type"); 425 | $this->vars->globals[] = (object) array('type'=>$this->types[$rec->TypeNo]); 426 | if ($rec->Flags & 1) { 427 | // exported 428 | $n = IFPSGlobals::ReadUInt32($data,$datalen,$offset); 429 | $e = (object) array(); 430 | if ($datalen < $offset + $n) throw new Exception("Reached end of file"); 431 | $e->FName = substr($data,$offset,$n); 432 | $offset += $n; 433 | $e->FNameHash = IFPSGlobals::MakeHash($e->FName); 434 | $e->FVarNo = $i; 435 | $this->vars->exported[] = $e; 436 | } 437 | } 438 | return $offset; 439 | } 440 | 441 | private function ParseOperand($bc,$bclen,&$offset) { 442 | if ($bclen < $offset + 1) return array(); 443 | $vartype = current(unpack('C',substr($bc,$offset++,1))); 444 | switch ($vartype) { 445 | case 0: 446 | return (object)array("otype"=>$vartype,"var"=>IFPSGlobals::ParseVar(IFPSGlobals::ReadUInt32($bc,$bclen,$offset))); 447 | case 1: 448 | $type = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 449 | if (!array_key_exists($type,$this->types)) throw new Exception("Type offset out of range"); 450 | $type = $this->types[$type]; 451 | $ret = (object)array("otype"=>$vartype,"type"=>$type); 452 | switch ($type->BaseType) { 453 | case IFPSTypes::Set: 454 | if ($bclen < $offset + $type->aByteSize) throw new Exception("Reached end of file"); 455 | $data = substr($bc,$offset,$type->aByteSize); 456 | $offset += $type->aByteSize; 457 | $ret->value = $data; 458 | return $ret; 459 | case IFPSTypes::S8: 460 | case IFPSTypes::Char: 461 | case IFPSTypes::U8: 462 | if ($bclen < $offset + 1) throw new Exception("Reached end of file"); 463 | switch ($type->BaseType) { 464 | case IFPSTypes::S8: 465 | $ret->value = current(unpack('c',substr($bc,$offset,1))); 466 | break; 467 | case IFPSTypes::U8: 468 | $ret->value = current(unpack('C',substr($bc,$offset,1))); 469 | break; 470 | case IFPSTypes::Char: 471 | $ret->value = substr($bc,$offset,1); 472 | break; 473 | } 474 | $offset += 1; 475 | return $ret; 476 | case IFPSTypes::S16: 477 | case IFPSTypes::WideChar: 478 | case IFPSTypes::U16: 479 | if ($bclen < $offset + 2) throw new Exception("Reached end of file"); 480 | switch ($type->BaseType) { 481 | case IFPSTypes::S16: 482 | $ret->value = current(unpack('v',substr($bc,$offset,2))); 483 | // no pack() format char for signed little endian int16, so convert from unsigned to signed ourselves 484 | if ($ret->value >= 0x8000) $ret->value -= 0x10000; 485 | break; 486 | case IFPSTypes::U16: 487 | $ret->value = current(unpack('v',substr($bc,$offset,2))); 488 | break; 489 | case IFPSTypes::WideChar: 490 | $ret->value = mb_convert_encoding(substr($bc,$offset,2),'utf-8','utf-16le'); 491 | break; 492 | } 493 | $offset += 2; 494 | return $ret; 495 | case IFPSTypes::S32: 496 | case IFPSTypes::U32: 497 | $ret->value = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 498 | if ($type->BaseType == IFPSTypes::S32) { 499 | // no pack() format char for signed little endian int32, so convert from unsigned to signed ourselves 500 | if ($ret->value >= 0x80000000) $ret->value -= 0x100000000; 501 | } 502 | return $ret; 503 | case IFPSTypes::ProcPtr: 504 | $ret->value = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 505 | if ($ret->value == 0) { 506 | $att->Ptr = $att->Self = null; 507 | } 508 | return $ret; 509 | case IFPSTypes::S64: 510 | if ($bclen < $offset + 8) throw new Exception("Reached end of file"); 511 | $ret->value = current(unpack('P',substr($bc,$offset,8))); 512 | if ($ret->value >= 0x8000000000000000) $ret->value -= 0x10000000000000000; 513 | $offset += 8; 514 | return $ret; 515 | case IFPSTypes::Single: 516 | $ret->value = IFPSGlobals::hex2float(str_pad(dechex(IFPSGlobals::ReadUInt32($bc,$bclen,$offset)),8,'0',STR_PAD_LEFT)); 517 | return $ret; 518 | case IFPSTypes::Double: 519 | if ($bclen < $offset + 8) throw new Exception("Reached end of file"); 520 | if (IFPSGlobals::isLittleEndian()) { 521 | $ret->value = current(unpack('d',substr($bc,$offset,8))); 522 | } else { 523 | // swap endianness, yayhaxx 524 | $ret->value = current(unpack('d',pack('Q',current(unpack('P',substr($bc,$offset,8)))))); 525 | } 526 | $offset += 8; 527 | return $ret; 528 | case IFPSTypes::Extended: 529 | // BUGBUG: this does not work, and probably there's no way to make it work. 530 | if ($bclen < $offset + 10) throw new Exception("Reached end of file"); 531 | $ret->value = IFPSGlobals::hex_extended2float(current(unpack('H*',substr($bc,$offset,10)))); 532 | $offset += 10; 533 | return $ret; 534 | case IFPSTypes::Currency: 535 | if ($bclen < $offset + 8) throw new Exception("Reached end of file"); 536 | $ret->value = current(unpack('P',substr($bc,$offset,8))) / 10000; 537 | $offset += 8; 538 | return $ret; 539 | case IFPSTypes::PChar: 540 | case IFPSTypes::String: 541 | $namelen = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 542 | if ($bclen < $offset + $namelen) throw new Exception("Reached end of file"); 543 | $ret->value = substr($bc,$offset,$namelen); 544 | $offset += $namelen; 545 | return $ret; 546 | case IFPSTypes::WideString: 547 | case IFPSTypes::UnicodeString: 548 | $namelen = IFPSGlobals::ReadUInt32($bc,$bclen,$offset) * 2; 549 | if ($bclen < $offset + $namelen) throw new Exception("Reached end of file"); 550 | $ret->value = mb_convert_encoding(substr($bc,$offset,$namelen),'utf-8','utf-16le'); 551 | $offset += $namelen; 552 | return $ret; 553 | default: 554 | throw new Exception("Invalid type"); 555 | break; 556 | } 557 | case 2: 558 | return (object)array("otype"=>$vartype,"var"=>IFPSGlobals::ParseVar(IFPSGlobals::ReadUInt32($bc,$bclen,$offset)),"index"=>IFPSGlobals::ReadUInt32($bc,$bclen,$offset)); 559 | case 3: 560 | return (object)array("otype"=>$vartype,"var"=>IFPSGlobals::ParseVar(IFPSGlobals::ReadUInt32($bc,$bclen,$offset)),"index"=>IFPSGlobals::ParseVar(IFPSGlobals::ReadUInt32($bc,$bclen,$offset))); 561 | default: 562 | throw new Exception("Unhandled operand"); 563 | } 564 | } 565 | 566 | private function ParseBytecode($bc) { 567 | $ret = array(); 568 | $offset = 0; 569 | $bclen = strlen($bc); 570 | $StackCount = 0; 571 | if (DEBUG) file_put_contents("curr_bytecode.bin",$bc); 572 | do { 573 | if (DEBUG) echo "[Bytecode] Offset: 0x".dechex($offset)."\r\n"; 574 | if ($bclen < $offset + 1) throw new Exception("Reached end of bytecode"); 575 | $curr = (object) array( "offset" => $offset, "opcode" => current(unpack('C',substr($bc,$offset,1))), "operands"=>array(), "jumptarget"=>false ); 576 | $offset++; 577 | switch ($curr->opcode) { 578 | case IFPSOpcodes::Assign: 579 | for ($i = 0; $i < 2; $i++) { 580 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 581 | } 582 | break; 583 | case IFPSOpcodes::Calculate: 584 | if ($bclen < $offset + 1) throw new Exception("Reached end of bytecode"); 585 | $cop = current(unpack('C',substr($bc,$offset++,1))); 586 | $cops = array("+","-","*","/","%","<<",">>","&","|","^"); 587 | if (!array_key_exists($cop,$cops)) throw new Exception("Unhandled Calculate operand"); 588 | $cop = $cops[$cop]; 589 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 590 | $curr->operands[] = $cop; 591 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 592 | break; 593 | case IFPSOpcodes::Push: 594 | $StackCount++; 595 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 596 | break; 597 | case IFPSOpcodes::PushVar: 598 | $StackCount++; 599 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 600 | break; 601 | case IFPSOpcodes::Pop: 602 | $StackCount--; 603 | break; 604 | case IFPSOpcodes::Call: 605 | $curr->operands[] = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 606 | break; 607 | case IFPSOpcodes::Jump: 608 | $jumploc = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 609 | if ($jumploc >= 0x80000000) $jumploc -= 0x100000000; 610 | $curr->operands[] = $jumploc + $offset; 611 | break; 612 | case IFPSOpcodes::JumpTrue: 613 | $jumploc = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 614 | if ($jumploc >= 0x80000000) $jumploc -= 0x100000000; 615 | $op2 = $this->ParseOperand($bc,$bclen,$offset); 616 | $curr->operands[] = $jumploc + $offset; 617 | $curr->operands[] = $op2; 618 | break; 619 | case IFPSOpcodes::JumpFalse: 620 | $jumploc = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 621 | if ($jumploc >= 0x80000000) $jumploc -= 0x100000000; 622 | $op2 = $this->ParseOperand($bc,$bclen,$offset); 623 | $curr->operands[] = $jumploc + $offset; 624 | $curr->operands[] = $op2; 625 | break; 626 | case IFPSOpcodes::Ret: 627 | break; 628 | case IFPSOpcodes::SetStackType: 629 | $curr->operands[] = IFPSGlobals::ParseVar(IFPSGlobals::ReadUInt32($bc,$bclen,$offset)); 630 | $curr->operands[] = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 631 | break; 632 | case IFPSOpcodes::PushType: 633 | $StackCount++; 634 | $curr->operands[] = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 635 | break; 636 | case IFPSOpcodes::Compare: 637 | if ($bclen < $offset + 1) throw new Exception("Reached end of bytecode"); 638 | $cop = current(unpack('C',substr($bc,$offset++,1))); 639 | $cops = array(">=","<=",">","<","!=","==","in","is"); 640 | if (!array_key_exists($cop,$cops)) throw new Exception("Unhandled Compare operand"); 641 | $cop = $cops[$cop]; 642 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 643 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 644 | $curr->operands[] = $cop; 645 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 646 | break; 647 | case IFPSOpcodes::CallVar: 648 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 649 | break; 650 | case IFPSOpcodes::SetPtr: 651 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 652 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 653 | break; 654 | case IFPSOpcodes::LogicalNot: 655 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 656 | break; 657 | case IFPSOpcodes::Neg: 658 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 659 | break; 660 | case IFPSOpcodes::SetFlag: 661 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 662 | if ($bclen < $offset + 1) throw new Exception("Reached end of bytecode"); 663 | $curr->operands[] = current(unpack('C',substr($bc,$offset++,1))); 664 | break; 665 | case IFPSOpcodes::JumpFlag: 666 | $jumploc = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 667 | if ($jumploc >= 0x80000000) $jumploc -= 0x100000000; 668 | $curr->operands[] = $jumploc + $offset; 669 | break; 670 | case IFPSOpcodes::PushEH: 671 | for ($i = 0; $i < 4; $i++) 672 | $curr->operands[] = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 673 | break; 674 | case IFPSOpcodes::PopEH: 675 | if ($bclen < $offset + 1) throw new Exception("Reached end of bytecode"); 676 | $curr->operands[] = current(unpack('C',substr($bc,$offset++,1))); 677 | break; 678 | case IFPSOpcodes::Not: 679 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 680 | break; 681 | case IFPSOpcodes::SetCopyPointer: 682 | for ($i = 0; $i < 2; $i++) { 683 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 684 | } 685 | break; 686 | case IFPSOpcodes::Inc: 687 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 688 | break; 689 | case IFPSOpcodes::Dec: 690 | $curr->operands[] = $this->ParseOperand($bc,$bclen,$offset); 691 | break; 692 | case IFPSOpcodes::PopJump: 693 | $StackCount--; 694 | $jumploc = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 695 | if ($jumploc >= 0x80000000) $jumploc -= 0x100000000; 696 | $curr->operands[] = $jumploc + $offset; 697 | break; 698 | case IFPSOpcodes::PopPopJump: 699 | $StackCount -= 2; 700 | $jumploc = IFPSGlobals::ReadUInt32($bc,$bclen,$offset); 701 | if ($jumploc >= 0x80000000) $jumploc -= 0x100000000; 702 | $curr->operands[] = $jumploc + $offset; 703 | break; 704 | case IFPSOpcodes::Nop: 705 | break; 706 | default: 707 | var_dump($ret); 708 | throw new Exception("Unknown opcode: 0x".dechex($curr->opcode)); 709 | } 710 | $curr->stackcount = $StackCount; 711 | $ret[] = $curr; 712 | } while ($offset < $bclen); 713 | // walk the bytecode to resolve jump targets 714 | foreach ($ret as $inst) { 715 | if (in_array($inst->opcode,array(IFPSOpcodes::Jump,IFPSOpcodes::JumpTrue,IFPSOpcodes::JumpFalse,IFPSOpcodes::JumpFlag,IFPSOpcodes::PopJump,IFPSOpcodes::PopPopJump))) { 716 | $resolved = false; 717 | foreach ($ret as &$target) { 718 | if ($target->offset == $inst->operands[0]) { 719 | $target->jumptarget = $resolved = true; 720 | break; 721 | } 722 | } 723 | if (!$resolved) throw new Exception("Couldn't resolve jump target - location: 0x".dechex($inst->operands[0])); 724 | } 725 | } 726 | return $ret; 727 | } 728 | 729 | private function ParseExportDecl($edecl) { 730 | $edecl = explode(" ",$edecl); 731 | 732 | $ret = (object) array(); 733 | $rtype = (int) array_shift($edecl); 734 | if ($rtype == -1) $ret->isVoid = true; 735 | else { 736 | $ret->ReturnType = $this->types[$rtype]; 737 | $ret->isVoid = false; 738 | } 739 | $ret->params = array(); 740 | foreach ($edecl as $param) { 741 | $pmode = substr($param,0,1); 742 | $paramtype = (int) substr($param,1); 743 | $p = (object) array("modeIn" => ($pmode === "@"), "type" => $this->types[$paramtype]); 744 | $ret->params[] = $p; 745 | } 746 | return $ret; 747 | } 748 | } 749 | --------------------------------------------------------------------------------