#!/usr/bin/php foreach # + smaller format correction (whitespaces at end of line, removed TAB constant) # + Added "0x" to every hex notation # + "no command given" -> "Unknown command" # + misc other changes # + Added feature "levelstats" # + formatted help view (tabs) # + removed 0x80 = '*' transcription. This is wrong. "80" and above is for 16-bit MBCS characters. "80 80" is a star. "80 21" is something different, for example. # + changed 0x76 from " to ' . # + changed transcription for right, left, top, down arrow and the 2 quotes (beginning and ending) # TODO: transcription-table aktualisieren # TODO: dictionary wird nicht aufgelöst! (itemnames) # TODO: itemnames geht nicht in JPN ?! # TODO: Lang "sp" -> "es" ... # TODO: "0x" überall # TODO: hexdaten immer angeben, z.b. FIRE (0x...) # TODO: alles so anordnen, damit die hex-daten exakt der reihenfolge in der ROM entsprechen? # TODO: white spaces -> tabs @codeformation # TODO: FROM rom_map in the wiki: DBC5B to DBC9A = Armor status ailment immunities # TODO: FROM rom_map in the wiki: DBD89 to DC6FC = Enemy data # TODO: rom offsets der einzelnen items (waffen,monster,armor,level,statmod) angeben # TODO: "ROM offset: $os_..." anzeige inkl $options['header'] ? Im Moment ohne. # TODO: alle offsets uppercase # --- # define version $version = '1.0R+vts(2013-01-06)'; # TODO: auslagern in $offsets # TODO: kann man das aus der datei auslesen $max_statsmod_idx = 18; // 0x80 .. 0xC8 $max_levels = 50; # define default offsets $offsets = array(); $offsets['en'] = array(); $offsets['de'] = array(); $offsets['jp'] = array(); $offsets['fr'] = array(); $offsets['sp'] = array(); $offsets['en']['enemies_index'] = 0x000DBCC3; $offsets['en']['enemies_bank'] = 0x000D0000; $offsets['en']['armor'] = 0x000DBBDB; $offsets['en']['weapons'] = 0x000DBB5B; $offsets['en']['rings'] = 0x000DBC9B; $offsets['en']['itemnames_index'] = 0; $offsets['en']['itemnames'] = 0x001285A4; $offsets['en']['first_weapon_id'] = 128; $offsets['en']['first_armor_id'] = 160; $offsets['en']['first_ring_id'] = 2; $offsets['en']['signature'] = 'c1'; $offsets['en']['statsmod_index'] = 0x0005F68A; $offsets['en']['level_stats'] = 0x0DB92A; $offsets['de']['enemies_index'] = 0x000DBA90; $offsets['de']['enemies_bank'] = 0x000D0000; $offsets['de']['armor'] = 0x000DB9A8; $offsets['de']['weapons'] = 0x000DB928; $offsets['de']['rings'] = 0x000DBA68; $offsets['de']['itemnames_index'] = 0; $offsets['de']['itemnames'] = 0x00128576; $offsets['de']['first_weapon_id'] = 128; $offsets['de']['first_armor_id'] = 160; $offsets['de']['first_ring_id'] = 2; $offsets['de']['signature'] = 'd5'; $offsets['de']['statsmod_index'] = 0x0005F6BD; $offsets['de']['level_stats'] = 0x01F422; $offsets['jp']['enemies_index'] = 0x000DBDFA; $offsets['jp']['enemies_bank'] = 0x000D0000; $offsets['jp']['armor'] = 0x000DBD12; $offsets['jp']['weapons'] = 0x000DBC92; $offsets['jp']['rings'] = 0x000DBDD2; $offsets['jp']['itemnames_index'] = 0; $offsets['jp']['itemnames'] = 0x00128579; $offsets['jp']['first_weapon_id'] = 128; $offsets['jp']['first_armor_id'] = 160; $offsets['jp']['first_ring_id'] = 2; $offsets['jp']['signature'] = 'db'; $offsets['jp']['statsmod_index'] = 0x0005F5F2; $offsets['jp']['level_stats'] = 0x0DBA61; $offsets['fr']['enemies_index'] = 0x000DBA92; $offsets['fr']['enemies_bank'] = 0x000D0000; $offsets['fr']['armor'] = 0x000DB9AA; $offsets['fr']['weapons'] = 0x000DB92A; $offsets['fr']['rings'] = 0x000DBA6A; $offsets['fr']['itemnames_index'] = 0; $offsets['fr']['itemnames'] = 0x00128592; $offsets['fr']['first_weapon_id'] = 128; $offsets['fr']['first_armor_id'] = 160; $offsets['fr']['first_ring_id'] = 2; $offsets['fr']['signature'] = 'd2'; $offsets['fr']['statsmod_index'] = 0x0005F6BD; $offsets['fr']['level_stats'] = 0x01F422; // TODO: WHICH ONE IS THE EFFECTIVE ONE? $offsets['fr']['level_stats'] = 0x19FDBE; $offsets['sp']['enemies_index'] = 0x000DBBC9; $offsets['sp']['enemies_bank'] = 0x000D0000; $offsets['sp']['armor'] = 0x000DBAE1; $offsets['sp']['weapons'] = 0x000DBA61; $offsets['sp']['rings'] = 0x000DBBA1; $offsets['sp']['itemnames_index'] = 0; $offsets['sp']['itemnames'] = 0x00128560; $offsets['sp']['first_weapon_id'] = 128; $offsets['sp']['first_armor_id'] = 160; $offsets['sp']['first_ring_id'] = 2; $offsets['sp']['signature'] = 'd4'; $offsets['sp']['statsmod_index'] = 0x0005F6BD; $offsets['sp']['level_stats'] = 0x19FDBE; # define special enemy blocks $enemies_extra[57] = 1; $enemies_extra[65] = 1; $enemies_extra[69] = 2; # define default options $options = array(); $options['language'] = 'de'; $options['csv'] = false; $options['offset'] = 0; $options['start'] = 0; $options['end'] = 0; $options['binary'] = false; $options['without-names'] = false; $options['name-offset'] = 0; $options['autodetect-offset'] = 0x0000B873; $options['header'] = 0; $options['enemy-raw'] = false; $options['utf8'] = false; // currently not changeable in CLI mode $language_override = false; # check commandline parameters if($argc == 1 || ($argc >= 2 && (strtolower($argv[1]) == '--help' || strtolower($argv[1]) == 'help'))) { # Display help echo "Terranigma Data Reader $version -- by Road\n"; echo "Usage: ./".basename($argv[0])." [options]\n"; echo "\n"; echo "Commands:\n"; echo "\titemnames Dumps all itemnames\n"; echo "\trings Dumps all magic rings\n"; echo "\tweapons Dumps all weapons\n"; echo "\tarmor Dumps all armor\n"; echo "\tenemies Dumps all enemies\n"; echo "\tstatsmod Dumps all stats modifications for weapons and armor\n"; echo "\tlevelstats Dumps all level stats\n"; echo "\traw Dumps raw data from rom\n"; echo "\n"; echo "Options:\n"; echo "\t--help Show this help\n"; echo "\t--language=XX Choose rom language (default: auto, fallback: ".$options['language'].")\n"; echo "\t--offset=XXXXX Change base offset for the chosen command\n"; echo "\t--name-offset=XXXXX Change base offset for item name resolution\n"; echo "\t--start=XXXXX Set start offset for raw output\n"; echo "\t--end=XXXXX Set end offset for raw output\n"; echo "\t--binary Output raw as binary\n"; echo "\t--csv Output raw as csv (Delimiter is ;)\n"; echo "\t--without-names Disable item name resolution\n"; echo "\t--enemy-raw Show raw enemy statblock\n"; echo "\t--autodetect-offset=XXXXX Change offset for rom version autodetection\n"; echo "\t--header(=XXX) Move all offsets by header size (default: 512)\n"; echo "\n"; echo "Supported ROM Versions: "; foreach($offsets as $language => $val) { echo $language." "; } echo "\n\n"; exit(1); } ### define functions function autodetect_version($auto_offset) { global $offsets; global $file_handle; global $options; if(fseek($file_handle, $auto_offset+$options['header']) == -1) { echo "Invalid autodetect offset\n"; exit(5); } $signature = bin2hex(fread($file_handle, 1)); foreach($offsets as $lang => $arrval) { if(isset($arrval['signature']) && $arrval['signature'] == $signature) { return $lang; } } return $options['language']; } function terra2ascii($tstring) { $tchars = array('20','21','22','23','24','25','26','27','28','29','2a','2b','2c','2d','2e','2f','30','31','32','33', '34','35','36','37','38','39','3a','3b','3c','3d','3e','3f','40','41','42','43','44','45','46','47', '48','49','4a','4b','4c','4d','4e','4f','50','51','52','53','54','55','56','57','58','59','5a','5b', '5c','5d','5e','5f','60','61','62','63','64','65','66','67','68','69','6a','6b','6c','6d','6e','6f', '70','71','72','73','74','75','76','77','78','79','7a','7b','7c','7d','7e','7f'); $achars = array(' ','A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X', 'Y','Z','ü','ä','Ä','ö','Ö','','a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p', 'q','r','s','t','u','v','w','x','y','z','Ãœ',' ',' ',' ','ß','?','(',')','0','1','2','3','4','5','6', '7','8','9','!',',',':',/* Arrows: */ '->','<-','^','v','"' /* beginning */,'"' /* ending */,"'",'=','','%','ô','+','-','/','&','.'); # Sorry, german table only (TODO) $output = ''; $arr_string = str_split($tstring, 2); foreach($arr_string as $value) { $output .= str_replace($tchars,$achars,$value); } global $options; if (!$options['utf8']) { $output = utf8_decode($output); # Added by VTS } return $output; } function m_element($element_type) { if(is_numeric($element_type)) $element_type .= ''; # convert to string switch($element_type) { case '00': case '0': return 'Non-Elemental';break; case '08': case '8': return '[LIGHT]';break; case '10': return '[EARTH]';break; case '20': return '[THUNDER]';break; case '40': return '[ICE]';break; case '80': return '[FIRE]';break; default: return 'Invalid Element';break; } } function decode_resistance($hexbytes, $type) { $output = ''; switch($type) { case 'mresist': $data = str_split($hexbytes,2); $data[0] = hexdec($data[0]); $data[1] = hexdec($data[1]); if($data[1] & 8) { # Ignore first byte $res = 0; if($data[1] & 1) $res = 50; if($data[1] & 2) $res = 75; if($res == 0) { $output .= '[LIGHT]:Immunity '; } else { $output .= "[LIGHT]:-$res% "; } } if($data[1] & 16) { $res = 0; if($data[0] & 64) $res = 50; if($data[0] & 128) $res = 75; if($res == 0) { $output .= '[EARTH]:Immunity '; } else { $output .= "[EARTH]:-$res% "; } } if($data[1] & 32) { $res = 0; if($data[0] & 16) $res = 50; if($data[0] & 32) $res = 75; if($res == 0) { $output .= '[THUNDER]:Absorb/Immunity '; } else { $output .= "[THUNDER]:-$res% "; } } if($data[1] & 64) { $res = 0; if($data[0] & 4) $res = 50; if($data[0] & 8) $res = 75; if($res == 0) { $output .= '[ICE]:Absorb/Immunity '; } else { $output .= "[ICE]:-$res% "; } } if($data[1] & 128) { $res = 0; if($data[0] & 1) $res = 50; if($data[0] & 2) $res = 75; if($res == 0) { $output .= '[FIRE]:Absorb/Immunity '; } else { $output .= "[FIRE]:-$res% "; } } if($data[1] & 4) $output .= '(04 Set) '; break; case 'mweak': $data = str_split($hexbytes,2); $data[0] = hexdec($data[0]); $data[1] = hexdec($data[1]); if($data[1] & 8) { # Ignore first byte $res = 0; if($data[1] & 1) $res = 50; if($data[1] & 2) $res = 100; if($res == 0) { $output .= '[LIGHT]:Exclusive '; } else { $output .= "[LIGHT]:+$res% "; } } if($data[1] & 16) { $res = 0; if($data[0] & 64) $res = 50; if($data[0] & 128) $res = 100; if($res == 0) { $output .= '[EARTH]:Exclusive '; } else { $output .= "[EARTH]:+$res% "; } } if($data[1] & 32) { $res = 0; if($data[0] & 16) $res = 50; if($data[0] & 32) $res = 100; if($res == 0) { $output .= '[THUNDER]:Exclusive '; } else { $output .= "[THUNDER]:+$res% "; } } if($data[1] & 64) { $res = 0; if($data[0] & 4) $res = 50; if($data[0] & 8) $res = 100; if($res == 0) { $output .= '[ICE]:Exclusive '; } else { $output .= "[ICE]:+$res% "; } } if($data[1] & 128) { $res = 0; if($data[0] & 1) $res = 50; if($data[0] & 2) $res = 100; if($res == 0) { $output .= '[FIRE]:Exclusive '; } else { $output .= "[FIRE]:+$res% "; } } if($data[1] & 4) $output .= '(04 Set) '; break; case 'presist': $data = str_split($hexbytes,2); $data[0] = hexdec($data[0]); $data[1] = hexdec($data[1]); if($data[1] & 8) { # Ignore first byte $res = 0; if($data[1] & 1) $res = 50; if($data[1] & 2) $res = 75; if($res == 0) { $output .= '[UNKNOWN]:Immunity '; # What does unknown mean? (Marschall) } else { $output .= "[UNKNOWN]:-$res% "; } } if($data[1] & 16) { $res = 0; if($data[0] & 64) $res = 50; if($data[0] & 128) $res = 75; if($res == 0) { $output .= '[SLICER]:Immunity '; } else { $output .= "[SLICER]:-$res% "; } } if($data[1] & 32) { $res = 0; if($data[0] & 16) $res = 50; if($data[0] & 32) $res = 75; if($res == 0) { $output .= '[SLIDER]:Immunity '; } else { $output .= "[SLIDER]:-$res% "; } } if($data[1] & 64) { $res = 0; if($data[0] & 4) $res = 50; if($data[0] & 8) $res = 75; if($res == 0) { $output .= '[SPINNER]:Immunity '; } else { $output .= "[SPINNER]:-$res% "; } } if($data[1] & 128) { $res = 0; if($data[0] & 1) $res = 50; if($data[0] & 2) $res = 75; if($res == 0) { $output .= '[RUSHING]:Immunity '; } else { $output .= "[RUSHING]:-$res% "; } } if($data[1] & 4) $output .= '(04 Set) '; break; case 'pweak': $data = str_split($hexbytes,2); $data[0] = hexdec($data[0]); $data[1] = hexdec($data[1]); if($data[1] & 8) { # Ignore first byte $res = 0; if($data[1] & 1) $res = 50; if($data[1] & 2) $res = 100; if($res == 0) { $output .= '[UNKNOWN]:Exclusive '; } else { $output .= "[UNKNOWN]:+$res% "; } } if($data[1] & 16) { $res = 0; if($data[0] & 64) $res = 50; if($data[0] & 128) $res = 100; if($res == 0) { $output .= '[SLICER]:Exclusive '; } else { $output .= "[SLICER]:+$res% "; } } if($data[1] & 32) { $res = 0; if($data[0] & 16) $res = 50; if($data[0] & 32) $res = 100; if($res == 0) { $output .= '[SLIDER]:Exclusive '; } else { $output .= "[SLIDER]:+$res% "; } } if($data[1] & 64) { $res = 0; if($data[0] & 4) $res = 50; if($data[0] & 8) $res = 100; if($res == 0) { $output .= '[SPINNER]:Exclusive '; } else { $output .= "[SPINNER]:+$res% "; } } if($data[1] & 128) { $res = 0; if($data[0] & 1) $res = 50; if($data[0] & 2) $res = 100; if($res == 0) { $output .= '[RUSHING]:Exclusive '; } else { $output .= "[RUSHING]:+$res% "; } } if($data[1] & 4) $output .= '(04 Set) '; break; } return $output; } function decode_sfx($hexbytes) { $output = ''; $data = str_split($hexbytes,2); $data[0] = hexdec($data[0]); $data[1] = hexdec($data[1]); if($data[0] & 32) $output .= "*Burned* "; if($data[0] & 64) $output .= "*Sleeping* "; if($data[0] & 128) $output .= "*Paralysed* "; if($data[1] & 1) $output .= "*Frozen* "; if($data[1] & 2) $output .= "*DEF down* "; if($data[1] & 4) $output .= "*STR down* "; if($data[1] & 8) $output .= "*Charmed* "; if($data[1] & 16) $output .= "*Confused* "; if($data[1] & 32) $output .= "*Poision* "; if($data[1] & 64) $output .= "*Deadly Poison* "; if($data[1] & 128) $output .= "*Cursed* "; return $output; } function decode_attack_sfx($data) { $output = ''; switch(true) { case (($data & 8) && ($data & 32) && ($data & 64)): $output.= '~Critical~'; break; case (($data & 8) && ($data & 16) && ($data & 64)): $output.= '*Burned*'; break; case (($data & 8) && ($data & 16) && ($data & 32)): $output.= '*DEF down*'; break; case (($data & 16) && ($data & 64)): $output.= '*Sleeping*'; break; case (($data & 8) && ($data & 64)): $output.= '*Paralysed*'; break; case (($data & 16) && ($data & 32)): $output.= '*STR down*'; break; case (($data & 8) && ($data & 32)): $output.= '*Charmed*'; break; case (($data & 8) && ($data & 16)): $output.= '*Poison*'; break; case ($data & 64): $output.= '*Frozen*'; break; case ($data & 32): $output.= '*Confused*'; break; case ($data & 16): $output.= '*Deadly Poison*'; break; case ($data & 8): $output.= '*Cursed*'; break; } return $output; } function read_itemnames($data_offset) { global $file_handle; global $options; if(fseek($file_handle, $data_offset+$options['header']) == -1) { echo "Invalid (itemname data) offset\n"; exit(5); } for($i = 1;$i <= 255;$i++) { # 255 Items $c = 0; $data[$i] = ''; switch($options['language']) { case 'sp': # discard nothing break; default: fread($file_handle,2); # discard 2 bytes break; } while(true) { $stream_byte = bin2hex(fread($file_handle, 1)); if($stream_byte == 'd4' || $c > 100) break; # something is wrong, cut off after 100 chars $data[$i] .= $stream_byte; $c++; } $data[$i] = terra2ascii($data[$i]); } return $data; } function read_enemy_offsets($eoffset) { global $file_handle; global $options; $enemies = array(); if(fseek($file_handle, $eoffset+$options['header']) == -1) { echo "Invalid (enemy index) offset\n"; exit(5); } for($i = 0;$i <= 98;$i++) { $pointer = str_split(bin2hex(fread($file_handle, 2)),2); # 99 Enemies, 2 byte pointers $enemies[$i] = hexdec($pointer[1] . $pointer[0]); # Endian order and conversion } unset($enemies[0]); # unset first (invalid) offset unset($enemies[74]); # also invalid return $enemies; } ### end functions $command = $argv[1]; $filename = $argv[2]; # Read options $option_count = $argc - 3; if($option_count > 0) { for($i = 0;$i <= $option_count;$i++) { # check if it's an option, else ignore if(substr($argv[$i+2], 0, 2) == '--') { if(strpos($argv[$i+2], '=') !== FALSE) { $pos = strpos($argv[$i+2], '='); $has_val = true; } else { $pos = strlen($argv[$i+2]); $has_val = false; } $option = strtolower(substr($argv[$i+2], 2, $pos - 2)); switch($option) { case 'offset': if($has_val) $options['offset'] = intval(substr($argv[$i+2], $pos+1),0); break; case 'start': if($has_val) $options['start'] = intval(substr($argv[$i+2], $pos+1),0); break; case 'end': if($has_val) $options['end'] = intval(substr($argv[$i+2], $pos+1),0); break; case 'language': if($has_val) $options['language'] = substr($argv[$i+2], $pos+1); $language_override = true; break; case 'csv': $options['csv'] = true; break; case 'binary': $options['binary'] = true; break; case 'without-names': $options['without-names'] = true; break; case 'enemy-raw': $options['enemy-raw'] = true; break; case 'name-offset': if($has_val) $options['name-offset'] = intval(substr($argv[$i+2], $pos+1),0); break; case 'autodetect-offset': if($has_val) $options['autodetect-offset'] = intval(substr($argv[$i+2], $pos+1),0); break; case 'header': if($has_val) $options['header'] = intval(substr($argv[$i+2], $pos+1),0); else $options['header'] = 512; break; default: break; } } } } # check if the file really exists if(!file_exists($filename)) { echo "Invalid filename: $filename\n"; exit(2); } $file_handle = @fopen($filename,'rb'); if($file_handle === FALSE) { echo "Cannot open file: $filename\n"; exit(3); } # Try to autodetect language if(!$language_override) $options['language'] = autodetect_version($options['autodetect-offset']); switch($command) { # Dump Raw Data case 'raw': if($options['start'] >= $options['end']) { echo "No data to dump\n"; exit(4); } if(fseek($file_handle, $options['start']+$options['header']) == -1) { echo "Invalid start offset\n"; exit(5); } $size = $options['end'] - $options['start']; for($i = 1;$i <= $size;$i++) { $data = fread($file_handle, 1); if($options['binary']) { echo $data; } elseif($options['csv']) { echo bin2hex($data).';'; } else { echo bin2hex($data).' '; } } if(!($options['binary'] || $options['csv'])) echo "\n"; break; # Dump Ring data case 'rings': $offset = ($options['offset'] == 0) ? $offsets[$options['language']]['rings'] : $options['offset']; if(!$options['without-names']) { $name_offset = ($options['name-offset'] == 0) ? $offsets[$options['language']]['itemnames'] : $options['name-offset']; $itemnames = read_itemnames($name_offset); } if(fseek($file_handle, $offset+$options['header']) == -1) { echo "Invalid (rings) offset\n"; exit(5); } for($i = 1;$i <= 10;$i++) { # Ten rings to bind them... $rings[] = str_split(bin2hex(fread($file_handle, 4)), 2); # 4 Bytes Data } foreach($rings as $index => $ring) { echo "Ring $index ("; if(@isset($itemnames[$index+$offsets[$options['language']]['first_ring_id']])) echo @$itemnames[$index+$offsets[$options['language']]['first_ring_id']]; echo ")\n"; echo "\tAttack power: ".hexdec($ring[1].$ring[0])." (".$ring[1].$ring[0].")\n"; echo "\tElement: ".m_element($ring[3])." (0x".$ring[3].")\n"; echo "\n"; } break; # Dump statsmod data case 'statsmod': for ($i=0x80; $i<=0x80+($max_statsmod_idx*4); $i+=4) { $os_statsmod = $offsets[$options['language']]['statsmod_index'] + ($i-0x80); if (fseek($file_handle, $os_statsmod+$options['header']) == -1) { echo "Invalid (statsmod) offset\n"; exit(5); } $j = ($i-0x80) / 4; echo "Stats modification $j (Wpn/Armr code 0x".strtoupper(dechex($i)).", ROM offset: 0x$os_statsmod)\n"; $statsmod = fread($file_handle, 4); $mod_hp = uint8($statsmod[0]); if ($mod_hp >=0) $mod_hp ='+'.$mod_hp; $mod_str = uint8($statsmod[1]); if ($mod_str >=0) $mod_str ='+'.$mod_str; $mod_def = uint8($statsmod[2]); if ($mod_def >=0) $mod_def ='+'.$mod_def; $mod_luck = uint8($statsmod[3]); if ($mod_luck>=0) $mod_luck='+'.$mod_luck; echo "\tHP $mod_hp (0x".bin2hex($statsmod[0]).")\n"; echo "\tSTR $mod_str (0x".bin2hex($statsmod[1]).")\n"; echo "\tDEF $mod_def (0x".bin2hex($statsmod[2]).")\n"; echo "\tLuck $mod_luck (0x".bin2hex($statsmod[3]).")\n"; echo "\n"; } break; # Dump level stats case 'levelstats': for ($i=1; $i<=$max_levels; $i++) { $os_level = $offsets[$options['language']]['level_stats'] + ($i-1)*11; if(fseek($file_handle, $os_level+$options['header']) == -1) { echo "Invalid (level_stats) offset\n"; exit(5); } echo "Level $i (ROM offset 0x$os_level)\n"; $data = fread($file_handle, 11); $lev_exp = intval(bin2hex($data[2].$data[1].$data[0])); echo "\tEXP:\t$lev_exp (0x".bin2hex($data[0].$data[1].$data[2])." LittleEndian+BCD)\n"; $lev_hp_dec = hexdec(bin2hex($data[4].$data[3])); $lev_hp_hex = bin2hex($data[3].$data[4]); echo "\tHP:\t$lev_hp_dec (0x$lev_hp_hex)\n"; $lev_str_dec = hexdec(bin2hex($data[6].$data[5])); $lev_str_hex = bin2hex($data[5].$data[6]); echo "\tSTR:\t$lev_str_dec (0x$lev_str_hex)\n"; $lev_def_dec = hexdec(bin2hex($data[8].$data[7])); $lev_def_hex = bin2hex($data[7].$data[8]); echo "\tDEF:\t$lev_def_dec (0x$lev_def_hex)\n"; $lev_luck_dec = hexdec(bin2hex($data[10].$data[9])); $lev_luck_hex = bin2hex($data[9].$data[10]); echo "\tLuck:\t$lev_luck_dec (0x$lev_luck_hex)\n"; echo "\n"; } break; # Dump Weapon data case 'weapons': $offset = ($options['offset'] == 0) ? $offsets[$options['language']]['weapons'] : $options['offset']; if(!$options['without-names']) { $name_offset = ($options['name-offset'] == 0) ? $offsets[$options['language']]['itemnames'] : $options['name-offset']; $itemnames = read_itemnames($name_offset); } if(fseek($file_handle, $offset+$options['header']) == -1) { echo "Invalid (weapons) offset\n"; exit(5); } for($i = 1;$i <= 32;$i++) { # 32 weapons (not all are used) $weapons[] = str_split(bin2hex(fread($file_handle, 4)), 2); # 4 Bytes Data } foreach($weapons as $index => $weapon) { echo "Weapon $index ("; if(@isset($itemnames[$index+$offsets[$options['language']]['first_weapon_id']])) echo @$itemnames[$index+$offsets[$options['language']]['first_weapon_id']]; echo ")\n"; echo "\tAttack power: ".hexdec($weapon[0])." (0x".$weapon[0].")\n"; if($weapon[1] != '00') { $os_statsmod = $offsets[$options['language']]['statsmod_index'] + (hexdec($weapon[1])-0x80); if (fseek($file_handle, $os_statsmod+$options['header']) == -1) { echo "Invalid (statsmod) offset\n"; exit(5); } echo "\tMod Index: 0x".strtoupper($weapon[1])." (ROM offset: 0x$os_statsmod)\n"; $statsmod = fread($file_handle, 4); $mod_hp = uint8($statsmod[0]); if ($mod_hp >=0) $mod_hp ='+'.$mod_hp; $mod_str = uint8($statsmod[1]); if ($mod_str >=0) $mod_str ='+'.$mod_str; $mod_def = uint8($statsmod[2]); if ($mod_def >=0) $mod_def ='+'.$mod_def; $mod_luck = uint8($statsmod[3]); if ($mod_luck>=0) $mod_luck='+'.$mod_luck; echo "\t\tHP $mod_hp (0x".bin2hex($statsmod[0]).")\n"; echo "\t\tSTR $mod_str (0x".bin2hex($statsmod[1]).")\n"; echo "\t\tDEF $mod_def (0x".bin2hex($statsmod[2]).")\n"; echo "\t\tLuck $mod_luck (0x".bin2hex($statsmod[3]).")\n"; } if($weapon[3] != '00') { echo "\tElement: ".m_element($weapon[3])." (0x".$weapon[3].")\n"; } else { echo "\tNon-elemental Weapon (0x00)\n"; } echo "\n"; } break; # Dump Armor data case 'armor': $offset = ($options['offset'] == 0) ? $offsets[$options['language']]['armor'] : $options['offset']; if(!$options['without-names']) { $name_offset = ($options['name-offset'] == 0) ? $offsets[$options['language']]['itemnames'] : $options['name-offset']; $itemnames = read_itemnames($name_offset); } if(fseek($file_handle, $offset+$options['header']) == -1) { echo "Invalid (armor) offset\n"; exit(5); } # Read the basic attributes for($i = 1;$i <= 32;$i++) { # 32 armors (not all are used) $armors[] = str_split(bin2hex(fread($file_handle, 4)), 2); # 4 Bytes Data } # Now read the status effect immunities for($i = 1;$i <= 32;$i++) { # 32 armors (not all are used) $armors[$i-1]['sfx'] = bin2hex(fread($file_handle, 2)); # 2 Bytes Data } foreach($armors as $index => $armor) { echo "Armor $index ("; if(@isset($itemnames[$index+$offsets[$options['language']]['first_armor_id']])) echo @$itemnames[$index+$offsets[$options['language']]['first_armor_id']]; echo ")\n"; echo "\tDefense power: ".hexdec($armor[0])." (0x".$armor[0].")\n"; if($armor[1] != '00') { $os_statsmod = $offsets[$options['language']]['statsmod_index'] + (hexdec($armor[1])-0x80); if (fseek($file_handle, $os_statsmod+$options['header']) == -1) { echo "Invalid (statsmod) offset\n"; exit(5); } echo "\tMod Index: 0x".strtoupper($armor[1])." (ROM offset: 0x$os_statsmod)\n"; $statsmod = fread($file_handle, 4); $mod_hp = uint8($statsmod[0]); if ($mod_hp >=0) $mod_hp ='+'.$mod_hp; $mod_str = uint8($statsmod[1]); if ($mod_str >=0) $mod_str ='+'.$mod_str; $mod_def = uint8($statsmod[2]); if ($mod_def >=0) $mod_def ='+'.$mod_def; $mod_luck = uint8($statsmod[3]); if ($mod_luck>=0) $mod_luck='+'.$mod_luck; echo "\t\tHP $mod_hp (0x".bin2hex($statsmod[0]).")\n"; echo "\t\tSTR $mod_str (0x".bin2hex($statsmod[1]).")\n"; echo "\t\tDEF $mod_def (0x".bin2hex($statsmod[2]).")\n"; echo "\t\tLuck $mod_luck (0x".bin2hex($statsmod[3]).")\n"; } if($armor[3] != '00') { echo "\tElemental Resistances: ".decode_resistance($armor[2].$armor[3],'mresist')."(0x".$armor[2].$armor[3].")\n"; } if($armor['sfx'] != '0000') { echo "\tStatus Effect Immunities: ".decode_sfx($armor['sfx'])."(0x".$armor['sfx'].")\n"; } echo "\n"; } break; # Dump Item Names case 'itemnames': $offset = ($options['offset'] == 0) ? $offsets[$options['language']]['itemnames'] : $options['offset']; $itemnames = read_itemnames($offset); foreach ($itemnames as $n => &$a) { echo "$n = $a\n"; } break; # Dump enemy data case 'enemies': $e_offsets = read_enemy_offsets($offsets[$options['language']]['enemies_index']); $offset = ($options['offset'] == 0) ? $offsets[$options['language']]['enemies_bank'] : $options['offset']; if(fseek($file_handle, $offset+$options['header']) == -1) { echo "Invalid (enemy bank) offset\n"; exit(5); } foreach($e_offsets as $e_id => $e_offset) { if($options['enemy-raw']) { if(fseek($file_handle, $offset+$options['header']+$e_offset) == -1) { echo "Invalid (enemy) offset\n"; exit(5); } $rawdata = '<'.join(' ',str_split(bin2hex(fread($file_handle, 8)),2)).'>'; $rawdata .= ' <'.join(' ',str_split(bin2hex(fread($file_handle, 1)),2)).'>'; $rawdata .= ' <'.join(' ',str_split(bin2hex(fread($file_handle, 2)),2)).'>'; $rawdata .= ' <'.join(' ',str_split(bin2hex(fread($file_handle, 2)),2)).'>'; $rawdata .= ' <'.join(' ',str_split(bin2hex(fread($file_handle, 2)),2)).'>'; $rawdata .= ' <'.join(' ',str_split(bin2hex(fread($file_handle, 5)),2)).'>'; $rawdata .= ' <'.join(' ',str_split(bin2hex(fread($file_handle, 5)),2)).'>'; if(isset($enemies_extra[$e_id])) { for($i = 1;$i <= $enemies_extra[$e_id];$i++) { $rawdata .= ' <'.join(' ',str_split(bin2hex(fread($file_handle, 5)),2)).'>'; } } echo $rawdata."\n"; } if(fseek($file_handle, $offset+$options['header']+$e_offset) == -1) { echo "Invalid (enemy) offset\n"; exit(5); } # Enemy ID $output[0] = "Monster ".$e_id." (0x".dechex($e_id).")"; # First Block # Read magic resistances $data = bin2hex(fread($file_handle, 2)); $output[5] = "\tMagic Resistance against: ".decode_resistance($data, 'mresist')." (0x$data)"; # Read physical resistances $data = bin2hex(fread($file_handle, 2)); $output[7] = "\tPhysical Resistance against: ".decode_resistance($data, 'presist')." (0x$data)"; # Read magic weaknesses $data = bin2hex(fread($file_handle, 2)); $output[6] = "\tMagic Weakness against: ".decode_resistance($data, 'mweak')." (0x$data)"; # Read physical weaknesses $data = bin2hex(fread($file_handle, 2)); $output[8] = "\tPhysical Weakness against: ".decode_resistance($data, 'pweak')." (0x$data)"; # Read Level $data_hex = bin2hex(fread($file_handle, 1)); $data = hexdec($data_hex); $output[1] = "\tLevel: ".$data." (0x$data_hex)"; # Read Lifepoints $data = str_split(bin2hex(fread($file_handle, 2)),2); # Endian order $data_dec = hexdec($data[1].$data[0]); $data_hex_endian = $data[0].$data[1]; $output[2] = "\tLife: $data_dec (0x$data_hex_endian LittleEndian)"; # Read Exp Points $data = str_split(bin2hex(fread($file_handle, 2)),2); # Endian order $data_dec = intval($data[1].$data[0]); # decimal, not hex $data_hex_endian = $data[0].$data[1]; $output[3] = "\tExp.: $data_dec (0x$data_hex_endian LittleEndian+BCD)"; # Read Dropped Gems $data = str_split(bin2hex(fread($file_handle, 2)),2); $drop = '100'; $decval = hexdec($data[1]); # extract drop chance if($decval & 128) { $drop = $drop/2; $decval -= hexdec(80); } if($decval & 64) { $drop = $drop/2; $decval -= hexdec(40); } if($decval & 32) { $drop = $drop/2; $decval -= hexdec(20); } if($decval & 16) { $drop = $drop/2; $decval -= hexdec(10); } $output[4] = "\tGems: ".intval($decval.$data[0])." (".$drop."%) (0x".$data[0].$data[1].")"; # decimal, not hex # Read Strength and Status Effects of primary attack $data = str_split(bin2hex(fread($file_handle, 2)),2); $data[0] = hexdec($data[0]); $data[1] = hexdec($data[1]); if($data[1] & 1) $data[0] += 256; if($data[1] & 2) $data[0] += 512; $output[10] = "\tStrength (Primary Attack): ".$data[0]." (0x".dechex($data[0]).")"; $output[12] = "\tStatus Effect (Primary Attack): "; $output[12] .= decode_attack_sfx($data[1])." (0x".$data[1].")"; # TODO: das ist immer leer. ist das ok? # Read Defense $data = str_split(bin2hex(fread($file_handle, 2)),2); # Endian order $output[9] = "\tDefense: ".hexdec($data[1].$data[0])." (0x".$data[0].$data[1]." LittleEndian)"; # Read Luck (Primary Attack) $data = bin2hex(fread($file_handle, 1)); $output[11] = "\tLuck (Primary Attack): ".hexdec($data)." (0x$data)"; # Read Strength and Status Effects of secondary attack $data = str_split(bin2hex(fread($file_handle, 2)),2); $data[0] = hexdec($data[0]); $data[1] = hexdec($data[1]); if($data[1] & 1) $data[0] += 256; if($data[1] & 2) $data[0] += 512; $output[13] = "\tStrength (Secondary Attack): ".$data[0]." (0x".dechex($data[0]).")"; $output[16] = "\tStatus Effect (Secondary Attack): "; $output[16] .= decode_attack_sfx($data[1])." (0x".$data[1].")"; # Read Unknown/Unused Byte and discard fread($file_handle, 1); # Read Element of secondary attack $data = bin2hex(fread($file_handle, 1)); $byte = false; if(hexdec($data) & 4) { $data = dechex(hexdec($data)-4); $byte = true; } $output[15] = "\tElement (Secondary Attack): ".m_element($data)." (0x$data)"; if($byte) $output[15] .= " (04 Set)"; # Read Luck (Secondary Attack) $data = bin2hex(fread($file_handle, 1)); $output[14] = "\tLuck (Secondary Attack): ".hexdec($data)." (0x$data)"; # Read extra attack blocks $extra = 0; if(isset($enemies_extra[$e_id])) { $extra = $enemies_extra[$e_id]; for($i = 1;$i <= $extra;$i++) { $data = str_split(bin2hex(fread($file_handle, 2)),2); $data[0] = hexdec($data[0]); $data[1] = hexdec($data[1]); if($data[1] & 1) $data[0] += 256; if($data[1] & 2) $data[0] += 512; $output[13+($i*4)] = "\tStrength (".(2+$i).". Attack): ".$data[0]; $output[16+($i*4)] = "\tStatus Effect (".(2+$i).". Attack): "; $output[16+($i*4)] .= decode_attack_sfx($data[1]); fread($file_handle, 1); $data = bin2hex(fread($file_handle, 1)); $byte = false; if(hexdec($data) & 4) { $data = dechex(hexdec($data)-4); $byte = true; } $output[15+($i*4)] = "\tElement (".(2+$i).". Attack): ".m_element($data); if($byte) $output[14+($i*4)] .= " (04 Set)"; $data = bin2hex(fread($file_handle, 1)); $output[14+($i*4)] = "\tLuck (".(2+$i).". Attack): ".hexdec($data); } } # Output everything in order for($i = 0;$i <= 16+($extra*4);$i++) { echo $output[$i]."\n"; } echo "\n"; } break; default: echo "Unknown command\n"; break; } exit(0); # --- function uint8($x) { $x = ord($x); if ($x >= 0x80) $x -= 0x100; return $x; } ?>