diff --git a/res/app.js b/res/app.js index addb397..36a8c74 100755 --- a/res/app.js +++ b/res/app.js @@ -1,29 +1,29 @@ /* - + Copyright (C) 2021 Hash Borgir - + This file is part of D2Modder - + Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - + * This software must not be used for commercial purposes * without my consent. Any sales or commercial use are prohibited * without my express knowledge and consent. - + This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY! - + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF @@ -37,7 +37,7 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - + */ // highlight json dump for item debugger function syntaxHighlight(json) { @@ -97,7 +97,7 @@ $(document).ready(function () { }); x = $(this).find('option:selected').text(); $("*[name='*type']").val(x); - $('.itemtype').html("("+x+")"); + $('.itemtype').html("(" + x + ")"); }); $('.a-select').change(function () { @@ -107,7 +107,7 @@ $(document).ready(function () { }); x = $(this).find('option:selected').text(); $("*[name='*type']").val(x); - $('.itemtype').html("("+x+")"); + $('.itemtype').html("(" + x + ")"); }); $('.m-select').change(function () { $('.w-select,.a-select').each(function (i, v) { @@ -116,7 +116,7 @@ $(document).ready(function () { }); x = $(this).find('option:selected').text(); $("*[name='*type']").val(x); - $('.itemtype').html("("+x+")"); + $('.itemtype').html("(" + x + ")"); }); $(".btnconfig").click(function () { @@ -322,5 +322,37 @@ $(document).ready(function () { }); + // Chars.php Character Editor AJAXIFY + // Prevent default form submit + $('form').submit(function (event) { + event.preventDefault(); + }); -}); \ No newline at end of file + $(".wpcheck").change(function () { + // Toggle the value between 0 and 1 + var value = $(this).is(":checked") ? 1 : 0; + + // Get the name of the checkbox + var name = $(this).attr("name"); + + // Get the name of the difficulty + var diff = $(this).attr("diff"); + + // Get the value of the input named "filePath" + var filePath = $("input[name='filePath']").val(); + + // Construct the URL for the GET request + var url = "/saveCharacter.php?cmd=wp&name=" + name + "&value=" + value + "&filePath=" + filePath + "&diff=" + diff; + + + // Send the GET request + $.get(url, function (response) { + // Handle the response if needed + console.log(response); + }); + }); + + + + +});// end document.ready \ No newline at end of file diff --git a/saveCharacter.php b/saveCharacter.php index da076d7..a0add26 100644 --- a/saveCharacter.php +++ b/saveCharacter.php @@ -1,138 +1,200 @@ string 'wp' (length=2) + 'name' => string '1' (length=1) + 'value' => string '1' (length=1) + 'filePath' => string 'D:\\Diablo II\\MODS\\ironman-dev\\save\\Sorc.d2s' (length=48) + 'diff' => string 'Norm' (length=4) + */ -$p = $_POST; -// ddump($p); + $fileContents = file_get_contents($filePath); // Read the contents of the file + $ByteReader = new D2ByteReader($fileContents); // Create a new instance of D2ByteReader with the file data + $fileData = $ByteReader->getData(); // Get the data from the ByteReader instance -foreach($p['quest'] as $k => $v) { - $q[$k] = ($dStruct->_qNorm[$k]); -} + $wpBytes = $ByteReader->read($offset, 5, 1); + $wpBytesToBits = $ByteReader->toBits($wpBytes); -foreach($p['wp'] as $k => $v) { - $w[$k] = ($dStruct->_qNorm[$k]); + $BitReader = new D2BitReader($wpBytesToBits); + + $BitReader->setBit($g['name'], $g['value']); + + $newBits = $BitReader->getBits(); + + $newBitsToBytes = $ByteReader->bitsToHexString($newBits); + + $ByteReader->writeBytes($offset, $newBitsToBytes); + + $newFileData = $ByteReader->getData(); + $fileSaved = file_put_contents($filePath, $newFileData); + $checksum = (shell_exec("bin\d2scs.exe \"$filePath\"")); + + if ($fileSaved) { + echo "Success"; + } else { + echo "Fail"; + } + } + + + + + + + + + + + + + + + + + + + + + + + + + +die(); + +// Get the POST data +$p = $_GET; + +// Loop through each 'quest' element in the POST data and assign the corresponding value from 'dStruct->_qNorm' to 'q' +foreach ($p['quest'] as $k => $v) { + $q[$k] = $dStruct->_qNorm[$k]; +} + +$wpNames_flipped = array_values($dStruct->wpNames_flipped); + +// Loop through each 'wp' element in the POST data and assign the corresponding value from 'dStruct->_qNorm' to 'w' +foreach ($p['wp'] as $k => $v) { + $w[$k] = $dStruct->_qNorm[$k]; +} + +// Get the file path from the POST data and replace backslashes / $filePath = $p['filePath']; $filePath = str_replace("\\", "\\\\", $filePath); + +// Open the file in binary mode for both reading and writing $fp = fopen($filePath, "rb+"); -// delete old checksum at offset 0x0C - byte 12 -//fseek($fp, 12); -// (I) unsigned integer (machine dependent size and byte order) -//fwrite($fp, pack('I', 0)); // produces 4 bytes - -// edit quests which are checked, to Just finished FE FF +// Edit quests which are checked to set them as 'Just finished' (FE FF) foreach ($q as $k => $v) { + // Set the file pointer position to the quest offset fseek($fp, $v); + + // Write the byte value 0xFE at the current position fwrite($fp, pack('C', 0xFE)); - fseek($fp, $v+1); + + // Move the file pointer to the next position + fseek($fp, $v + 1); + + // Write the byte value 0xFF at the current position fwrite($fp, pack('C', 0xFF)); } -if ($p['wp_all'] == "1") { - fseek($fp, 643); - fwrite($fp, pack('C', 0xFF)); - fseek($fp, 644); - fwrite($fp, pack('C', 0xFF)); - fseek($fp, 645); - fwrite($fp, pack('C', 0xFF)); - fseek($fp, 646); - fwrite($fp, pack('C', 0xFF)); - fseek($fp, 647); - fwrite($fp, pack('C', 0x7F)); - - fseek($fp, 667); - fwrite($fp, pack('C', 0xFF)); - fseek($fp, 668); - fwrite($fp, pack('C', 0xFF)); - fseek($fp, 669); - fwrite($fp, pack('C', 0xFF)); - fseek($fp, 670); - fwrite($fp, pack('C', 0xFF)); - fseek($fp, 671); - fwrite($fp, pack('C', 0x7F)); - - fseek($fp, 691); - fwrite($fp, pack('C', 0xFF)); - fseek($fp, 692); - fwrite($fp, pack('C', 0xFF)); - fseek($fp, 693); - fwrite($fp, pack('C', 0xFF)); - fseek($fp, 694); - fwrite($fp, pack('C', 0xFF)); - fseek($fp, 695); - fwrite($fp, pack('C', 0x7F)); - -} - else if ($p['wp_all'] == "0") -{ - fseek($fp, 643); - fwrite($fp, pack('C', 0x00)); - fseek($fp, 644); - fwrite($fp, pack('C', 0x00)); - fseek($fp, 645); - fwrite($fp, pack('C', 0x00)); - fseek($fp, 646); - fwrite($fp, pack('C', 0x00)); - fseek($fp, 647); - fwrite($fp, pack('C', 0x00)); - - fseek($fp, 667); - fwrite($fp, pack('C', 0x00)); - fseek($fp, 668); - fwrite($fp, pack('C', 0x00)); - fseek($fp, 669); - fwrite($fp, pack('C', 0x00)); - fseek($fp, 670); - fwrite($fp, pack('C', 0x00)); - fseek($fp, 671); - fwrite($fp, pack('C', 0x00)); - - fseek($fp, 691); - fwrite($fp, pack('C', 0x00)); - fseek($fp, 692); - fwrite($fp, pack('C', 0x00)); - fseek($fp, 693); - fwrite($fp, pack('C', 0x00)); - fseek($fp, 694); - fwrite($fp, pack('C', 0x00)); - fseek($fp, 695); - fwrite($fp, pack('C', 0x00)); + +// Array containing the fseek wp offsets +// 5 acts per difficulty +// starts at 641, but we skip two bytes, go to 643, then read next 5 bytes, each byte is a bitfield per act. + + + +$offsets = [ + 643, 644, 645, 646, 647, // First set of offsets Norm + 667, 668, 669, 670, 671, // Second set of offsets NM + 691, 692, 693, 694, 695 // Third set of offsets Hell +]; + +// Determine the value to write based on the condition $p['wp_all'] == "1" +$valueToWrite = ($p['wp_all'] == "1") ? 0xFF : 0x00; + +// Loop over each offset in the $offsets array +foreach ($offsets as $offset) { + // Set the file pointer position to the current offset + fseek($fp, $offset); + + // Write the value specified by pack('C', $valueToWrite) + fwrite($fp, pack('C', $valueToWrite)); } $checksum = (shell_exec("bin\d2scs.exe \"$filePath\"")); - -// write NEW checksum at offset 0x0C - byte 12 -//fseek($fp, 12); -// (I) unsigned integer (machine dependent size and byte order) -//fwrite($fp, pack('H*', $checksum)); // produces 4 bytes - - fclose($fp); -header('Location: /'); \ No newline at end of file +header('Location: /'); diff --git a/src/D2Char.php b/src/D2Char.php index 5f8c6f7..ff80023 100644 --- a/src/D2Char.php +++ b/src/D2Char.php @@ -14,47 +14,60 @@ require_once 'D2Functions.php'; class D2Char { /** - * @var null + * @var null|string char data output */ - public $cData = null; // char data output + public $cData = null; + /** - * @var null + * @var null|string char item data */ - public $items = null; // char item data + public $items = null; + /** - * @var D2ByteReader|null + * @var D2ByteReader|null put $data into bytereader */ - public $ByteReader = null; // put $data into bytereader + public $ByteReader = null; + /** - * @var string|null + * @var null|string .d2s file path */ - public $filePath = null; // .d2s file path + public $filePath = null; + /** - * @var D2CharStructureData + * @var D2CharStructureData char file structure data */ - private $sData = null; // char file structure data + private $sData = null; + /** - * @var null + * @var null|string char binary data from d2s */ - private $bData = null; // char binary data from d2s + private $bData = null; + /** - * @var false|resource + * @var false|resource file pointer */ - private $fp = null; // file pointer + private $fp = null; + /** - * @var false|string + * @var false|string full d2s file loaded in $data */ - private $data = null; // full d2s file loaded in $data + private $data = null; + /** * @var null */ private $ISC = null; + /** * @var null */ private $skillData = null; /** + * Saves the data to a file. + * + * This method updates the data with a new checksum and saves it to the specified file path. + * * @return void */ public function save() { @@ -62,7 +75,7 @@ class D2Char { $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 @@ -70,65 +83,70 @@ class D2Char { } /** - * @param $file + * Initializes the class instance with the given file. + * + * @param string $file The file to be processed. */ public function __construct($file) { - $this->sData = new D2CharStructureData(); - $this->filePath = $_SESSION['savepath'] . $file; - $this->fp = fopen($this->filePath, "r+b"); - $data = file_get_contents($this->filePath); - $this->ByteReader = new D2ByteReader($data); - $this->data = $this->ByteReader->getData(); - + $this->sData = new D2CharStructureData(); // Create a new instance of D2CharStructureData + $this->filePath = $_SESSION['savepath'] . $file; // Set the file path based on the session save path and the provided file + $this->fp = fopen($this->filePath, "r+b"); // Open the file in read/write binary mode + $data = file_get_contents($this->filePath); // Read the contents of the file + $this->ByteReader = new D2ByteReader($data); // Create a new instance of D2ByteReader with the file data + $this->data = $this->ByteReader->getData(); // Get the data from the ByteReader instance + // Fetch itemstatcost data from the database and store it in $this->ISC $sql = "SELECT ID,Stat,CSvBits,ValShift FROM itemstatcost WHERE Saved=1"; $ISCData = PDO_FetchAll($sql); - foreach ($ISCData as $k => $v) { $this->ISC[$v['ID']] = $v; $this->_ISC[$v['Stat']] = $v; } - // read offsets here from sData and put into $this->bData - // which will be used for cData output + // Read offsets from sData and store them in $this->bData foreach ($this->sData->offsets as $k => $v) { - fseek($this->fp, $k); - $this->bData[$k] = fread($this->fp, $v); + fseek($this->fp, $k); // Move the file pointer to the specified offset + $this->bData[$k] = fread($this->fp, $v); // Read data from the file at the current offset and store it in $this->bData } $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))]; + $class = $classes[hexdec($this->ByteReader->readh(40, 1))]; // Determine the character class based on the byte read from the file $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 LEFT JOIN skilldesc ON sk.skilldesc = skilldesc.skilldesc LEFT JOIN strings on skilldesc.`str name` = strings.Key WHERE sk.charclass = '$class'"; - $sd = PDO_FetchAll($sql); + $sd = PDO_FetchAll($sql); // Fetch skill data from the database foreach ($sd as $k => $v) { $this->skillData[$k + 1] = $v; } - return $this->parseChar(); // end of parseChar() calls parseItems(), parseStats, etc. + return $this->parseChar(); // Call the parseChar() method and return its result } /** + * Parses the items in the data. + * + * This method extracts and processes the item data from the provided data. + * * @return void */ public function parseItems() { - $i_TotalOffset = strpos($this->data, "JM"); - fseek($this->fp, $i_TotalOffset + 2); - $i_Total = unpack('S*', (fread($this->fp, 2)))[1]; + $i_TotalOffset = strpos($this->data, "JM"); // Find the offset of the "JM" marker in the data + fseek($this->fp, $i_TotalOffset + 2); // Move the file pointer to the next position after the "JM" marker + $i_Total = unpack('S*', (fread($this->fp, 2)))[1]; // Read 2 bytes from the file and unpack them as an unsigned short (16-bit) value $i_Offsets = []; for ($i = 0; $i <= $i_Total; $i++) { - $i_Offsets[] = strposX($this->data, "JM", $i + 2); + $i_Offsets[] = strposX($this->data, "JM", $i + 2); // Find the offsets of the "JM" markers for each item } foreach ($i_Offsets as $k => $v) { - $itemOffsets[$v] = $i_Offsets[$k + 1] - $i_Offsets[$k]; + $itemOffsets[$v] = $i_Offsets[$k + 1] - $i_Offsets[$k]; // Calculate the length of each item's data by subtracting consecutive offsets } - array_pop($itemOffsets); + array_pop($itemOffsets); // Remove the last element from the itemOffsets array + $_items = []; foreach ($itemOffsets as $offset => $bytes) { - $this->items[] = new D2Item($this->ByteReader->toBits($this->ByteReader->readh($offset, $bytes))); + $this->items[] = new D2Item($this->ByteReader->toBits($this->ByteReader->readh($offset, $bytes))); // Create a new D2Item object and add it to the items array } } @@ -166,7 +184,6 @@ WHERE sk.charclass = '$class'"; $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('L', $this->bData[48])[0]); @@ -228,10 +245,12 @@ WHERE sk.charclass = '$class'"; * @return array */ public function parseSkills() { - $if = strposX($this->data, 'if', 1) + 2; // find if and skip it + $if = strposX($this->data, 'if', 1) + 2; // Find 'if' and skip it $jm = strposX($this->data, 'JM', 1); $skills = ($this->ByteReader->readc($if, ($jm - $if))); + $cData = []; // Initialize the result array + foreach ($skills as $k => $v) { if ($this->skillData[$k]['String']) { $cData['skills'][$k] = [ @@ -244,83 +263,108 @@ WHERE sk.charclass = '$class'"; ]; } } - $cData['skills'] = array_values($cData['skills']); + + $cData['skills'] = array_values($cData['skills']); // Reset the array keys + return $cData; } /** - * @param int $points + * Set all skills to a given number of points. + * + * @param int $points The number of points to set for all skills. * @return void */ public function setAllSkills(int $points) { - $if = strposX($this->data, 'if', 1) + 2; // find if and skip it + $if = strposX($this->data, 'if', 1) + 2; // Find 'if' and skip it $jm = strposX($this->data, 'JM', 1); $len = $jm - $if; + for ($i = 0; $i < $len; $i++) { - $this->ByteReader->writeByte($if + $i, $points); + $this->ByteReader->writeByte($if + $i, $points); // Set the skill points to the given value } - $this->data = $this->ByteReader->getData(); - $this->save(); + + $this->data = $this->ByteReader->getData(); // Update the character data + $this->save(); // Save the changes to the character file } /** - * @param int $skill - * @param int $points + * Set the points for a specific skill. + * + * @param int $skill The skill ID. + * @param int $points The number of points to set for the skill. * @return void */ public function setSkill(int $skill, int $points) { - $skill -= 1; - $if = strposX($this->data, 'if', 1) + 2; // find if and skip it + $skill -= 1; // Adjust the skill ID to match the array index + $if = strposX($this->data, 'if', 1) + 2; // Find 'if' and skip it $jm = strposX($this->data, 'JM', 1); - // set $kill to $points + + // Set the points for the specified skill $this->ByteReader->writeByte($if + $skill, $points); - $this->data = $this->ByteReader->getData(); - $this->save(); + + $this->data = $this->ByteReader->getData(); // Update the character data + $this->save(); // Save the changes to the character file } /** + * Parse the character stats. + * * @return void */ public function parseStats() { - $gf = strposX($this->data, 'gf', 1) + 2; // find gf and skip it + $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(); - $bytes = $this->ByteReader->toBytes($bits); $stats->rewind(); + + $ids = []; // Array to store the encountered stat IDs + // Iterate over the stats and collect their IDs for ($i = 0; $i <= count($this->ISC); $i++) { $id = hexdec($this->ByteReader->toBytesR($stats->readb(9))); + + // Skip the bits corresponding to the stat if needed if ($this->ISC[$id]['CSvBits'] !== NULL && $this->ISC[$id]['CSvBits'] !== '') { $stats->skip($this->ISC[$id]['CSvBits']); } - $ids[$id] = $id; + + $ids[$id] = $id; // Store the ID in the array } + $stats->rewind(); + + $values = []; // Array to store the parsed stat values + // Iterate over the collected stat IDs and retrieve their values foreach ($ids as $id) { $stats->skip(9); + + // Skip the bits corresponding to the stat if needed 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)); } } + + // Perform additional calculations or conversions on specific stats $values['hitpoints'] = (int) round($values['hitpoints'] >> 11); $values['maxhp'] = (int) round($values['maxhp'] >> 11); $values['mana'] = (int) round($values['mana'] >> 11); $values['maxmana'] = (int) round($values['maxmana'] >> 11); $values['stamina'] = (int) round($values['stamina'] >> 11); $values['maxstamina'] = (int) round($values['maxstamina'] >> 11); -// $values['soulcounter'] = (int) round($values['soulcounter'] / 2); -// $values['killcounter'] = (int) round($values['killcounter'] / 2); - $this->cData['stats'] = $values; + $this->cData['stats'] = $values; // Assign the parsed stats to the character data } /** + * Set character attributes. + * * @param string $stat * @param mixed $val * @param mixed|null $val2 @@ -349,61 +393,54 @@ WHERE sk.charclass = '$class'"; ]; $this->ByteReader->writeByte(40, $classes[$val]); break; + case "CharacterLevel": if ($val > 99) { $val = 99; } - // level is edited in two places. - // byte 43 $this->ByteReader->writeByte(43, $val); - // and in charstats $this->setStat('level', $val); - // now we have to set the experience stat for this level - // it's 1 level below as it starts from 0, for next level - // $val -= 1; $sql = "SELECT {$this->cData['CharacterClass']} FROM experience WHERE level = '$val'"; $res = PDO_FetchOne($sql); $this->setStat('experience', $res); break; + case 'CharacterStatus': + $status = strrev(strtobits($this->data[36])); + 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; } + + $byte = $this->ByteReader->bitsToHexString($status); + $this->ByteReader->writeByte(36, hexdec($byte)); break; - case 'CharacterProgression': // 0 in normal, 1 finished normal, 2 finished nm, 3 finished hell + + case 'CharacterProgression': switch ($val) { - case 0: // in normal + case 0: $this->data[37] = pack('C', 3); break; - case 1: // finished normal + case 1: $this->data[37] = pack('C', 8); break; - case 2: // finished nm + case 2: $this->data[37] = pack('C', 13); break; - case 3: // finished hell + case 3: $this->data[37] = pack('C', 15); break; - $this->save(); } + $this->save(); break; case 'LeftmousebuttonskillID': @@ -423,12 +460,13 @@ WHERE sk.charclass = '$class'"; break; } - // finally save char data to d2s file $this->data = $this->ByteReader->getData(); $this->save(); } /** + * Reset the file size of the D2S file. + * * @return void */ public function resetFileSize() { @@ -436,8 +474,8 @@ WHERE sk.charclass = '$class'"; $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); @@ -446,54 +484,51 @@ WHERE sk.charclass = '$class'"; } /** - * @return false|string + * Generate all stats and update the D2S file. + * + * @return false|string The updated data of the D2S file. */ public function generateAllStats() { - // 003C08E081000F067860C001071C0008020800F040020064A000000A2C00C0030C000D000000006E05000000FE3F $stats = ''; 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); - + $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)); $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; $bytes = $this->ByteReader->toBytes($stats); - // refresh this data + // Refresh the data $data = $this->ByteReader->getData(); - // delete everything between GF---and---IF - $data = substr_replace($data, "", $gf, $len); - // pack hex bites into binary string - $packedbytes = (pack('H*', $bytes)); - // now insert new packed byte stat data between gf and if - $data = substr_replace($data, $packedbytes, 767, 0); + // Delete everything between GF and IF + $data = substr_replace($data, '', $gf, $len); + // Pack hex bytes into a binary string + $packedBytes = pack('H*', $bytes); + // Insert the new packed byte stat data between GF and IF + $data = substr_replace($data, $packedBytes, 767, 0); $this->data = $data; $this->save(); $this->resetFileSize(); - $filedata = file_get_contents($this->filePath); - $this->ByteReader = new D2ByteReader($filedata); + $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 + * Set a specific stat in the D2S file. + * + * @param string $stat The stat to set. + * @param mixed $val The value to set for the stat. * @return void */ public function setStat(string $stat, mixed $val) { @@ -549,44 +584,59 @@ WHERE sk.charclass = '$class'"; } /** - * @param $file - * @return array + * Get the quest data from the specified file. + * + * @param string $file The file to read the quest data from. + * @return array The quest data. */ public function getQuestData($file) { $questsNorm = null; $questsNM = null; $questsHell = null; $quests = null; + + // Read quests from qNorm foreach ($this->sData->qNorm as $k => $v) { fseek($this->fp, $k); $questsNorm[$k] = fread($this->fp, 2); } + + // Read quests from qNM foreach ($this->sData->qNM as $k => $v) { fseek($this->fp, $k); $questsNM[$k] = fread($this->fp, 2); } + + // Read quests from qHell foreach ($this->sData->qHell as $k => $v) { fseek($this->fp, $k); $questsHell[$k] = fread($this->fp, 2); } + + // Process quests from qNorm foreach ($questsNorm as $k => $v) { $x = (str_split(strtobits($v), 8)); // if ($x[0][0]) { $quests['Norm'][$this->sData->qNorm[$k]] = $x[0][0]; // } } + + // Process quests from qNM foreach ($questsNM as $k => $v) { $x = array_filter(str_split(strtobits($v), 8)); // if ($x[0][0]) { $quests['NM'][$this->sData->qNM[$k]] = $x[0][0]; // } } + + // Process quests from qHell foreach ($questsHell as $k => $v) { $x = array_filter(str_split(strtobits($v), 8)); // if ($x[0][0]) { $quests['Hell'][$this->sData->qHell[$k]] = $x[0][0]; // } } + return $quests; } @@ -595,44 +645,26 @@ WHERE sk.charclass = '$class'"; * @return array */ public function getWaypointsData($file) { - $wp = null; - fseek($this->fp, $this->sData->wpOffsetsNorm); - $a1 = strrev(strtobits(fread($this->fp, 1))); - fseek($this->fp, $this->sData->wpOffsetsNorm + 1); - $a2 = strrev(strtobits(fread($this->fp, 1))); - fseek($this->fp, $this->sData->wpOffsetsNorm + 2); - $a3 = strrev(strtobits(fread($this->fp, 1))); - fseek($this->fp, $this->sData->wpOffsetsNorm + 3); - $a4 = strrev(strtobits(fread($this->fp, 1))); - fseek($this->fp, $this->sData->wpOffsetsNorm + 4); - $a5 = strrev(strtobits(fread($this->fp, 1))); - $wp['Norm'] = str_split($a1 . $a2 . $a3 . $a4 . $a5); + $wp = []; + $offsets = [ + 'Norm' => $this->sData->wpOffsetsNorm, + 'NM' => $this->sData->wpOffsetsNM, + 'Hell' => $this->sData->wpOffsetsHell, + ]; - // ddump($wp['Norm']); + foreach ($offsets as $difficulty => $offset) { + fseek($this->fp, $offset); + $waypointData = ''; - fseek($this->fp, $this->sData->wpOffsetsNM); - $a1 = strrev(strtobits(fread($this->fp, 1))); - fseek($this->fp, $this->sData->wpOffsetsNM + 1); - $a2 = strrev(strtobits(fread($this->fp, 1))); - fseek($this->fp, $this->sData->wpOffsetsNM + 2); - $a3 = strrev(strtobits(fread($this->fp, 1))); - fseek($this->fp, $this->sData->wpOffsetsNM + 3); - $a4 = strrev(strtobits(fread($this->fp, 1))); - fseek($this->fp, $this->sData->wpOffsetsNM + 4); - $a5 = strrev(strtobits(fread($this->fp, 1))); - $wp['NM'] = str_split($a1 . $a2 . $a3 . $a4 . $a5); + for ($i = 0; $i < 5; $i++) { + $a = strrev(strtobits(fread($this->fp, 1))); + $waypointData .= $a; + fseek($this->fp, $offset + $i + 1); + } + + $wp[$difficulty] = str_split($waypointData); + } - fseek($this->fp, $this->sData->wpOffsetsHell); - $a1 = strrev(strtobits(fread($this->fp, 1))); - fseek($this->fp, $this->sData->wpOffsetsHell + 1); - $a2 = strrev(strtobits(fread($this->fp, 1))); - fseek($this->fp, $this->sData->wpOffsetsHell + 2); - $a3 = strrev(strtobits(fread($this->fp, 1))); - fseek($this->fp, $this->sData->wpOffsetsHell + 3); - $a4 = strrev(strtobits(fread($this->fp, 1))); - fseek($this->fp, $this->sData->wpOffsetsHell + 4); - $a5 = strrev(strtobits(fread($this->fp, 1))); - $wp['Hell'] = str_split($a1 . $a2 . $a3 . $a4 . $a5); foreach ($wp['Norm'] as $k => $v) { // if ($v == 1) { diff --git a/src/tabs/Chars.php b/src/tabs/Chars.php index 5592d15..b237788 100644 --- a/src/tabs/Chars.php +++ b/src/tabs/Chars.php @@ -1,66 +1,12 @@ - - - - -
- -
cData['Quests'] as $quest) { - foreach ($quest as $difficulty => $q) { + $quests = ''; + foreach ($c->cData['Quests'] as $quest) { + foreach ($quest as $difficulty => $q) { $quests .= "

$difficulty

"; + foreach ($q as $k => $v) { $kD = str_replace("_", " ", $k); - $kD = str_replace(" NM", "", $kD); - $kD = str_replace(" Hell", "", $kD); - - if ($v == 1) { - $quests .= "
"; - } else { - $quests .= "
"; - } + $kD = str_replace([" NM", " Hell"], "", $kD); + $checked = ($v == 1) ? 'checked' : ''; + + $quests .= ""; + $quests .= "
"; } } } - $wps = null; - $wps .= "

"; - foreach ($c->cData['Waypoints'] as $diff => $waypoints) { - $wps .= "

$diff

"; + $wps = ''; + $wps .= ""; + $wps .= "
"; + $wps .= ""; + $wps .= "
"; + + + + foreach ($c->cData['Waypoints'] as $diff => $waypoints) { // diff is difficulty + $wps .= "

$diff

"; array_pop($waypoints); - foreach ($waypoints as $k => $v) { - $kD = str_replace("_", " ", $k); - $kD = str_replace(" NM", "", $kD); - $kD = str_replace(" Hell", "", $kD); - if ($v == 1 && $k != ''){ - $wps .= "
"; - } else { - $wps .= "
"; - } + $wp_count = 0; + foreach ($waypoints as $k => $v) { + $kD = str_replace("_", " ", $k); + $kD = str_replace([" NM", " Hell"], "", $kD); + $checked = ($v == 1 && $k != '') ? 'checked ' : ''; + + $wps .= ""; + $wps .= "
"; + $wp_count++; } - } - - - - - - + + } + + $classOptions = [ + 'Amazon', 'Assassin', 'Barbarian', 'Druid', 'Paladin', 'Necromancer', 'Sorceress' + ]; $option = ''; - if ($c->cData['CharacterClass'] == 'Amazon'){ - $option .= ""; - } else { - $option .= ""; + + foreach ($classOptions as $class) { + $selected = ($c->cData['CharacterClass'] == $class) ? 'selected' : ''; + $option .= ""; } - if ($c->cData['CharacterClass'] == 'Assassin'){ - $option .= ""; - } else { - $option .= ""; - } - - if ($c->cData['CharacterClass'] == 'Barbarian'){ - $option .= ""; - } else { - $option .= ""; - } + $difficulties = [ + 'Normal' => 'DifficultyNormal', + 'NM' => 'DifficultyNM', + 'Hell' => 'DifficultyHell' + ]; - if ($c->cData['CharacterClass'] == 'Druid'){ - $option .= ""; - } else { - $option .= ""; - } - - if ($c->cData['CharacterClass'] == 'Paladin'){ - $option .= ""; - } else { - $option .= ""; - } - - if ($c->cData['CharacterClass'] == 'Necromancer'){ - $option .= ""; - } else { - $option .= ""; - } - - if ($c->cData['CharacterClass'] == 'Sorceress'){ - $option .= ""; - } else { - $option .= ""; - } - $radio = ''; - if ($c->cData['Difficulty']['Norm'] == 1){ - $radio .= 'Normal
'; - } else { - $radio .= 'Normal
'; + + foreach ($difficulties as $difficulty => $id) { + $checked = ($c->cData['Difficulty'][$difficulty] == 1) ? 'checked' : ''; + $radio .= "$difficulty
"; } - - if ($c->cData['Difficulty']['NM'] == 1){ - $radio .= 'NM
'; - } else { - $radio .= 'NM
'; - } - - - if ($c->cData['Difficulty']['Hell'] == 1){ - $radio .= 'Hell
'; - } else { - $radio .= 'Hell
'; - } - - - - if ($c->cData['Quests'][0]['Norm']["DenOfEvil"]) $a1q1 = "checked"; - if ($c->cData['Quests'][0]['Norm']["SistersBurialGrounds"]) $a1q2 = "checked"; - if ($c->cData['Quests'][0]['Norm']["TheSearchForCain"]) $a1q3 = "checked"; - if ($c->cData['Quests'][0]['Norm']["TheForgottenTower"]) $a1q4 = "checked"; - if ($c->cData['Quests'][0]['Norm']["ToolsOfTheTrade"]) $a1q5 = "checked"; - if ($c->cData['Quests'][0]['Norm']["SistersToTheSlaughter"]) $a1q6 = "checked"; - $skills = ''; - foreach($c->cData['skills']['skills'] as $k => $skill){ - $skills .= ": {$skill['skill']}
"; - } + foreach ($c->cData['skills']['skills'] as $k => $skill) { + $skills .= ": {$skill['skill']}
"; + } + // dump($c['Waypoints']); $tabContent .= << -
- -
-
-
+
+ + +
+
+

{$c->cData['CharacterName']}


- - - Level:
- $radio - $skills - - -
-

Quests

- $quests -
-

Waypoints

- $wps - -
- - - + + + Level:
+ $radio + $skills +
+
+

Quests

+ $quests +
+
+

Waypoints

+ $wps +
- -
- -
+ + +
EOT; echo $tabContent;