D2S Editing WIP

This commit is contained in:
Hash Borgir 2023-06-01 23:42:12 -06:00
parent 3357f757ea
commit b9f4054028
4 changed files with 450 additions and 436 deletions

View File

@ -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();
});
});
$(".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

View File

@ -1,138 +1,200 @@
<?php
// Set error reporting to only show errors and parse errors
error_reporting(E_ERROR | E_PARSE);
// Set the time limit for script execution to unlimited
set_time_limit(-1);
// Set the maximum input time to unlimited
ini_set('max_input_time', '-1');
// Set the maximum execution time to unlimited
ini_set('max_execution_time', '0');
// Start the session
session_start();
// Start output buffering
ob_start();
// Define the constant 'DB_FILE' with the value of 'modname' session variable followed by ".db"
define('DB_FILE', $_SESSION['modname'] . ".db");
// Include the configuration file
require_once './config.php';
// Include the PDO wrapper file
require_once './_pdo.php';
// Include the required class files
require_once "./src/D2Functions.php";
require_once "./src/D2Database.php";
require_once './src/D2Files.php';
require_once './src/D2TxtParser.php';
require_once './src/D2ItemDesc.php';
require_once './src/D2Char.php';
require_once './src/D2Char.php';
require_once './src/D2CharStructureData.php';
require_once './src/D2ByteReader.php';
$dStruct = new D2CharStructureData();
// Create an instance of D2CharStructureData class
$csData = new D2CharStructureData();
$g = $_GET;
$cmd = $g['cmd'];
// Get the file path from the POST data and replace backslashes /
$filePath = $g['filePath'];
$filePath = str_replace("\\", "\\\\", $filePath);
// Handle the WP check/uncheck
if ($cmd == "wp") {
$diff = $g['diff'];
if ($diff == "Norm") {
$offset = 643;
}
if ($diff == "NM") {
$offset = 667;
}
if ($diff == "Hell") {
$offset = 691;
}
/*
array (size=5)
'cmd' => 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);
$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";
}
foreach($p['wp'] as $k => $v) {
$w[$k] = ($dStruct->_qNorm[$k]);
}
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));
// 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.
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));
$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
];
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));
// 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: /');

View File

@ -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() {
@ -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) {

View File

@ -1,66 +1,12 @@
<?php
$form = new Formr\Formr();
/*
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
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
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.
*/
//dump($charData);
?>
<div style="text-align: center; margin-top: 20px;">
<div class="container">
<ul class="nav nav-tabs" id="CTabs" role="tablist">
<?php
foreach ($charData as $c) {
$tabs = '';
// ddump($c);
$tabs .= <<<EOT
<li class="nav-item" role="presentation">
<a class="nav-link btn btn-outline-danger" style="background: #ddd; border: 1px solid #888;" id="{$c->cData['CharacterName']}-tab" data-toggle="tab" href="#{$c->cData['CharacterName']}" role="tab" aria-controls="{$c->cData['CharacterName']}" aria-selected="true"><img height="64" width="" src="img/chars/{$c->cData['CharacterClass']}.gif">{$c->cData['CharacterName']}</a>
</li>
@ -68,174 +14,116 @@ EOT;
echo $tabs;
}
?>
</ul>
</div>
</div>
<div class="tab-content" id="CTabContent">
<?php
foreach ($charData as $c) {
$quests = null;
$quests = '';
foreach ($c->cData['Quests'] as $quest) {
foreach ($quest as $difficulty => $q) {
$quests .= "<h2>$difficulty</h2>";
foreach ($q as $k => $v) {
$kD = str_replace("_", " ", $k);
$kD = str_replace(" NM", "", $kD);
$kD = str_replace(" Hell", "", $kD);
$kD = str_replace([" NM", " Hell"], "", $kD);
$checked = ($v == 1) ? 'checked' : '';
if ($v == 1) {
$quests .= "<input type='checkbox' value='1' name='quest[$k]' id='$k' checked><label for='$k'>$kD</label><br>";
} else {
$quests .= "<input type='checkbox' value='1' name='quest[$k]' id='$k'><label for='$k'>$kD</label><br>";
}
$quests .= "<input type='checkbox' value='1' name='quest[$k]' id='$k' $checked>";
$quests .= "<label for='$k'>$kD</label><br>";
}
}
}
$wps = null;
$wps .= "<input type='radio' value='1' name='wp_all' id='wp_all'><label style='font-size: 1.3em;color:green;' for='wp_all'>Enable All Waypoints</label><br><input type='radio' value='0' name='wp_all' id='wp_all_off'><label style='font-size: 1.3em; color: red;' for='wp_all_off'>Disable All Waypoints</label><hr>";
foreach ($c->cData['Waypoints'] as $diff => $waypoints) {
$wps = '';
$wps .= "<input type='radio' value='1' name='wp_all' id='wp_all'>";
$wps .= "<label style='font-size: 1.3em;color:green;' for='wp_all'>Enable All Waypoints</label><br>";
$wps .= "<input type='radio' value='0' name='wp_all' id='wp_all_off'>";
$wps .= "<label style='font-size: 1.3em; color: red;' for='wp_all_off'>Disable All Waypoints</label><hr>";
foreach ($c->cData['Waypoints'] as $diff => $waypoints) { // diff is difficulty
$wps .= "<h2>$diff</h2>";
array_pop($waypoints);
$wp_count = 0;
foreach ($waypoints as $k => $v) {
$kD = str_replace("_", " ", $k);
$kD = str_replace(" NM", "", $kD);
$kD = str_replace(" Hell", "", $kD);
if ($v == 1 && $k != ''){
$wps .= "<input type='checkbox' value='1' name='wp[$k]' id='$k' checked disabled><label for='$k'>$kD</label><br>";
} else {
$wps .= "<input type='checkbox' value='1' name='wp[$k]' id='$k' disabled><label for='$k'>$kD</label><br>";
}
$kD = str_replace([" NM", " Hell"], "", $kD);
$checked = ($v == 1 && $k != '') ? 'checked ' : '';
$wps .= "<input diff='$diff' class='wpcheck' type='checkbox' value='1' name='$wp_count' id='$k' $checked>";
$wps .= "<label for='$k'>$kD</label><br>";
$wp_count++;
}
}
$classOptions = [
'Amazon', 'Assassin', 'Barbarian', 'Druid', 'Paladin', 'Necromancer', 'Sorceress'
];
$option = '';
if ($c->cData['CharacterClass'] == 'Amazon'){
$option .= "<option value='Amazon' selected>Amazon</option>";
} else {
$option .= "<option value='Amazon'>Amazon</option>";
foreach ($classOptions as $class) {
$selected = ($c->cData['CharacterClass'] == $class) ? 'selected' : '';
$option .= "<option value='$class' $selected>$class</option>";
}
if ($c->cData['CharacterClass'] == 'Assassin'){
$option .= "<option value='Assassin' selected>Assassin</option>";
} else {
$option .= "<option value='Assassin'>Assassin</option>";
}
if ($c->cData['CharacterClass'] == 'Barbarian'){
$option .= "<option value='Barbarian' selected>Barbarian</option>";
} else {
$option .= "<option value='Barbarian'>Barbarian</option>";
}
if ($c->cData['CharacterClass'] == 'Druid'){
$option .= "<option value='Druid' selected>Druid</option>";
} else {
$option .= "<option value='Druid'>Druid</option>";
}
if ($c->cData['CharacterClass'] == 'Paladin'){
$option .= "<option value='Paladin' selected>Paladin</option>";
} else {
$option .= "<option value='Paladin'>Paladin</option>";
}
if ($c->cData['CharacterClass'] == 'Necromancer'){
$option .= "<option value='Necromancer' selected>Necromancer</option>";
} else {
$option .= "<option value='Necromancer'>Necromancer</option>";
}
if ($c->cData['CharacterClass'] == 'Sorceress'){
$option .= "<option value='Sorceress' selected>Sorceress</option>";
} else {
$option .= "<option value='Sorceress'>Sorceress</option>";
}
$difficulties = [
'Normal' => 'DifficultyNormal',
'NM' => 'DifficultyNM',
'Hell' => 'DifficultyHell'
];
$radio = '';
if ($c->cData['Difficulty']['Norm'] == 1){
$radio .= '<input checked type="radio" id="DifficultyNormal" name="Difficulty" value="Normal">Normal<br>';
} else {
$radio .= '<input type="radio" id="DifficultyNormal" name="Difficulty" value="Normal">Normal<br>';
foreach ($difficulties as $difficulty => $id) {
$checked = ($c->cData['Difficulty'][$difficulty] == 1) ? 'checked' : '';
$radio .= "<input $checked type='radio' id='$id' name='Difficulty' value='$difficulty'>$difficulty<br>";
}
if ($c->cData['Difficulty']['NM'] == 1){
$radio .= '<input checked type="radio" id="DifficultyNM" name="Difficulty" value="NM">NM<br>';
} else {
$radio .= '<input type="radio" id="DifficultyNM" name="Difficulty" value="NM">NM<br>';
}
if ($c->cData['Difficulty']['Hell'] == 1){
$radio .= '<input checked type="radio" id="DifficultyHell" name="Difficulty" value="Hell">Hell<br>';
} else {
$radio .= '<input type="radio" id="DifficultyHell" name="Difficulty" value="Hell">Hell<br>';
}
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 .= "<input style='width: 64px;' class='skill-$k skill' name='$k' type='number' min='1' max='99' value='{$skill['points']}'>: {$skill['skill']}<hr>";
foreach ($c->cData['skills']['skills'] as $k => $skill) {
$skills .= "<input style='width: 64px;' class='skill-$k skill' name='skills[$k]' type='number' min='0' max='99' value='{$skill['points']}'>: {$skill['skill']}<hr>";
}
// dump($c['Waypoints']);
$tabContent .= <<<EOT
<div style="background: white;" class="tab-pane fade" id="{$c->cData['CharacterName']}" role="tabpanel" aria-labelledby="{$c->cData['CharacterName']}-tab">
<form method="POST" action="/saveCharacter.php">
<input type="hidden" name="filePath" id="filePath" value="{$c->cData['filePath']}">
<div class="container" style="font-size: 14px;">
<div style="background: white;" class="tab-pane fade" id="{$c->cData['CharacterName']}" role="tabpanel" aria-labelledby="{$c->cData['CharacterName']}-tab">
<form class="charform" method="POST" action="/saveCharacter.php">
<input type="hidden" name="filePath" id="filePath" value="{$c->cData['filePath']}">
<div class="container" style="font-size: 14px;">
<div class="row">
<div class="col">
<div class="col">
<h1 style="margin: 15px;">{$c->cData['CharacterName']}</h1>
<input class="btn btn-danger" style="" type="submit" value="Save Character">
<img src="/img/chars/{$c->cData['CharacterClass']}.gif"><br>
<input type="text" id="ChracterName" name="ChracterName" maxlength="16" value="{$c->cData['CharacterName']}">
<select id='CharacterClass'>
$option
</select>
Level: <input style="border: 1px solid black;width: 54px;" type="number" id="CharacterLevel" value="{$c->cData['CharacterLevel']}"><br>
$radio
$skills
</div>
<div class="col"><h2>Quests</h2>
$quests
</div>
<div class="col"><h2>Waypoints</h2>
$wps
</div>
<input type="text" id="ChracterName" name="ChracterName" maxlength="16" value="{$c->cData['CharacterName']}">
<select id='CharacterClass'>
$option
</select>
Level: <input style="border: 1px solid black;width: 54px;" type="number" id="CharacterLevel" value="{$c->cData['CharacterLevel']}"><br>
$radio
$skills
</div>
<div class="col">
<h2>Quests</h2>
$quests
</div>
<div class="col">
<h2>Waypoints</h2>
$wps
</div>
</div>
</div>
<input class="btn btn-danger" style="" type="submit" value="Save Character">
</form>
</div>
<input class="btn btn-danger" style="" type="submit" value="Save Character">
</form>
</div>
EOT;
echo $tabContent;