diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/D2Modder.iml b/.idea/D2Modder.iml new file mode 100644 index 0000000..48c936f --- /dev/null +++ b/.idea/D2Modder.iml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..0f9d6d1 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/php.xml b/.idea/php.xml new file mode 100644 index 0000000..38ad428 --- /dev/null +++ b/.idea/php.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CharEditor.php b/CharEditor.php index dd9dd62..a04b751 100644 --- a/CharEditor.php +++ b/CharEditor.php @@ -42,23 +42,39 @@ foreach ($ISCData as $k => $v) { //$filePath = "D:\Diablo II\MODS\MedianXL2012\save\Test.d2s"; -$filePath = "Test.d2s"; +$filePath = "Necro.d2s"; $char = new D2Char($filePath); -//$char->setAllSkills(56); +$char->setChar("CharacterStatus", "Died", 0); +$char->setChar("CharacterStatus", "Hardcore", 1); +//$char->setChar("CharacterStatus", "Expansion", 1); +//$char->setChar("LeftmousebuttonskillID", 223); + +$char->setAllSkills(1); //$char->setSkill(1, 99); -//$char->setChar("CharacterClass", 1); // 127 -$char->setChar("CharacterLevel", 0); -$char->setStat("strength", 30); -$char->setStat("energy", 30); -$char->setStat("dexterity", 30); -$char->setStat("vitality", 30); -$char->setStat("mana", 120); -$char->setStat("maxmana", 200); -$char->setStat("stamina", 80); -$char->setStat("maxstamina", 120); +//$char->setChar("CharacterClass", "Necromancer"); // 127 +//$char->setChar("CharacterProgression", 1); // 0 in normal, 1 finished normal, 2 finished nm, 3 finished hell +$char->setChar("CharacterLevel", 25); +//$char->setStat("strength", 70); +//$char->setStat("energy", 70); +//$char->setStat("dexterity", 70); +//$char->setStat("vitality", 70); +//$char->setStat("mana", 200); +//$char->setStat("maxmana", 200); +//$char->setStat("soulcounter", 80); +// +//$char->setStat("hitpoints", 70); +//$char->setStat("maxhp", 70); +//$char->setStat("stamina", 70); +//$char->setStat("maxstamina", 200); +// +// +//$char->setStat("gold", 0); +//$char->setStat("soulcounter", 40273409479012734098712903479012374091827349081273490172093478); + + unset($char); // destroy $char so we can read it again after writing to it to get updated stats $char = new D2Char($filePath); -var_dump($char->cData['stats']); \ No newline at end of file +var_dump($char->cData); diff --git a/bin/d2cr.exe b/bin/d2cr.exe new file mode 100644 index 0000000..5ab26f0 Binary files /dev/null and b/bin/d2cr.exe differ diff --git a/bin/d2sgcs.exe b/bin/d2sgcs.exe deleted file mode 100644 index 7e9ec98..0000000 Binary files a/bin/d2sgcs.exe and /dev/null differ diff --git a/img/bg.png b/img/bg.png new file mode 100644 index 0000000..5b53ecc Binary files /dev/null and b/img/bg.png differ diff --git a/ironman-dev.sqbpro b/ironman-dev.sqbpro new file mode 100644 index 0000000..f259bc1 --- /dev/null +++ b/ironman-dev.sqbpro @@ -0,0 +1,11 @@ +
CREATE INDEX m_code ON misc (code); +DROP INDEX m_codeSELECT a.code,a.invfile,a.uniqueinvfile,a.type,a.type2,i.VarInvGfx,i.InvGfx1 FROM armor as a, itemtypes as i LEFT JOIN itemtypes on a.type = i.code +-- UNION ALL +-- SELECT a.code,a.invfile,a.uniqueinvfile,a.type,a.type2 FROM misc as a LEFT JOIN itemtypes on a.type = itemtypes.code +-- UNION ALL +-- SELECT a.code,a.invfile,a.uniqueinvfile,a.type,a.type2 FROM weapons as a LEFT JOIN itemtypes on a.type = itemtypes.code +SELECT InvGfx1 FROM itemtypes WHERE Code = 'cm3'SELECT sk.id,sk.Skill,sk.skilldesc,`str name`,strings.`String` FROM skills as sk +LEFT JOIN skilldesc ON sk.skilldesc = skilldesc.skilldesc +LEFT JOIN strings on skilldesc.`str name` = strings.Key +WHERE sk.charclass = 'ama'PRAGMA table_info(experience); +SELECT Amazon FROM experience WHERE level = 1-1
diff --git a/src/D2BitReader.php b/src/D2BitReader.php index 68d8c93..70c61a4 100644 --- a/src/D2BitReader.php +++ b/src/D2BitReader.php @@ -1,15 +1,31 @@ bits = $bits; return true; } + /** + * @param int $numBits + * @return int + */ public function skip(int $numBits): int { $this->offset += $numBits; return $this->offset; @@ -17,6 +33,11 @@ class D2BitReader { /* read X number of bits, like fread */ + /** + * @param int $numBits + * @param bool $str + * @return string + */ public function read(int $numBits = 0, bool $str = true): string { $bits = null; for ($i = $this->offset; $i < $this->offset + $numBits; $i++) { @@ -26,10 +47,21 @@ class D2BitReader { return $bits; } + /** + * @param string $bits + * @param string $bitsToWrite + * @param int $offset + * @return array|string|string[] + */ public function writeBits(string $bits, string $bitsToWrite, int $offset) { return substr_replace($bits, $bitsToWrite, $offset, strlen($bitsToWrite)); } + /** + * @param int $numBits + * @param bool $str + * @return string + */ public function readb(int $numBits = 0, bool $str = true): string { $bits = null; for ($i = $this->offset; $i < $this->offset + $numBits; $i++) { @@ -39,6 +71,10 @@ class D2BitReader { return strrev(str_pad($bits, 16, 0, STR_PAD_RIGHT)); } + /** + * @param int $numBits + * @return string + */ public function readr(int $numBits = 0): string { $bits = null; for ($i = $this->offset; $i < $this->offset + $numBits; $i++) { @@ -50,6 +86,10 @@ class D2BitReader { /* seek to offset (like fseek) */ + /** + * @param int $pos + * @return bool + */ public function seek(int $pos): bool { if ($pos < 0 || $pos > strlen($this->bits)) { return false; @@ -60,6 +100,9 @@ class D2BitReader { /* rewind offset to 0 */ + /** + * @return bool + */ public function rewind(): bool { $this->offset = 0; return true; @@ -67,6 +110,10 @@ class D2BitReader { /* Get Bit */ + /** + * @param string $bitNum + * @return int + */ public function getBit(string $bitNum): int { if ($bitNum < 0 || $bitNum > strlen($this->bits)) { return false; @@ -76,6 +123,11 @@ class D2BitReader { /* Set Bit */ + /** + * @param int $bitNum + * @param int $bitVal + * @return bool + */ public function setBit(int $bitNum, int $bitVal): bool { if ($bitVal < 0 || $bitVal > 1) { return false; @@ -84,18 +136,32 @@ class D2BitReader { return true; } + /** + * @return string + */ public function getBits(): string { return $this->bits; } + /** + * @param string $bits + * @return void + */ public function setBits(string $bits) { $this->bits = $bits; } + /** + * @return int + */ public function getOffset(): int { return $this->offset; } + /** + * @param int $offset + * @return bool + */ public function setOffset(int $offset): bool { if ($offset < 0 || $offset > strlen($this->bits)) return false; diff --git a/src/D2ByteReader.php b/src/D2ByteReader.php index 94b66d4..4c087bd 100644 --- a/src/D2ByteReader.php +++ b/src/D2ByteReader.php @@ -3,17 +3,33 @@ require_once './src/D2Functions.php'; require_once './src/D2BitReader.php'; +/** + * + */ class D2ByteReader { + /** + * @var string + */ private string $data = ''; + /** + * @var int + */ private int $offset = 0; + /** + * @param string $data + */ public function __construct(string $data) { if (!$data) return false; $this->data = $data; } + /** + * @param int $numBytes + * @return bool + */ public function skip(int $numBytes): bool { if ($numBytes < 0 || $numBytes > strlen($this->data)) return false; @@ -21,6 +37,10 @@ class D2ByteReader { return $true; } + /** + * @param int $pos + * @return bool + */ public function seek(int $pos): bool { if ($pos < 0 || $pos > strlen($this->data)) return false; @@ -28,6 +48,12 @@ class D2ByteReader { return true; } + /** + * @param int $offset + * @param int $numBytes + * @param bool $str + * @return string + */ public function readh(int $offset, int $numBytes, bool $str = true): string { $this->seek($offset); $bytes = null; @@ -37,6 +63,12 @@ class D2ByteReader { return unpack('H*', $bytes)[1]; } + /** + * @param int $offset + * @param int $numBytes + * @param bool $str + * @return array + */ public function readc(int $offset, int $numBytes, bool $str = true): array { $this->seek($offset); $bytes = null; @@ -46,15 +78,28 @@ class D2ByteReader { return unpack('C*', $bytes); } + /** + * @return bool + */ public function rewind(): bool { $this->offset = 0; return true; } + /** + * @param int $offset + * @param int $byte + * @return void + */ public function writeByte(int $offset, int $byte) { $this->data[$offset] = pack('C', $byte); } + /** + * @param int $offset + * @param string $bytes + * @return false|void + */ public function writeBytes(int $offset, string $bytes) { if ($offset < 0 || $offset > strlen($this->data) || $bytes == '') return false; @@ -64,26 +109,46 @@ class D2ByteReader { $this->data[$pos] = pack('H*', $byte); } } + + /** + * @param int $offset + * @param string $bytes + * @return false|void + */ public function insertBytes(int $offset, string $bytes) { if ($offset < 0 || $offset > strlen($this->data) || $bytes == '') return false; $data = bin2hex($this->data); // convert to hex bytes string $newData = substr_replace($data, $bytes, $offset, 0); $this->data = hex2bin($newData); - } + } + /** + * @return false|string + */ public function getData() { return $this->data ? $this->data : false; } - + + /** + * @param $data + * @return void + */ public function setData($data){ $this->data = $data; } + /** + * @return int + */ public function getOffset(): int { return $this->offset; } + /** + * @param string $str + * @return bool + */ public function isHexString(string $str): bool { if (strlen($str) % 2 == 0 && (ctype_xdigit($str))) { return true; @@ -91,6 +156,10 @@ class D2ByteReader { return false; } + /** + * @param $input + * @return string + */ public function toBits($input): string { $output = ''; if ($this->isHexString($input)) { @@ -113,13 +182,21 @@ class D2ByteReader { } } + /** + * @param string $bits + * @return string + */ public function toBytesR(string $bits) : string { foreach (str_split($bits, 8) as $byteString) { $bytes .= strtoupper(str_pad(dechex(bindec(($byteString))), 2, 0, STR_PAD_LEFT)); } return $bytes; } - + + /** + * @param string $bits + * @return string + */ public function toBytes(string $bits) : string { foreach (str_split($bits, 8) as $byteString) { $bytes .= strtoupper(str_pad(dechex(bindec(strrev($byteString))), 2, 0, STR_PAD_LEFT)); @@ -127,6 +204,10 @@ class D2ByteReader { return $bytes; } + /** + * @param string $bits + * @return string + */ public function bitsToHexString(string $bits): string { $bytes = ''; foreach (str_split($bits, 8) as $byte) { @@ -135,6 +216,10 @@ class D2ByteReader { return $bytes; } + /** + * @param string $bits + * @return array + */ public function bitsToHexArray(string $bits): array { $bytes = []; foreach (str_split($bits, 8) as $byte) { @@ -143,6 +228,10 @@ class D2ByteReader { return $bytes; } + /** + * @param string $bits + * @return array + */ public function bitsToIntArray(string $bits): array { $bytes = []; foreach (str_split($bits, 8) as $byte) { @@ -151,18 +240,21 @@ class D2ByteReader { return $bytes; } - /* - @return Byte with Nth bit set to X + /** + * @param int $byte + * @param int $pos + * @param bool $bit + * @return int */ - public function setBit(int $byte, int $pos, bool $bit) { return ($bit ? ($byte | (1 << $pos)) : ($byte & ~(1 << $pos)) ); } - /* - @return Bit at Nth position in Byte + /** + * @param int $byte + * @param int $pos + * @return int */ - public function getBit(int $byte, int $pos): int { return intval(($byte & (1 << $pos)) != 0); } diff --git a/src/D2Char.php b/src/D2Char.php index 6332c69..a4fdc75 100644 --- a/src/D2Char.php +++ b/src/D2Char.php @@ -8,24 +8,70 @@ require_once 'D2Item.php'; require_once 'D2ByteReader.php'; require_once 'D2Functions.php'; +/** + * + */ class D2Char { + /** + * @var null + */ public $cData = null; // char data output + /** + * @var null + */ public $items = null; // char item data + /** + * @var D2ByteReader|null + */ public $ByteReader = null; // put $data into bytereader + /** + * @var string|null + */ public $filePath = null; // .d2s file path + /** + * @var D2CharStructureData + */ private $sData = null; // char file structure data + /** + * @var null + */ private $bData = null; // char binary data from d2s + /** + * @var false|resource + */ private $fp = null; // file pointer + /** + * @var false|string + */ private $data = null; // full d2s file loaded in $data + /** + * @var null + */ private $ISC = null; + /** + * @var null + */ private $skillData = null; + /** + * @return void + */ public function save() { - file_put_contents($this->filePath, $this->data); - checksumFix($this->filePath); + $this->ByteReader->setData($this->data); // update bytereader data + $this->ByteReader->writeBytes(12, "00000000"); // clear old checksum + $this->data = $this->ByteReader->getData(); // update this data to what we get from bytereader after clearing checksum + $checksum = checksum(unpack('C*', $this->data)); // get new checksum + + $this->ByteReader->setData($this->data); // update bytereader data + $this->ByteReader->writeBytes(12, $checksum); // write new checksum + $this->data = $this->ByteReader->getData(); // update this data + file_put_contents($this->filePath, $this->data); // write file } + /** + * @param $file + */ public function __construct($file) { $this->sData = new D2CharStructureData(); $this->filePath = $_SESSION['savepath'] . $file; @@ -52,7 +98,7 @@ class D2Char { $classes = array_flip(['ama' => 0, 'sor' => 1, 'nec' => 2, 'pal' => 3, 'bar' => 4, 'dru' => 5, 'ass' => 6]); $class = $classes[hexdec($this->ByteReader->readh(40, 1))]; $sql = "SELECT sk.id,sk.Skill,sk.skilldesc,`str name`,strings.`String`,skilldesc.SkillPage,skilldesc.SkillRow,skilldesc.SkillColumn,skilldesc.ListRow,skilldesc.ListPool,skilldesc.IconCel - FROM skills as sk +FROM skills as sk LEFT JOIN skilldesc ON sk.skilldesc = skilldesc.skilldesc LEFT JOIN strings on skilldesc.`str name` = strings.Key WHERE sk.charclass = '$class'"; @@ -64,6 +110,9 @@ WHERE sk.charclass = '$class'"; return $this->parseChar(); // end of parseChar() calls parseItems(), parseStats, etc. } + /** + * @return void + */ public function parseItems() { $i_TotalOffset = strpos($this->data, "JM"); fseek($this->fp, $i_TotalOffset + 2); @@ -83,6 +132,9 @@ WHERE sk.charclass = '$class'"; } } + /** + * @return array|null + */ public function parseChar() { $cData = null; $cData['Identifier'] = bin2hex($this->bData[0]); @@ -90,30 +142,46 @@ WHERE sk.charclass = '$class'"; $cData['VersionID'] = ($this->sData->version[unpack('l', $this->bData[4])[1]]); // 1.41 KB (1,447 bytes) - checks out - $cData['Filesize'] = round(unpack('l', $this->bData[8])[1] / 1024, 2) . " KB"; + //$cData['Filesize'] = round(unpack('l', $this->bData[8])[1] / 1024, 2) . " KB"; + $cData['Filesize'] = unpack('L', $this->bData[8])[1]; $cData['Checksum'] = bin2hex($this->bData[12]); - $cData['Activeweapon'] = unpack('l', $this->bData[16]); + $cData['Activeweapon'] = unpack('L', $this->bData[16]); $cData['CharacterName'] = str_replace("\0", "", $this->bData[20]); - $cData['CharacterStatus'] = array_filter(str_split(strtobits($this->bData[36]))); - foreach ($cData['CharacterStatus'] as $k => $v) { - $str .= ($characterStatus[$k]) . " "; + $characterStatus = array_filter(str_split(strrev(strtobits($this->bData[36])))); + foreach ($characterStatus as $k => $v) { + $str .= $this->sData->characterStatus[$k] . " "; } - $cData['CharacterStatus'] = $str; - $cData['Characterprogression'] = bindec($this->bData[37]); + $cData['CharacterStatus'] = trim($str); + + $progression = hexdec(bin2hex($this->bData[37])); + $cData['CharacterProgression'] = $this->sData->characterProgressionClassic[$progression]; + + if ($cData['CharacterStatus'] == 'Hardcore Expansion') { + $cData['CharacterProgression'] = $this->sData->characterProgressionExpHC[$progression]; + } + if ($cData['CharacterStatus'] == "Expansion") { + $cData['CharacterProgression'] = $this->sData->characterProgressionExp[$progression]; + } + if ($cData['CharacterStatus'] == "Hardcore") { + $cData['CharacterProgression'] = $this->sData->characterProgressionClassicHC[$progression]; + } + + $cData['CharacterClass'] = $this->sData->class[unpack('C', $this->bData[40])[1]]; $cData['CharacterLevel'] = unpack('C', $this->bData[43])[1]; - $cData['Lastplayed'] = gmdate("Y-m-d\TH:i:s\Z", unpack('I', $this->bData[48])[0]); + $cData['Lastplayed'] = gmdate("Y-m-d\TH:i:s\Z", unpack('L', $this->bData[48])[0]); $skills = (unpack('l16', $this->bData[56])); + // Hotkey assigned skills foreach ($skills as $skill) { $cData['Assignedskills'][] = ($this->sData->skills[$skill]); $cData['Assignedskills'] = array_filter($cData['Assignedskills']); } - $cData['LeftmousebuttonskillID'] = $this->sData->skills[unpack('i', $this->bData[120])[1]]; - $cData['RightmousebuttonskillID'] = $this->sData->skills[unpack('i', $this->bData[124])[1]]; - $cData['LeftswapmousebuttonskillID'] = $this->sData->skills[unpack('i', $this->bData[128])[1]]; - $cData['RightswapmousebuttonskillID'] = $this->sData->skills[unpack('i', $this->bData[132])[1]]; + $cData['LeftmousebuttonskillID'] = $this->sData->skills[unpack('L', $this->bData[120])[1]]; + $cData['RightmousebuttonskillID'] = $this->sData->skills[unpack('L', $this->bData[124])[1]]; + $cData['LeftswapmousebuttonskillID'] = $this->sData->skills[unpack('L', $this->bData[128])[1]]; + $cData['RightswapmousebuttonskillID'] = $this->sData->skills[unpack('L', $this->bData[132])[1]]; // Char menu appearance not needed // $cData['Charactermenuappearance'] = unpack('i', $this->bData[136]); @@ -128,7 +196,7 @@ WHERE sk.charclass = '$class'"; // found in the character.map file, according to the difficulty being played. Not needed //$cData['MapID'] = $this->bData[171]; - $cData['MercenaryDead'] = unpack('i', $this->bData[177])[1]; + $cData['MercenaryDead'] = unpack('i-', $this->bData[177])[1]; // This looks like a random ID for your mercenary. // $cData['MercenaryID'] = unpack('H*', $this->bData[179]); $cData['MercenaryNameID'] = unpack('S', $this->bData[183])[1]; @@ -149,13 +217,16 @@ WHERE sk.charclass = '$class'"; $this->cData['skills'] = $this->parseSkills(); unset($this->items); - unset($this->bData); + //unset($this->bData); unset($this->sData); unset($this->fp); return $this->cData; } + /** + * @return array + */ public function parseSkills() { $if = strposX($this->data, 'if', 1) + 2; // find if and skip it $jm = strposX($this->data, 'JM', 1); @@ -177,6 +248,10 @@ WHERE sk.charclass = '$class'"; return $cData; } + /** + * @param int $points + * @return void + */ public function setAllSkills(int $points) { $if = strposX($this->data, 'if', 1) + 2; // find if and skip it $jm = strposX($this->data, 'JM', 1); @@ -188,6 +263,11 @@ WHERE sk.charclass = '$class'"; $this->save(); } + /** + * @param int $skill + * @param int $points + * @return void + */ public function setSkill(int $skill, int $points) { $skill -= 1; $if = strposX($this->data, 'if', 1) + 2; // find if and skip it @@ -198,6 +278,9 @@ WHERE sk.charclass = '$class'"; $this->save(); } + /** + * @return void + */ public function parseStats() { $gf = strposX($this->data, 'gf', 1) + 2; // find gf and skip it $if = strposX($this->data, 'if', 1); @@ -205,13 +288,13 @@ WHERE sk.charclass = '$class'"; $stats = new D2BitReader($this->ByteReader->toBits($this->ByteReader->readh($gf, $len))); $bits = $stats->getBits(); - - $bytes = $this->ByteReader->toBytes($bits); + + $bytes = $this->ByteReader->toBytes($bits); $stats->rewind(); for ($i = 0; $i <= strlen($bits); $i++) { $id = hexdec($this->ByteReader->toBytesR($stats->readb(9))); - if ($this->ISC[$id]['CSvBits'] !== NULL) { + if ($this->ISC[$id]['CSvBits'] !== NULL && $this->ISC[$id]['CSvBits'] !== '') { $stats->skip($this->ISC[$id]['CSvBits']); } $ids[$id] = $id; @@ -219,11 +302,11 @@ WHERE sk.charclass = '$class'"; $stats->rewind(); foreach ($ids as $id) { $stats->skip(9); - if ($this->ISC[$id]['CSvBits'] !== NULL) { + if ($this->ISC[$id]['CSvBits'] !== NULL && $this->ISC[$id]['CSvBits'] !== '') { $val = $stats->readb($this->ISC[$id]['CSvBits']); + $stat = $this->ISC[$id]['Stat']; + $values[$stat] = hexdec($this->ByteReader->toBytesR($val)); } - $stat = $this->ISC[$id]['Stat']; - $values[$stat] = hexdec($this->ByteReader->toBytesR($val)); } $values['hitpoints'] = (int) round($values['hitpoints'] >> 11); $values['maxhp'] = (int) round($values['maxhp'] >> 11); @@ -231,12 +314,19 @@ WHERE sk.charclass = '$class'"; $values['maxmana'] = (int) round($values['maxmana'] >> 11); $values['stamina'] = (int) round($values['stamina'] >> 11); $values['maxstamina'] = (int) round($values['maxstamina'] >> 11); - $values['killcounter'] = (int) round($values['killcounter'] >> 1); + $values['soulcounter'] = (int) round($values['soulcounter'] / 2); + $values['killcounter'] = (int) round($values['killcounter'] / 2); $this->cData['stats'] = $values; } - public function setChar(string $stat, mixed $val) { + /** + * @param string $stat + * @param mixed $val + * @param mixed|null $val2 + * @return false|void + */ + public function setChar(string $stat, mixed $val, mixed $val2 = null) { switch ($stat) { case 'CharacterName': if (strlen($val) < 1 || strlen($val) > 15) { @@ -244,15 +334,20 @@ WHERE sk.charclass = '$class'"; } $pack = $this->ByteReader->bitsToHexString($this->ByteReader->toBits(pack('Z16', $val))); $this->ByteReader->writeBytes(20, $pack); - $this->data = $this->ByteReader->getData(); - $this->save(); rename($this->filePath, $_SESSION['savepath'] . "$val.d2s"); break; case "CharacterClass": - $this->ByteReader->writeByte(40, $val); - $this->data = $this->ByteReader->getData(); - $this->save(); + $classes = [ + 'Amazon' => 0, + 'Sorceress' => 1, + 'Necromancer' => 2, + 'Paladin' => 3, + 'Barbarian' => 4, + 'Druid' => 5, + 'Assassin' => 6 + ]; + $this->ByteReader->writeByte(40, $classes[$val]); break; case "CharacterLevel": if ($val > 99) { @@ -271,26 +366,111 @@ WHERE sk.charclass = '$class'"; $res = PDO_FetchOne($sql); $this->setStat('experience', $res); break; + case 'CharacterStatus': + switch ($val) { + case 'Died': + $status = (strtobits($this->data[36])); + $status[3] = $val2; + $byte = $this->ByteReader->bitsToHexString($status); + $this->ByteReader->writeByte(36, hexdec($byte)); + break; + case 'Hardcore': + $status = (strtobits($this->data[36])); + $status[2] = $val2; + $byte = $this->ByteReader->bitsToHexString($status); + $this->ByteReader->writeByte(36, hexdec($byte)); + break; + case 'Expansion': + $status = strrev(strtobits($this->data[36])); + $status[5] = $val2; + $byte = $this->ByteReader->bitsToHexString($status); + $this->ByteReader->writeByte(36, hexdec($byte)); + break; + } + break; + case 'CharacterProgression': // 0 in normal, 1 finished normal, 2 finished nm, 3 finished hell + switch ($val) { + case 0: // in normal + $this->data[37] = pack('C', 3); + break; + case 1: // finished normal + $this->data[37] = pack('C', 8); + break; + case 2: // finished nm + $this->data[37] = pack('C', 13); + break; + case 3: // finished hell + $this->data[37] = pack('C', 15); + break; + $this->save(); + } + break; + + case 'LeftmousebuttonskillID': + $this->ByteReader->writeBytes(120, dechex($val)); + break; + + case 'RightmousebuttonskillID': + $this->ByteReader->writeBytes(124, dechex($val)); + break; + + case 'LeftswapmousebuttonskillID': + $this->ByteReader->writeBytes(128, dechex($val)); + break; + + case 'RightswapmousebuttonskillID': + $this->ByteReader->writeBytes(132, dechex($val)); + break; } + + // finally save char data to d2s file + $this->data = $this->ByteReader->getData(); + $this->save(); } + /** + * @return void + */ + public function resetFileSize() { + $filesize = strlen($this->data); + $this->fp = fopen($this->filePath, "r+b"); + fseek($this->fp, 8); + fwrite($this->fp, pack('L', $filesize)); + + fclose($this->fp); + $this->fp = fopen($this->filePath, "r+b"); + checksumFix($this->filePath); + + fseek($this->fp, 8); + //ddump(unpack('L', fread($this->fp, 4))[1]); + } + + /** + * @return false|string + */ public function generateAllStats() { // 003C08E081000F067860C001071C0008020800F040020064A000000A2C00C0030C000D000000006E05000000FE3F $stats = ''; - foreach ($this->ISC as $i) { - $id = strrev(str_pad((decbin($i['ID'])), 9, 0, STR_PAD_LEFT)); - $val = strrev(str_pad((decbin(20)), (int) $i['CSvBits'], 0, STR_PAD_LEFT)); + for ($i = 0; $i < 16; $i++) { + $id = strrev(str_pad((decbin((int) $this->ISC[$i]['ID'])), 9, 0, STR_PAD_LEFT)); + $val = strrev(str_pad((decbin(20)), (int) $this->ISC[$i]['CSvBits'], 0, STR_PAD_LEFT)); + +// dump($id); +// dump($val); + $stat = $id . $val; $stats .= $stat; } - + $stats .= "000011111111100"; + + dump($stats); + $gf = strposX($this->data, 'gf', 1) + 2; // find gf and skip it $if = strposX($this->data, 'if', 1); $len = $if - $gf; - $statall = $stats . "11111111100"; - $bytes = $this->ByteReader->toBytes($statall); - + $bytes = $this->ByteReader->toBytes($stats); + // refresh this data $data = $this->ByteReader->getData(); // delete everything between GF---and---IF @@ -303,19 +483,27 @@ WHERE sk.charclass = '$class'"; $this->data = $data; $this->save(); + $this->resetFileSize(); + $filedata = file_get_contents($this->filePath); $this->ByteReader = new D2ByteReader($filedata); $this->data = $this->ByteReader->getData(); + return $this->data; } + /** + * @param string $stat + * @param mixed $val + * @return void + */ public function setStat(string $stat, mixed $val) { $gf = strposX($this->data, 'gf', 1) + 2; // find gf and skip it $if = strposX($this->data, 'if', 1); $len = $if - $gf; $stats = new D2BitReader($this->ByteReader->toBits($this->ByteReader->readh($gf, $len))); - $bits = $stats->getBits(); - + $bits = $stats->getBits(); + $cleanbits = substr($bits, 0, -11); $stats->setBits($cleanbits); $bits = $stats->getBits(); @@ -324,7 +512,9 @@ WHERE sk.charclass = '$class'"; for ($i = 0; $i <= strlen($bits); $i++) { $id = hexdec($this->ByteReader->toBytesR($stats->readb(9))); $_offsets[$id] = $stats->getOffset(); - $stats->skip($this->ISC[$id]['CSvBits']); + if ($this->ISC[$id]['CSvBits'] !== null && $this->ISC[$id]['CSvBits'] !== '') { + $stats->skip($this->ISC[$id]['CSvBits']); + } } $_offsets[0] = 9; $offsets = null; @@ -332,7 +522,9 @@ WHERE sk.charclass = '$class'"; $_stat = $this->ISC[$k]['Stat']; $_stats[$_stat] = $this->ISC[$k]['Stat']; $csvbits[$_stat] = $this->ISC[$k]['CSvBits']; - $maxValues[$_stat] = pow(2, $this->ISC[$k]['CSvBits']) - 1; + if ($this->ISC[$k]['CSvBits'] !== null && $this->ISC[$k]['CSvBits'] !== '') { + $maxValues[$_stat] = pow(2, $this->ISC[$k]['CSvBits']) - 1; + } $offsets[$_stat] = $v; } if ($stat == 'hitpoints' || $stat == 'maxhp' || $stat == 'mana' || $stat == 'maxmana' || $stat == 'stamina' || $stat == 'maxstamina') { @@ -344,14 +536,22 @@ WHERE sk.charclass = '$class'"; } $bitsToWrite = strrev(str_pad(decbin(intval($val)), $csvbits[$_stats[$stat]], 0, STR_PAD_LEFT)); $statOffset = $offsets[$_stats[$stat]]; - $newBits = $stats->writeBits($bits, $bitsToWrite, $statOffset) . "11111111100"; // 0x1FF padding - $stats->setBits($newBits); - $bytes = $this->ByteReader->toBytes($newBits); - $this->ByteReader->writeBytes($gf, $bytes); - $this->data = $this->ByteReader->getData(); - $this->save(); + if (!array_key_exists($stat, $_stats)) { + $this->ByteReader->toBits($this->generateAllStats()); + } else { + $newBits = $stats->writeBits($bits, $bitsToWrite, $statOffset) . "11111111100"; // 0x1FF padding + $stats->setBits($newBits); + $bytes = $this->ByteReader->toBytes($newBits); + $this->ByteReader->writeBytes($gf, $bytes); + $this->data = $this->ByteReader->getData(); + $this->save(); + } } + /** + * @param $file + * @return array + */ public function getQuestData($file) { $questsNorm = null; $questsNM = null; @@ -390,6 +590,10 @@ WHERE sk.charclass = '$class'"; return $quests; } + /** + * @param $file + * @return array + */ public function getWaypointsData($file) { $wp = null; fseek($this->fp, $this->sData->wpOffsetsNorm); diff --git a/src/D2CharItem.php b/src/D2CharItem.php index 29f8207..8a64b0a 100644 --- a/src/D2CharItem.php +++ b/src/D2CharItem.php @@ -2,84 +2,306 @@ require_once 'D2BitReader.php'; +/** + * + */ class D2CharItem { + /** + * @var string + */ private string $bits; + /** + * @var string + */ public $basename = ''; //name of the base item + /** + * @var string + */ public $item_name = ''; //name string + /** + * @var string + */ public $item_rank = ''; //normal/exceptional/elite + /** + * @var string + */ public $item_type = ''; //basic type: armor/weapon/misc //flags + /** + * @var int + */ public $identified = 0; + /** + * @var int + */ public $sockets = 0; + /** + * @var int + */ public $ear = 0; + /** + * @var int + */ public $starter = 0; + /** + * @var int + */ public $compact = 0; + /** + * @var + */ public $ethereal; + /** + * @var + */ public $personalized; + /** + * @var + */ public $runeword; + /** + * @var + */ public $version; + /** + * @var string + */ public $runeword_name = ''; + /** + * @var string + */ public $runes_title = ''; //placement + /** + * @var int + */ public $location = 0; + /** + * @var int + */ public $body = 0; + /** + * @var int + */ public $col = 0; + /** + * @var int + */ public $row = 0; + /** + * @var int + */ public $container = 0; + /** + * @var + */ public $parent; + /** + * @var + */ public $storage; + /** + * @var + */ public $bodypart; + /** + * @var int + */ public $invH = 0; + /** + * @var int + */ public $invW = 0; + /** + * @var int + */ public $beltrows = 1; //features + /** + * @var string + */ public $item_code = ''; //3 letter code from txt + /** + * @var int + */ public $SocketsFilled = 0; + /** + * @var int + */ public $SocketsNum = 0; + /** + * @var bool + */ public $socketable = false; //gem, rune, jewel + /** + * @var string + */ public $fingerprint = ''; + /** + * @var int + */ public $itemlvl = 0; //item level + /** + * @var int + */ public $quality = 0; + /** + * @var bool + */ public $isCharm = false; + /** + * @var bool + */ public $isJewel = false; + /** + * @var string + */ public $magic_rank = 'Normal'; //normal/magic/rare/crafted/set/unique + /** + * @var int + */ public $set_id = 0; //set item id from txt + /** + * @var string + */ public $set_item = ''; + /** + * @var int + */ public $set_name = 0; //set name, if it is set item, + /** + * @var string + */ public $personname = ''; + /** + * @var int + */ public $questdif = -1; + /** + * @var + */ public $gold; + /** + * @var string + */ public $GUID = ''; + /** + * @var int + */ public $defense = 0; + /** + * @var int + */ public $mindam = 0; + /** + * @var int + */ public $maxdam = 0; + /** + * @var int + */ public $mindam2 = 0; + /** + * @var int + */ public $maxdam2 = 0; + /** + * @var int + */ public $mindammi = 0; + /** + * @var int + */ public $maxdammi = 0; + /** + * @var int + */ public $MaxDur = 0; + /** + * @var int + */ public $CurDur = 0; + /** + * @var int + */ public $reqlvl = 0; + /** + * @var int + */ public $reqstr = 0; + /** + * @var int + */ public $reqdex = 0; + /** + * @var int + */ public $speed = 0; + /** + * @var int + */ public $throwing = 0; + /** + * @var int + */ public $stackable = 0; + /** + * @var string + */ public $charName = ''; //ear's name + /** + * @var + */ public $gfx; //graphic file name + /** + * @var int + */ public $baseTrans = -1; //transform indexes for colour remap base item + /** + * @var int + */ public $magicTrans = -1; //transform indexes for colour remap magic item + /** + * @var + */ public $type; //type column from txt + /** + * @var string + */ public $spelldesc = ''; //desc for potions + /** + * @var + */ public $ditem; //link to item properties from txt //mods + /** + * @var int + */ public $dammult = 100; //damage multiply + /** + * @var int + */ public $damminadd = 0; //damage add + /** + * @var int + */ public $dammaxadd = 0; //damage add + /** + * @var int + */ public $defmult = 100; //defense multiply + /** + * @var int + */ public $defadd = 0; //defense multiply + /** + * @var int[] + */ public $resist = array(0, 0, 0, 0, 0, 0); //phy, mag, fire, light, cold, poison + /** + * @var int[] + */ public $attributes = array(0, 0, 0, 0); //str, dex, vit, ene //arrays @@ -87,18 +309,33 @@ class D2CharItem { //socketed items, collected in above function, //because item has only data for itselt, and //gems/runes/jewels are standalone - public $SocketItems = array(); + /** + * @var array + */ + public $SocketItems = array(); + /** + * @var array + */ public $properties = array(); //item variable properties + /** + * @var array + */ public $propids = array(); //properties ids list - + + /** + * @param string $bits + */ public function __construct(string $bits){ if ($bits == '') return false; $this->bits = $bits; return $this->parseItem(); } - + + /** + * @return void + */ public function parseItem(){ } diff --git a/src/D2CharStructureData.php b/src/D2CharStructureData.php index 83a5578..975537f 100644 --- a/src/D2CharStructureData.php +++ b/src/D2CharStructureData.php @@ -7,12 +7,24 @@ ini_set('max_execution_time', '0'); session_start(); ob_start(); +/** + * + */ define('DB_FILE', $_SESSION['modname'] . ".db"); PDO_Connect("sqlite:" . DB_FILE); +/** + * + */ class D2CharStructureData { + /** + * @var + */ public $skills; + /** + * @var string[] + */ public $class = [ 0 => 'Amazon', 1 => 'Sorceress', @@ -22,6 +34,9 @@ class D2CharStructureData { 5 => 'Druid', 6 => 'Assassin' ]; + /** + * @var string[] + */ public $characterStatus = [ 0 => '', 1 => '', @@ -32,6 +47,93 @@ class D2CharStructureData { 6 => 'Ladder', 7 => '' ]; + /** + * @var string[] + */ + public $characterProgressionClassicHC = [ + 0 => '', + 1 => '', + 2 => '', + 3 => '', + 4 => 'Count / Countess', + 5 => 'Count / Countess', + 6 => 'Count / Countess', + 7 => 'Count / Countess', + 8 => 'Duke / Duchess', + 9 => 'Duke / Duchess', + 10 => 'Duke / Duchess', + 11 => 'Duke / Duchess', + 12 => 'Duke / Duchess', + 13 => '', + 14 => '', + 15 => '', + ]; + /** + * @var string[] + */ + public $characterProgressionClassic = [ + 0 => '', + 1 => '', + 2 => '', + 3 => '', + 4 => 'Sir / Dame', + 5 => 'Sir / Dame', + 6 => 'Sir / Dame', + 7 => 'Sir / Dame', + 8 => 'Lord / Lady', + 9 => 'Lord / Lady', + 10 => 'Lord / Lady', + 11 => 'Lord / Lady', + 12 => 'Baron / Baroness', + 13 => '', + 14 => '', + 15 => '', + ]; + /** + * @var string[] + */ + public $characterProgressionExp = [ + 0 => '', + 1 => '', + 2 => '', + 3 => '', + 4 => '', + 5 => 'Slayer', + 6 => 'Slayer', + 7 => 'Slayer', + 8 => 'Slayer', + 9 => '', + 10 => 'Champion', + 11 => 'Champion', + 12 => 'Champion', + 13 => 'Champion', + 14 => 'Patriarch / Matriarch ', + 15 => 'Patriarch / Matriarch ', + ]; + /** + * @var string[] + */ + public $characterProgressionExpHC = [ + 0 => '', + 1 => '', + 2 => '', + 3 => '', + 4 => '', + 5 => 'Destroyer', + 6 => 'Destroyer', + 7 => 'Destroyer', + 8 => 'Destroyer', + 9 => '', + 10 => 'Conqueror', + 11 => 'Conqueror', + 12 => 'Conqueror', + 13 => 'Conqueror', + 14 => 'Guardian', + 15 => 'Guardian', + ]; + /** + * @var int[] + */ public $offsets = [ 0 => 4, // Identifier 4 => 4, // Version ID @@ -67,6 +169,9 @@ class D2CharStructureData { 633 => 81, // Waypoints 714 => 51, // NPC Introductions ]; + /** + * @var string[] + */ public $qNorm = [ 345 => 'introWarriv', 347 => 'Den_Of_Evil', @@ -95,14 +200,14 @@ class D2CharStructureData { 393 => 'introToAct4', 395 => 'The_Fallen_Angel', 397 => 'Terrors_End', - 399 => 'Hell_Forge', + 399 => 'Hell_Forge', 401 => 'traveledToAct5', //403 => 'empty31', //405 => 'empty32', //407 => 'empty33', 409 => 'completedTerrorsEnd', //411 => 'empty21', - //413 => 'empty22', + //413 => 'empty22', 415 => 'Siege_On_Harrogath', 417 => 'Rescue_On_MountArreat', 419 => 'Prison_Of_Ice', @@ -110,8 +215,10 @@ class D2CharStructureData { 423 => 'Rite_Of_Passage', 425 => 'Eve_Of_Destruction', // read 425, pointer at 427, + 14 = 441 qNM offset - ]; + /** + * @var string[] + */ public $qNM = [ 441 => 'introWarrivNM', 443 => 'Den_Of_Evil_NM', @@ -147,7 +254,7 @@ class D2CharStructureData { //503 => 'empty33', 505 => 'completedTerrorsEnd', //507 => 'empty21', - //509 => 'empty22', + //509 => 'empty22', 511 => 'Siege_On_Harrogath', 513 => 'Rescue_On_MountArreat', 515 => 'Prison_Of_Ice', @@ -155,9 +262,10 @@ class D2CharStructureData { 519 => 'Rite_Of_Passage', 521 => 'Eve_Of_Destruction', // read 521, pointer at 523, + 14 = 537 qHell offset - - ]; + /** + * @var string[] + */ public $qHell = [ 537 => 'introWarriv', 539 => 'Den_Of_Evil_Hell', @@ -193,15 +301,17 @@ class D2CharStructureData { //599 => 'empty33', 601 => 'completedTerrorsEnd', //603 => 'empty21', - //605 => 'empty22', + //605 => 'empty22', 607 => 'Siege_On_Harrogath', 609 => 'Rescue_On_MountArreat', 611 => 'Prison_Of_Ice', 613 => 'Betrayal_Of_Harrogath', - 615=> 'Rite_Of_Passage', + 615 => 'Rite_Of_Passage', 617 => 'Eve_Of_Destruction', ]; - + /** + * @var string[] + */ public $version = [ 71 => "1.00 through v1.06", 87 => "1.07 or Expansion Set v1.08", @@ -209,12 +319,21 @@ class D2CharStructureData { 92 => "v1.09 (both the standard game and the Expansion Set.)", 96 => "v1.10+" ]; - - + /** + * @var int + */ public $wpOffsetsNorm = 643; + /** + * @var int + */ public $wpOffsetsNM = 667; + /** + * @var int + */ public $wpOffsetsHell = 691; - + /** + * @var string[] + */ public $wpNames = [ 0 => 'Act 1 - Rogue_Encampment', 1 => 'Act 1 - Cold_Plains', @@ -255,18 +374,23 @@ class D2CharStructureData { 36 => 'Act 5 - Frozen_Tundra', 37 => "Act 5 - The_Ancients_Way", 38 => 'Act 5 - Worldstone_Keep_level_2' - ]; - + ]; + /** + * @var int[]|string[] + */ public $_qNorm; + /** + * @var int[]|string[] + */ public $_qNM; + /** + * @var int[]|string[] + */ public $_qHell; - - - /* - Initialize Skills From Skills.txt - */ - - + + /** + * + */ public function __construct() { $sql = " SELECT @@ -285,11 +409,11 @@ class D2CharStructureData { foreach ($res as $r) { $this->skills[$r['Id']] = $r['String']; } - - + + $this->_qNorm = array_flip($this->qNorm); $this->_qNM = array_flip($this->qNM); - $this->_qHell = array_flip($this->qHell); + $this->_qHell = array_flip($this->qHell); } } diff --git a/src/D2Database.php b/src/D2Database.php index ef92b30..80ac05f 100755 --- a/src/D2Database.php +++ b/src/D2Database.php @@ -42,13 +42,24 @@ */ +/** + * + */ class D2Database { - public function __construct() { + /** + * + */ + public function __construct() { PDO_Connect("sqlite:" . DB_FILE); } - public function createTables($file, $data) { + /** + * @param $file + * @param $data + * @return void + */ + public function createTables($file, $data) { $tableName = basename($file); $tableName = strtolower(substr($tableName, 0, -4)); $sql = ''; @@ -68,7 +79,12 @@ class D2Database { } } - public function fillsTables($file, $data) { + /** + * @param $file + * @param $data + * @return void + */ + public function fillsTables($file, $data) { $tableName = basename($file); $tableName = strtolower(substr($tableName, 0, -4)); @@ -106,7 +122,11 @@ class D2Database { } } - public function writeTbl($data) { + /** + * @param $data + * @return void + */ + public function writeTbl($data) { $sql = 'CREATE TABLE IF NOT EXISTS `strings` (`Key` VARCHAR(255), `String` VARCHAR(255));'; $res = PDO_Execute($sql); @@ -120,7 +140,11 @@ class D2Database { $res = PDO_Execute($sql); } - public function getString($key) { + /** + * @param $key + * @return mixed + */ + public function getString($key) { $sql = "SELECT String FROM `strings` WHERE `Key`='$key'"; $res = PDO_FetchRow($sql); diff --git a/src/D2DocGenerator.php b/src/D2DocGenerator.php index 423885f..56e7074 100644 --- a/src/D2DocGenerator.php +++ b/src/D2DocGenerator.php @@ -42,8 +42,14 @@ */ +/** + * + */ class D2DocGenerator { + /** + * + */ public function __construct() { require_once './config.php'; require_once './_pdo.php'; @@ -61,6 +67,9 @@ class D2DocGenerator { $idata = new D2ItemData(); } + /** + * @return array + */ public function getIscProps() { $sql = " SELECT p.`code` as prop, @@ -103,6 +112,9 @@ class D2DocGenerator { return $isc; } + /** + * @return array + */ public function getStrings() { // load strings $sql = 'SELECT * FROM strings'; @@ -110,6 +122,9 @@ class D2DocGenerator { return $strings; } + /** + * @return array + */ public function getItemTypesTbl() { // load itemtypes table in memory $sql = "SELECT ItemType,Code FROM itemtypes"; @@ -118,6 +133,9 @@ class D2DocGenerator { return $itemtypesTbl; } + /** + * @return array + */ public function getNameStr() { // load namestr from 3 files $sql = "SELECT code,namestr FROM armor @@ -130,6 +148,10 @@ class D2DocGenerator { return $namestr; } + /** + * @param $code + * @return string + */ public function getImage($code) { $sql = "SELECT invfile FROM armor WHERE `code`=\"$code\" OR `type`=\"$code\" OR `type2`=\"$code\""; $img = PDO_FetchOne($sql); @@ -144,6 +166,10 @@ class D2DocGenerator { return $img = (!empty($img)) ? "$img.png" : "1.png"; } + /** + * @param $code + * @return false + */ public function getItemName($code) { $sql = "SELECT name FROM armor WHERE `code`=\"$code\" OR `type`=\"$code\" OR `type2`=\"$code\""; $name = PDO_FetchOne($sql); @@ -158,6 +184,9 @@ class D2DocGenerator { return $name; } + /** + * @return void + */ public function generateDocs() { } diff --git a/src/D2Files.php b/src/D2Files.php index 66a1c68..4f6a22e 100755 --- a/src/D2Files.php +++ b/src/D2Files.php @@ -42,11 +42,23 @@ */ +/** + * + */ class D2Files { + /** + * @var array + */ public $files = []; + /** + * @var + */ public $charFiles; + /** + * + */ public function __construct() { $filesToIgnore = [ "aiparms.txt", @@ -64,6 +76,9 @@ class D2Files { return $this->files; } + /** + * @return mixed + */ public function getSaveFiles() { $glob = glob($_SESSION['savepath'] . '*.d2s'); diff --git a/src/D2Functions.php b/src/D2Functions.php index 7bdd515..be82851 100755 --- a/src/D2Functions.php +++ b/src/D2Functions.php @@ -42,6 +42,10 @@ */ +/** + * @param $var + * @return void + */ function ddump($var) { //echo "
";
     var_dump($var);
@@ -51,6 +55,10 @@ function ddump($var) {
     die();
 }
 
+/**
+ * @param $var
+ * @return void
+ */
 function dump($var) {
     //echo "
";
     var_dump($var);
@@ -59,6 +67,10 @@ function dump($var) {
     //echo json_encode($var, JSON_INVALID_UTF8_IGNORE | JSON_PRETTY_PRINT);
 }
 
+/**
+ * @param string $str
+ * @return string
+ */
 function strtobits(string $str): string {
     $ret = "";
     for ($i = 0; $i < strlen($str); ++$i) {
@@ -74,14 +86,29 @@ function strtobits(string $str): string {
     return $ret;
 }
 
+/**
+ * @param string $hex
+ * @return string
+ */
 function swapEndianness(string $hex) {
     return implode('', array_reverse(str_split($hex, 2)));
 }
 
+/**
+ * @param int $n
+ * @param int $p
+ * @param bool $b
+ * @return int
+ */
 function setBit(int $n, int $p, bool $b) {
     return ($b ? ($n | (1 << $p)) : ($n & ~(1 << $p)) );
 }
 
+/**
+ * @param int $b
+ * @param int $p
+ * @return int
+ */
 function getBit(int $b, int $p) {
     return intval(($b & (1 << $p)) !== 0);
 }
@@ -91,30 +118,22 @@ function getBit(int $b, int $p) {
  * @param $data
  * @return string
  */
-//function checksum($data) {
-//    $nSignature = 0;
-//    $checksum = 0;
-//    foreach ($data as $k => $byte) {
-////        if ($k == 12 || $k == 13 || $k == 14 || $k == 15) {
-////            $byte = 0;
-////        }
-//        $nSignature = ((($nSignature << 1) | ($nSignature >> 31)) + $byte) & 0xFFFFFFFF;
-//        $checksum = (($checksum << 1) & 0xffffffff) + $byte + (($checksum & 0x80000000) != 0 ? 1 : 0);
-//    }
-//    dump(swapEndianness(dechex($nSignature)));
-//    dump(swapEndianness(dechex($checksum)));
-//    //return swapEndianness(dechex($nSignature));
-//    //return swapEndianness(dechex($checksum)); // all of a sudden started returning wrong value, no longer works
-//}
-//
-//function checksumFix(object $ByteReader, string $filePath) {
-//    $ByteReader->writeBytes(12, 00000000); // zero old checksum
-//    $newChecksum = checksum(unpack('C*', $ByteReader->getData())); // get new checksum
-//    $ByteReader->writeBytes(12, $newChecksum); // write new checksum
-//    file_put_contents($filePath, $ByteReader->getData()); // write bytestream to file
-//}
+function checksum($fileData) {
+    $nSignature = 0;
+    foreach ($fileData as $k => $byte) {
+        if ($k == 12 || $k == 13 || $k == 14 || $k == 15) {
+            $byte = 0;
+        }
+        $nSignature = ((($nSignature << 1) | ($nSignature >> 31)) + $byte & 0xFFFFFFFF);
+    }
+    return swapEndianness(str_pad(dechex($nSignature), 8, 0, STR_PAD_LEFT));
+}
 
-function checksumFix(string $filePath) {   
+/**
+ * @param string $filePath
+ * @return string
+ */
+function checksumFix(string $filePath) {
     return trim(shell_exec("bin\d2scs.exe \"$filePath\""));
 }
 
@@ -135,10 +154,18 @@ function strposX($haystack, $needle, $number) {
     }
 }
 
+/**
+ * @param string $bit
+ * @return int
+ */
 function isBit(string $bit): int {
     return ((int) $bit ? 1 : 0 );
 }
 
+/**
+ * @param $input
+ * @return string
+ */
 function toBits($input): string {
     $output = '';
     if (is_string($input)) {
@@ -156,6 +183,9 @@ function toBits($input): string {
     }
 }
 
+/**
+ * @return void
+ */
 function print_mem() {
     /* Currently used memory */
     $mem_usage = memory_get_usage();
@@ -167,6 +197,11 @@ function print_mem() {
     echo 'Peak usage: ' . round($mem_peak / 1024 / 1024) . 'MB RAM.

'; } +/** + * @param $src + * @param $dst + * @return void + */ function rcopy($src, $dst) { // open the source directory $dir = opendir($src); @@ -185,6 +220,10 @@ function rcopy($src, $dst) { closedir($dir); } +/** + * @param $src + * @return void + */ function rrmdir($src) { $dir = opendir($src); while (false !== ( $file = readdir($dir))) { diff --git a/src/D2Item.php b/src/D2Item.php index 2744037..29e9e95 100644 --- a/src/D2Item.php +++ b/src/D2Item.php @@ -3,11 +3,23 @@ require_once 'D2BitReader.php'; require_once 'D2ItemStructureData.php'; +/** + * + */ class D2Item { + /** + * @var null + */ private $bits = null; + /** + * @var null + */ public $iData = null; + /** + * @param $bits + */ public function __construct($bits) { if ($bits == '') return false; @@ -20,6 +32,9 @@ class D2Item { * @return array of item details */ + /** + * @return null + */ private function parseItem() { $b = new D2BitReader($this->bits); diff --git a/src/D2ItemData.php b/src/D2ItemData.php index 19044d4..f61870d 100755 --- a/src/D2ItemData.php +++ b/src/D2ItemData.php @@ -1,10 +1,22 @@ images)) { $this->getImages(); @@ -897,6 +914,10 @@ class D2ItemData { } } + /** + * @param $iscStat + * @return mixed + */ public function getIscStrings($iscStat) { if (empty($this->strings)) { $this->getStrings(); @@ -904,6 +925,9 @@ class D2ItemData { return ($this->strings[$iscStat]); } + /** + * @return void + */ public function getStrings() { $sql = " SELECT p.`code` as prop, diff --git a/src/D2ItemDesc.php b/src/D2ItemDesc.php index 12d06d0..7c1c225 100755 --- a/src/D2ItemDesc.php +++ b/src/D2ItemDesc.php @@ -42,6 +42,9 @@ */ +/** + * + */ class D2ItemDesc { /* @@ -135,14 +138,26 @@ class D2ItemDesc { */ - public $str = ''; + /** + * @var string + */ + public $str = ''; // takes value, param. // value == Value to show on this stat //descfunc 14 - public $skilltabsDesc; - public $skilltabsDescClean; - public $skilltabs = [ + /** + * @var + */ + public $skilltabsDesc; + /** + * @var + */ + public $skilltabsDescClean; + /** + * @var string[] + */ + public $skilltabs = [ //ama '0' => 'StrSklTabItem3', '1' => 'StrSklTabItem2', @@ -172,7 +187,10 @@ class D2ItemDesc { '19' => 'StrSklTabItem20', '20' => 'StrSklTabItem21' ]; - public $charClass = [ + /** + * @var string[] + */ + public $charClass = [ "ama" => "Amazon", "sor" => "Sorceress", "nec" => "Necromancer", @@ -245,7 +263,13 @@ class D2ItemDesc { * */ - public function prep($par, $min, $max) { + /** + * @param $par + * @param $min + * @param $max + * @return void + */ + public function prep($par, $min, $max) { } @@ -268,16 +292,19 @@ class D2ItemDesc { */ - public function __construct() { + /** + * + */ + public function __construct() { } - - - - - public function getDesc($params = []) { + /** + * @param $params + * @return false|string + */ + public function getDesc($params = []) { if (empty($params)) return false; diff --git a/src/D2ItemStructureData.php b/src/D2ItemStructureData.php index 0a89ca0..aafd9bf 100644 --- a/src/D2ItemStructureData.php +++ b/src/D2ItemStructureData.php @@ -1,40 +1,112 @@ path.DIRECTORY_SEPARATOR.$file, 'a+'); $this->saveBackup($file); @@ -54,7 +65,11 @@ class D2SaveTXT { } - public function saveBackup($file){ + /** + * @param $file + * @return void + */ + public function saveBackup($file){ // if dir doesn't exist, create it if (!is_dir($this->path.DIRECTORY_SEPARATOR."backup")) mkdir($this->path."backup", 0700); @@ -71,8 +86,12 @@ class D2SaveTXT { echo "Failed to create backup of $file...\n"; } } - - public function saveTblEnries($filename) { + + /** + * @param $filename + * @return void + */ + public function saveTblEnries($filename) { $post = $_POST; if (!is_dir($this->path."tblEntries")) mkdir($this->path."tblEntries", 0700); @@ -81,9 +100,12 @@ class D2SaveTXT { $str = '"'.$post['index'].'"'."\t".'"'.$post['index'].'"'.PHP_EOL; $file = $this->path."\\tblEntries\\$filename"; file_put_contents($file, $str, FILE_APPEND); - } - - public function __construct() { + } + + /** + * + */ + public function __construct() { $this->path = TXT_PATH; } diff --git a/src/D2Strings.php b/src/D2Strings.php index a9c4b82..042d28f 100644 --- a/src/D2Strings.php +++ b/src/D2Strings.php @@ -1,8 +1,17 @@ strings = PDO_FetchAssoc($sql); diff --git a/src/D2TxtParser.php b/src/D2TxtParser.php index 429ddb9..ec9f5bf 100755 --- a/src/D2TxtParser.php +++ b/src/D2TxtParser.php @@ -41,20 +41,41 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + +/** + * + */ class D2TxtParser { - public $path = TXT_PATH; + /** + * @var mixed + */ + public $path = TXT_PATH; - public $db; - - public function __construct() { + /** + * @var + */ + public $db; + + /** + * + */ + public function __construct() { } - public function parseFile($file) { + /** + * @param $file + * @return array + */ + public function parseFile($file) { return $this->parseData($file); } - function filterProps($file) { + /** + * @param $file + * @return array + */ + function filterProps($file) { $data = $this->parseData($file); $propsToFilter = file(FILTER_PROPERTIES_FILE, FILE_IGNORE_NEW_LINES); foreach ($data as $d) { @@ -65,7 +86,11 @@ class D2TxtParser { return $filteredProps; } - public function parseData($file) { + /** + * @param $file + * @return array + */ + public function parseData($file) { $file = $this->path . $file; $rows = array_map(function ($v) { return str_getcsv($v, "\t"); diff --git a/src/tabs/UniqueItems.php b/src/tabs/UniqueItems.php index 72089fc..df4e1c0 100755 --- a/src/tabs/UniqueItems.php +++ b/src/tabs/UniqueItems.php @@ -47,7 +47,7 @@
-
+
diff --git a/test.php b/test.php index aa6878f..3c1c5e6 100644 --- a/test.php +++ b/test.php @@ -1,195 +1,42 @@ data = $data; - } - - public function skip(int $numBytes): bool { - if ($numBytes < 0 || $numBytes > strlen($this->data)) - return false; - $this->offset += $numBytes; - return $true; - } - - public function seek(int $pos): bool { - if ($pos < 0 || $pos > strlen($this->data)) - return false; - $this->offset = $pos; - return true; - } - - public function readh(int $offset, int $numBytes, bool $str = true): string { - $this->seek($offset); - $bytes = null; - for ($i = $this->offset; $i < $this->offset + $numBytes; $i++) { - $str ? $bytes .= $this->data[$i] : $bytes[] = $this->data[$i]; - } - return unpack('H*', $bytes)[1]; - } - - public function readc(int $offset, int $numBytes, bool $str = true): array { - $this->seek($offset); - $bytes = null; - for ($i = $this->offset; $i < $this->offset + $numBytes; $i++) { - $str ? $bytes .= $this->data[$i] : $bytes[] = $this->data[$i]; - } - return unpack('C*', $bytes); - } - - public function rewind(): bool { - $this->offset = 0; - return true; - } - - public function writeByte(int $offset, int $byte) { - $this->data[$offset] = pack('C', $byte); - } - - public function writeBytes(int $offset, string $bytes) { - if ($offset < 0 || $offset > strlen($this->data) || $bytes == '') - return false; - $_bytes = str_split($bytes, 2); - foreach ($_bytes as $k => $byte) { - $pos = $offset + $k; - $this->data[$pos] = pack('H*', $byte); - } - } - - public function getData() { - return $this->data ? $this->data : false; - } - - public function getOffset(): int { - return $this->offset; - } - - public function isHexString(string $str): bool { - if (strlen($str) % 2 == 0 && (ctype_xdigit($str))) { - return true; - } - return false; - } - - public function toBits($input): string { - $output = ''; - if ($this->isHexString($input)) { - foreach (str_split($input, 2) as $byte) { - $output .= (strrev(str_pad(decbin(hexdec($byte)), 8, 0, STR_PAD_LEFT))); - } - return $output; - } else if (is_string($input)) { - foreach (str_split($input) as $i) { - $output .= strrev(str_pad(decbin(ord($i)), 8, 0, STR_PAD_LEFT)); - } - return $output; - } else if (is_int($input)) { - return strrev(str_pad(decbin($input), 8, 0, STR_PAD_LEFT)); - } else if (is_array($input)) { - foreach ($input as $i) { - $output .= $this->tobits($i); - } - return $output; - } - } - - public function toBytes(string $bits) { - foreach (str_split($bits, 8) as $byteString) { - $bytes[] = (bindec(strrev($byteString))); - } - foreach ($bytes as $byte) { - dump($byte); - } - } - - public function bitsToHexString(string $bits): string { - $bytes = ''; - foreach (str_split($bits, 8) as $byte) { - $bytes .= (str_pad(dechex((bindec(strrev($byte)))), 2, 0, STR_PAD_LEFT)); - } - return $bytes; - } - - public function bitsToHexArray(string $bits): array { - $bytes = []; - foreach (str_split($bits, 8) as $byte) { - $bytes[] = (str_pad(dechex((bindec(strrev($byte)))), 2, 0, STR_PAD_LEFT)); - } - return $bytes; - } - - public function bitsToIntArray(string $bits): array { - $bytes = []; - foreach (str_split($bits, 8) as $byte) { - $bytes[] = (int) (str_pad(dechex((bindec(strrev($byte)))), 2, 0, STR_PAD_LEFT)); - } - return $bytes; - } - - /* - @return Byte with Nth bit set to X - */ - - public function setBit(int $byte, int $pos, bool $bit) { - return ($bit ? ($byte | (1 << $pos)) : ($byte & ~(1 << $pos)) ); - } - - /* - @return Bit at Nth position in Byte - */ - - public function getBit(int $byte, int $pos): int { - return intval(($byte & (1 << $pos)) != 0); - } + Checksum field is at byte 12. + Bytes 12/13/14/15 as a uint32. + Set this to 0. + After clearing the checksum field add up the values of all the bytes in the file and rotate the running total one bit to the left before adding the next byte. + */ +function swapEndianness(string $hex) { + return implode('', array_reverse(str_split($hex, 2))); } -$filename = "D:\Diablo II\MODS\ironman-dev\save\Barb.d2s"; +function checksum($fileData) { + $nSignature = 0; + foreach ($fileData as $k => $byte) { + if ($k == 12 || $k == 13 || $k == 14 || $k == 15) { + $byte = 0; + } + $nSignature = ((($nSignature << 1) | ($nSignature >> 31)) + $byte & 0xFFFFFFFF); + } + return swapEndianness(str_pad(dechex($nSignature), 8, 0, STR_PAD_LEFT)); +} + +$filename = "D:\Diablo II\MODS\ironman-dev\save\Necro.d2s"; +$fp = fopen($filename, "r+b"); + +fseek($fp, 12); // go to byte 12 +fwrite($fp, pack('I', 0)); // clear the checksum field uInt32 + $fileData = unpack('C*', file_get_contents($filename)); // open file and unpack + + var_dump(checksum($fileData)); - - - -//$file = "D:\Diablo II\MODS\ironman-dev\save\Barb.d2s"; -//$data = file_get_contents($file); -// -//$c = new D2ByteReader($data); -// -//$checksum = checksum(unpack('C*', $c->getData())); -// -//ddump($checksum); - -//$bytes = $c->readh(643, 5); -//$bits = $c->toBits($bytes); -// -//$bits[4] = 0; -//$bits[5] = 0; -//$bits[8] = 0; - -//$newBytes = $c->bitsToHexString($bits); -//$c->writeBytes(643, $newBytes); -// -//$c->writeBytes(12, "00000000"); // zero old checksum -// -//dump($c->readh(12, 4)); -// -//$newChecksum = checksum(unpack('C*', $c->getData())); // get new checksum -//$c->writeBytes(12, $newChecksum); // write new checksum -// -//dump($c->readh(12, 4)); -// -//file_put_contents($file, $c->getData()); // write bytestream to file - +fseek($fp, 12); // go to byte 12 +fwrite($fp, pack('H8', checksum($fileData))); // write new checksum +fclose($fp); \ No newline at end of file