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

@ -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,17 +1,33 @@
<?php <?php
// Set error reporting to only show errors and parse errors
error_reporting(E_ERROR | E_PARSE); error_reporting(E_ERROR | E_PARSE);
// Set the time limit for script execution to unlimited
set_time_limit(-1); set_time_limit(-1);
// Set the maximum input time to unlimited
ini_set('max_input_time', '-1'); ini_set('max_input_time', '-1');
// Set the maximum execution time to unlimited
ini_set('max_execution_time', '0'); ini_set('max_execution_time', '0');
// Start the session
session_start(); session_start();
// Start output buffering
ob_start(); ob_start();
// Define the constant 'DB_FILE' with the value of 'modname' session variable followed by ".db"
define('DB_FILE', $_SESSION['modname'] . ".db"); define('DB_FILE', $_SESSION['modname'] . ".db");
// Include the configuration file
require_once './config.php'; require_once './config.php';
// Include the PDO wrapper file
require_once './_pdo.php'; require_once './_pdo.php';
// Include the required class files
require_once "./src/D2Functions.php"; require_once "./src/D2Functions.php";
require_once "./src/D2Database.php"; require_once "./src/D2Database.php";
require_once './src/D2Files.php'; require_once './src/D2Files.php';
@ -19,120 +35,166 @@ require_once './src/D2TxtParser.php';
require_once './src/D2ItemDesc.php'; require_once './src/D2ItemDesc.php';
require_once './src/D2Char.php'; require_once './src/D2Char.php';
require_once './src/D2CharStructureData.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
$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";
}
}
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) { foreach ($p['quest'] as $k => $v) {
$q[$k] = ($dStruct->_qNorm[$k]); $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) { foreach ($p['wp'] as $k => $v) {
$w[$k] = ($dStruct->_qNorm[$k]); $w[$k] = $dStruct->_qNorm[$k];
} }
// Get the file path from the POST data and replace backslashes /
$filePath = $p['filePath']; $filePath = $p['filePath'];
$filePath = str_replace("\\", "\\\\", $filePath); $filePath = str_replace("\\", "\\\\", $filePath);
// Open the file in binary mode for both reading and writing
$fp = fopen($filePath, "rb+"); $fp = fopen($filePath, "rb+");
// delete old checksum at offset 0x0C - byte 12 // Edit quests which are checked to set them as 'Just finished' (FE FF)
//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
foreach ($q as $k => $v) { foreach ($q as $k => $v) {
// Set the file pointer position to the quest offset
fseek($fp, $v); fseek($fp, $v);
// Write the byte value 0xFE at the current position
fwrite($fp, pack('C', 0xFE)); fwrite($fp, pack('C', 0xFE));
// Move the file pointer to the next position
fseek($fp, $v + 1); fseek($fp, $v + 1);
// Write the byte value 0xFF at the current position
fwrite($fp, pack('C', 0xFF)); 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); // Array containing the fseek wp offsets
fwrite($fp, pack('C', 0xFF)); // 5 acts per difficulty
fseek($fp, 668); // starts at 641, but we skip two bytes, go to 643, then read next 5 bytes, each byte is a bitfield per act.
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); $offsets = [
fwrite($fp, pack('C', 0x00)); 643, 644, 645, 646, 647, // First set of offsets Norm
fseek($fp, 668); 667, 668, 669, 670, 671, // Second set of offsets NM
fwrite($fp, pack('C', 0x00)); 691, 692, 693, 694, 695 // Third set of offsets Hell
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); // Determine the value to write based on the condition $p['wp_all'] == "1"
fwrite($fp, pack('C', 0x00)); $valueToWrite = ($p['wp_all'] == "1") ? 0xFF : 0x00;
fseek($fp, 692);
fwrite($fp, pack('C', 0x00)); // Loop over each offset in the $offsets array
fseek($fp, 693); foreach ($offsets as $offset) {
fwrite($fp, pack('C', 0x00)); // Set the file pointer position to the current offset
fseek($fp, 694); fseek($fp, $offset);
fwrite($fp, pack('C', 0x00));
fseek($fp, 695); // Write the value specified by pack('C', $valueToWrite)
fwrite($fp, pack('C', 0x00)); fwrite($fp, pack('C', $valueToWrite));
} }
$checksum = (shell_exec("bin\d2scs.exe \"$filePath\"")); $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); fclose($fp);
header('Location: /'); header('Location: /');

View File

@ -14,47 +14,60 @@ require_once 'D2Functions.php';
class D2Char { 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 * @var null
*/ */
private $ISC = null; private $ISC = null;
/** /**
* @var null * @var null
*/ */
private $skillData = 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 * @return void
*/ */
public function save() { 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) { public function __construct($file) {
$this->sData = new D2CharStructureData(); $this->sData = new D2CharStructureData(); // Create a new instance of D2CharStructureData
$this->filePath = $_SESSION['savepath'] . $file; $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"); $this->fp = fopen($this->filePath, "r+b"); // Open the file in read/write binary mode
$data = file_get_contents($this->filePath); $data = file_get_contents($this->filePath); // Read the contents of the file
$this->ByteReader = new D2ByteReader($data); $this->ByteReader = new D2ByteReader($data); // Create a new instance of D2ByteReader with the file data
$this->data = $this->ByteReader->getData(); $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"; $sql = "SELECT ID,Stat,CSvBits,ValShift FROM itemstatcost WHERE Saved=1";
$ISCData = PDO_FetchAll($sql); $ISCData = PDO_FetchAll($sql);
foreach ($ISCData as $k => $v) { foreach ($ISCData as $k => $v) {
$this->ISC[$v['ID']] = $v; $this->ISC[$v['ID']] = $v;
$this->_ISC[$v['Stat']] = $v; $this->_ISC[$v['Stat']] = $v;
} }
// read offsets here from sData and put into $this->bData // Read offsets from sData and store them in $this->bData
// which will be used for cData output
foreach ($this->sData->offsets as $k => $v) { foreach ($this->sData->offsets as $k => $v) {
fseek($this->fp, $k); fseek($this->fp, $k); // Move the file pointer to the specified offset
$this->bData[$k] = fread($this->fp, $v); $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]); $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 $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 skilldesc ON sk.skilldesc = skilldesc.skilldesc
LEFT JOIN strings on skilldesc.`str name` = strings.Key LEFT JOIN strings on skilldesc.`str name` = strings.Key
WHERE sk.charclass = '$class'"; WHERE sk.charclass = '$class'";
$sd = PDO_FetchAll($sql); $sd = PDO_FetchAll($sql); // Fetch skill data from the database
foreach ($sd as $k => $v) { foreach ($sd as $k => $v) {
$this->skillData[$k + 1] = $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 * @return void
*/ */
public function parseItems() { public function parseItems() {
$i_TotalOffset = strpos($this->data, "JM"); $i_TotalOffset = strpos($this->data, "JM"); // Find the offset of the "JM" marker in the data
fseek($this->fp, $i_TotalOffset + 2); 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]; $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 = []; $i_Offsets = [];
for ($i = 0; $i <= $i_Total; $i++) { 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) { 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 = []; $_items = [];
foreach ($itemOffsets as $offset => $bytes) { 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['CharacterProgression'] = $this->sData->characterProgressionClassicHC[$progression];
} }
$cData['CharacterClass'] = $this->sData->class[unpack('C', $this->bData[40])[1]]; $cData['CharacterClass'] = $this->sData->class[unpack('C', $this->bData[40])[1]];
$cData['CharacterLevel'] = unpack('C', $this->bData[43])[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]); $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 * @return array
*/ */
public function parseSkills() { 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); $jm = strposX($this->data, 'JM', 1);
$skills = ($this->ByteReader->readc($if, ($jm - $if))); $skills = ($this->ByteReader->readc($if, ($jm - $if)));
$cData = []; // Initialize the result array
foreach ($skills as $k => $v) { foreach ($skills as $k => $v) {
if ($this->skillData[$k]['String']) { if ($this->skillData[$k]['String']) {
$cData['skills'][$k] = [ $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; 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 * @return void
*/ */
public function setAllSkills(int $points) { 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); $jm = strposX($this->data, 'JM', 1);
$len = $jm - $if; $len = $jm - $if;
for ($i = 0; $i < $len; $i++) { 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 * Set the points for a specific skill.
* @param int $points *
* @param int $skill The skill ID.
* @param int $points The number of points to set for the skill.
* @return void * @return void
*/ */
public function setSkill(int $skill, int $points) { public function setSkill(int $skill, int $points) {
$skill -= 1; $skill -= 1; // Adjust the skill ID to match the array index
$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); $jm = strposX($this->data, 'JM', 1);
// set $kill to $points
// Set the points for the specified skill
$this->ByteReader->writeByte($if + $skill, $points); $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 * @return void
*/ */
public function parseStats() { 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); $if = strposX($this->data, 'if', 1);
$len = $if - $gf; $len = $if - $gf;
$stats = new D2BitReader($this->ByteReader->toBits($this->ByteReader->readh($gf, $len))); $stats = new D2BitReader($this->ByteReader->toBits($this->ByteReader->readh($gf, $len)));
$bits = $stats->getBits(); $bits = $stats->getBits();
$bytes = $this->ByteReader->toBytes($bits); $bytes = $this->ByteReader->toBytes($bits);
$stats->rewind(); $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++) { for ($i = 0; $i <= count($this->ISC); $i++) {
$id = hexdec($this->ByteReader->toBytesR($stats->readb(9))); $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'] !== '') { if ($this->ISC[$id]['CSvBits'] !== NULL && $this->ISC[$id]['CSvBits'] !== '') {
$stats->skip($this->ISC[$id]['CSvBits']); $stats->skip($this->ISC[$id]['CSvBits']);
} }
$ids[$id] = $id;
$ids[$id] = $id; // Store the ID in the array
} }
$stats->rewind(); $stats->rewind();
$values = []; // Array to store the parsed stat values
// Iterate over the collected stat IDs and retrieve their values
foreach ($ids as $id) { foreach ($ids as $id) {
$stats->skip(9); $stats->skip(9);
// Skip the bits corresponding to the stat if needed
if ($this->ISC[$id]['CSvBits'] !== NULL && $this->ISC[$id]['CSvBits'] !== '') { if ($this->ISC[$id]['CSvBits'] !== NULL && $this->ISC[$id]['CSvBits'] !== '') {
$val = $stats->readb($this->ISC[$id]['CSvBits']); $val = $stats->readb($this->ISC[$id]['CSvBits']);
$stat = $this->ISC[$id]['Stat']; $stat = $this->ISC[$id]['Stat'];
$values[$stat] = hexdec($this->ByteReader->toBytesR($val)); $values[$stat] = hexdec($this->ByteReader->toBytesR($val));
} }
} }
// Perform additional calculations or conversions on specific stats
$values['hitpoints'] = (int) round($values['hitpoints'] >> 11); $values['hitpoints'] = (int) round($values['hitpoints'] >> 11);
$values['maxhp'] = (int) round($values['maxhp'] >> 11); $values['maxhp'] = (int) round($values['maxhp'] >> 11);
$values['mana'] = (int) round($values['mana'] >> 11); $values['mana'] = (int) round($values['mana'] >> 11);
$values['maxmana'] = (int) round($values['maxmana'] >> 11); $values['maxmana'] = (int) round($values['maxmana'] >> 11);
$values['stamina'] = (int) round($values['stamina'] >> 11); $values['stamina'] = (int) round($values['stamina'] >> 11);
$values['maxstamina'] = (int) round($values['maxstamina'] >> 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 string $stat
* @param mixed $val * @param mixed $val
* @param mixed|null $val2 * @param mixed|null $val2
@ -349,61 +393,54 @@ WHERE sk.charclass = '$class'";
]; ];
$this->ByteReader->writeByte(40, $classes[$val]); $this->ByteReader->writeByte(40, $classes[$val]);
break; break;
case "CharacterLevel": case "CharacterLevel":
if ($val > 99) { if ($val > 99) {
$val = 99; $val = 99;
} }
// level is edited in two places.
// byte 43
$this->ByteReader->writeByte(43, $val); $this->ByteReader->writeByte(43, $val);
// and in charstats
$this->setStat('level', $val); $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'"; $sql = "SELECT {$this->cData['CharacterClass']} FROM experience WHERE level = '$val'";
$res = PDO_FetchOne($sql); $res = PDO_FetchOne($sql);
$this->setStat('experience', $res); $this->setStat('experience', $res);
break; break;
case 'CharacterStatus': case 'CharacterStatus':
$status = strrev(strtobits($this->data[36]));
switch ($val) { switch ($val) {
case 'Died': case 'Died':
$status = (strtobits($this->data[36]));
$status[3] = $val2; $status[3] = $val2;
$byte = $this->ByteReader->bitsToHexString($status);
$this->ByteReader->writeByte(36, hexdec($byte));
break; break;
case 'Hardcore': case 'Hardcore':
$status = (strtobits($this->data[36]));
$status[2] = $val2; $status[2] = $val2;
$byte = $this->ByteReader->bitsToHexString($status);
$this->ByteReader->writeByte(36, hexdec($byte));
break; break;
case 'Expansion': case 'Expansion':
$status = strrev(strtobits($this->data[36]));
$status[5] = $val2; $status[5] = $val2;
break;
}
$byte = $this->ByteReader->bitsToHexString($status); $byte = $this->ByteReader->bitsToHexString($status);
$this->ByteReader->writeByte(36, hexdec($byte)); $this->ByteReader->writeByte(36, hexdec($byte));
break; break;
}
break; case 'CharacterProgression':
case 'CharacterProgression': // 0 in normal, 1 finished normal, 2 finished nm, 3 finished hell
switch ($val) { switch ($val) {
case 0: // in normal case 0:
$this->data[37] = pack('C', 3); $this->data[37] = pack('C', 3);
break; break;
case 1: // finished normal case 1:
$this->data[37] = pack('C', 8); $this->data[37] = pack('C', 8);
break; break;
case 2: // finished nm case 2:
$this->data[37] = pack('C', 13); $this->data[37] = pack('C', 13);
break; break;
case 3: // finished hell case 3:
$this->data[37] = pack('C', 15); $this->data[37] = pack('C', 15);
break; break;
$this->save();
} }
$this->save();
break; break;
case 'LeftmousebuttonskillID': case 'LeftmousebuttonskillID':
@ -423,12 +460,13 @@ WHERE sk.charclass = '$class'";
break; break;
} }
// finally save char data to d2s file
$this->data = $this->ByteReader->getData(); $this->data = $this->ByteReader->getData();
$this->save(); $this->save();
} }
/** /**
* Reset the file size of the D2S file.
*
* @return void * @return void
*/ */
public function resetFileSize() { public function resetFileSize() {
@ -436,8 +474,8 @@ WHERE sk.charclass = '$class'";
$this->fp = fopen($this->filePath, "r+b"); $this->fp = fopen($this->filePath, "r+b");
fseek($this->fp, 8); fseek($this->fp, 8);
fwrite($this->fp, pack('L', $filesize)); fwrite($this->fp, pack('L', $filesize));
fclose($this->fp); fclose($this->fp);
$this->fp = fopen($this->filePath, "r+b"); $this->fp = fopen($this->filePath, "r+b");
checksumFix($this->filePath); 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() { public function generateAllStats() {
// 003C08E081000F067860C001071C0008020800F040020064A000000A2C00C0030C000D000000006E05000000FE3F
$stats = ''; $stats = '';
for ($i = 0; $i < 16; $i++) { for ($i = 0; $i < 16; $i++) {
$id = strrev(str_pad((decbin((int) $this->ISC[$i]['ID'])), 9, 0, STR_PAD_LEFT)); $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)); $val = strrev(str_pad(decbin(20), (int) $this->ISC[$i]['CSvBits'], '0', STR_PAD_LEFT));
// dump($id);
// dump($val);
$stat = $id . $val; $stat = $id . $val;
$stats .= $stat; $stats .= $stat;
} }
$stats .= "000011111111100"; $stats .= "000011111111100";
//dump($stats);
$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); $if = strposX($this->data, 'if', 1);
$len = $if - $gf; $len = $if - $gf;
$bytes = $this->ByteReader->toBytes($stats); $bytes = $this->ByteReader->toBytes($stats);
// refresh this data // Refresh the data
$data = $this->ByteReader->getData(); $data = $this->ByteReader->getData();
// delete everything between GF---and---IF // Delete everything between GF and IF
$data = substr_replace($data, "", $gf, $len); $data = substr_replace($data, '', $gf, $len);
// pack hex bites into binary string // Pack hex bytes into a binary string
$packedbytes = (pack('H*', $bytes)); $packedBytes = pack('H*', $bytes);
// now insert new packed byte stat data between gf and if // Insert the new packed byte stat data between GF and IF
$data = substr_replace($data, $packedbytes, 767, 0); $data = substr_replace($data, $packedBytes, 767, 0);
$this->data = $data; $this->data = $data;
$this->save(); $this->save();
$this->resetFileSize(); $this->resetFileSize();
$filedata = file_get_contents($this->filePath); $fileData = file_get_contents($this->filePath);
$this->ByteReader = new D2ByteReader($filedata); $this->ByteReader = new D2ByteReader($fileData);
$this->data = $this->ByteReader->getData(); $this->data = $this->ByteReader->getData();
return $this->data; return $this->data;
} }
/** /**
* @param string $stat * Set a specific stat in the D2S file.
* @param mixed $val *
* @param string $stat The stat to set.
* @param mixed $val The value to set for the stat.
* @return void * @return void
*/ */
public function setStat(string $stat, mixed $val) { public function setStat(string $stat, mixed $val) {
@ -549,44 +584,59 @@ WHERE sk.charclass = '$class'";
} }
/** /**
* @param $file * Get the quest data from the specified file.
* @return array *
* @param string $file The file to read the quest data from.
* @return array The quest data.
*/ */
public function getQuestData($file) { public function getQuestData($file) {
$questsNorm = null; $questsNorm = null;
$questsNM = null; $questsNM = null;
$questsHell = null; $questsHell = null;
$quests = null; $quests = null;
// Read quests from qNorm
foreach ($this->sData->qNorm as $k => $v) { foreach ($this->sData->qNorm as $k => $v) {
fseek($this->fp, $k); fseek($this->fp, $k);
$questsNorm[$k] = fread($this->fp, 2); $questsNorm[$k] = fread($this->fp, 2);
} }
// Read quests from qNM
foreach ($this->sData->qNM as $k => $v) { foreach ($this->sData->qNM as $k => $v) {
fseek($this->fp, $k); fseek($this->fp, $k);
$questsNM[$k] = fread($this->fp, 2); $questsNM[$k] = fread($this->fp, 2);
} }
// Read quests from qHell
foreach ($this->sData->qHell as $k => $v) { foreach ($this->sData->qHell as $k => $v) {
fseek($this->fp, $k); fseek($this->fp, $k);
$questsHell[$k] = fread($this->fp, 2); $questsHell[$k] = fread($this->fp, 2);
} }
// Process quests from qNorm
foreach ($questsNorm as $k => $v) { foreach ($questsNorm as $k => $v) {
$x = (str_split(strtobits($v), 8)); $x = (str_split(strtobits($v), 8));
// if ($x[0][0]) { // if ($x[0][0]) {
$quests['Norm'][$this->sData->qNorm[$k]] = $x[0][0]; $quests['Norm'][$this->sData->qNorm[$k]] = $x[0][0];
// } // }
} }
// Process quests from qNM
foreach ($questsNM as $k => $v) { foreach ($questsNM as $k => $v) {
$x = array_filter(str_split(strtobits($v), 8)); $x = array_filter(str_split(strtobits($v), 8));
// if ($x[0][0]) { // if ($x[0][0]) {
$quests['NM'][$this->sData->qNM[$k]] = $x[0][0]; $quests['NM'][$this->sData->qNM[$k]] = $x[0][0];
// } // }
} }
// Process quests from qHell
foreach ($questsHell as $k => $v) { foreach ($questsHell as $k => $v) {
$x = array_filter(str_split(strtobits($v), 8)); $x = array_filter(str_split(strtobits($v), 8));
// if ($x[0][0]) { // if ($x[0][0]) {
$quests['Hell'][$this->sData->qHell[$k]] = $x[0][0]; $quests['Hell'][$this->sData->qHell[$k]] = $x[0][0];
// } // }
} }
return $quests; return $quests;
} }
@ -595,44 +645,26 @@ WHERE sk.charclass = '$class'";
* @return array * @return array
*/ */
public function getWaypointsData($file) { public function getWaypointsData($file) {
$wp = null; $wp = [];
fseek($this->fp, $this->sData->wpOffsetsNorm); $offsets = [
$a1 = strrev(strtobits(fread($this->fp, 1))); 'Norm' => $this->sData->wpOffsetsNorm,
fseek($this->fp, $this->sData->wpOffsetsNorm + 1); 'NM' => $this->sData->wpOffsetsNM,
$a2 = strrev(strtobits(fread($this->fp, 1))); 'Hell' => $this->sData->wpOffsetsHell,
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);
// ddump($wp['Norm']); foreach ($offsets as $difficulty => $offset) {
fseek($this->fp, $offset);
$waypointData = '';
fseek($this->fp, $this->sData->wpOffsetsNM); for ($i = 0; $i < 5; $i++) {
$a1 = strrev(strtobits(fread($this->fp, 1))); $a = strrev(strtobits(fread($this->fp, 1)));
fseek($this->fp, $this->sData->wpOffsetsNM + 1); $waypointData .= $a;
$a2 = strrev(strtobits(fread($this->fp, 1))); fseek($this->fp, $offset + $i + 1);
fseek($this->fp, $this->sData->wpOffsetsNM + 2); }
$a3 = strrev(strtobits(fread($this->fp, 1)));
fseek($this->fp, $this->sData->wpOffsetsNM + 3); $wp[$difficulty] = str_split($waypointData);
$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);
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) { foreach ($wp['Norm'] as $k => $v) {
// if ($v == 1) { // 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 style="text-align: center; margin-top: 20px;">
<div class="container"> <div class="container">
<ul class="nav nav-tabs" id="CTabs" role="tablist"> <ul class="nav nav-tabs" id="CTabs" role="tablist">
<?php <?php
foreach ($charData as $c) { foreach ($charData as $c) {
$tabs = ''; $tabs = '';
// ddump($c); // ddump($c);
$tabs .= <<<EOT $tabs .= <<<EOT
<li class="nav-item" role="presentation"> <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> <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> </li>
@ -68,141 +14,88 @@ EOT;
echo $tabs; echo $tabs;
} }
?> ?>
</ul> </ul>
</div> </div>
</div> </div>
<div class="tab-content" id="CTabContent"> <div class="tab-content" id="CTabContent">
<?php <?php
foreach ($charData as $c) { foreach ($charData as $c) {
$quests = null; $quests = '';
foreach ($c->cData['Quests'] as $quest) { foreach ($c->cData['Quests'] as $quest) {
foreach ($quest as $difficulty => $q) { foreach ($quest as $difficulty => $q) {
$quests .= "<h2>$difficulty</h2>"; $quests .= "<h2>$difficulty</h2>";
foreach ($q as $k => $v) { foreach ($q as $k => $v) {
$kD = str_replace("_", " ", $k); $kD = str_replace("_", " ", $k);
$kD = str_replace(" NM", "", $kD); $kD = str_replace([" NM", " Hell"], "", $kD);
$kD = str_replace(" Hell", "", $kD); $checked = ($v == 1) ? 'checked' : '';
if ($v == 1) { $quests .= "<input type='checkbox' value='1' name='quest[$k]' id='$k' $checked>";
$quests .= "<input type='checkbox' value='1' name='quest[$k]' id='$k' checked><label for='$k'>$kD</label><br>"; $quests .= "<label for='$k'>$kD</label><br>";
} else {
$quests .= "<input type='checkbox' value='1' name='quest[$k]' id='$k'><label for='$k'>$kD</label><br>";
}
} }
} }
} }
$wps = null; $wps = '';
$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>"; $wps .= "<input type='radio' value='1' name='wp_all' id='wp_all'>";
foreach ($c->cData['Waypoints'] as $diff => $waypoints) { $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>"; $wps .= "<h2>$diff</h2>";
array_pop($waypoints); array_pop($waypoints);
$wp_count = 0;
foreach ($waypoints as $k => $v) { foreach ($waypoints as $k => $v) {
$kD = str_replace("_", " ", $k); $kD = str_replace("_", " ", $k);
$kD = str_replace(" NM", "", $kD); $kD = str_replace([" NM", " Hell"], "", $kD);
$kD = str_replace(" Hell", "", $kD); $checked = ($v == 1 && $k != '') ? 'checked ' : '';
if ($v == 1 && $k != ''){
$wps .= "<input type='checkbox' value='1' name='wp[$k]' id='$k' checked disabled><label for='$k'>$kD</label><br>"; $wps .= "<input diff='$diff' class='wpcheck' type='checkbox' value='1' name='$wp_count' id='$k' $checked>";
} else { $wps .= "<label for='$k'>$kD</label><br>";
$wps .= "<input type='checkbox' value='1' name='wp[$k]' id='$k' disabled><label for='$k'>$kD</label><br>"; $wp_count++;
} }
} }
}
$classOptions = [
'Amazon', 'Assassin', 'Barbarian', 'Druid', 'Paladin', 'Necromancer', 'Sorceress'
];
$option = ''; $option = '';
if ($c->cData['CharacterClass'] == 'Amazon'){
$option .= "<option value='Amazon' selected>Amazon</option>"; foreach ($classOptions as $class) {
} else { $selected = ($c->cData['CharacterClass'] == $class) ? 'selected' : '';
$option .= "<option value='Amazon'>Amazon</option>"; $option .= "<option value='$class' $selected>$class</option>";
} }
if ($c->cData['CharacterClass'] == 'Assassin'){ $difficulties = [
$option .= "<option value='Assassin' selected>Assassin</option>"; 'Normal' => 'DifficultyNormal',
} else { 'NM' => 'DifficultyNM',
$option .= "<option value='Assassin'>Assassin</option>"; 'Hell' => 'DifficultyHell'
} ];
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>";
}
$radio = ''; $radio = '';
if ($c->cData['Difficulty']['Norm'] == 1){
$radio .= '<input checked type="radio" id="DifficultyNormal" name="Difficulty" value="Normal">Normal<br>'; foreach ($difficulties as $difficulty => $id) {
} else { $checked = ($c->cData['Difficulty'][$difficulty] == 1) ? 'checked' : '';
$radio .= '<input type="radio" id="DifficultyNormal" name="Difficulty" value="Normal">Normal<br>'; $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 = ''; $skills = '';
foreach ($c->cData['skills']['skills'] as $k => $skill) { 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>"; $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']); // dump($c['Waypoints']);
$tabContent .= <<<EOT $tabContent .= <<<EOT
<div style="background: white;" class="tab-pane fade" id="{$c->cData['CharacterName']}" role="tabpanel" aria-labelledby="{$c->cData['CharacterName']}-tab"> <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"> <form class="charform" method="POST" action="/saveCharacter.php">
<input type="hidden" name="filePath" id="filePath" value="{$c->cData['filePath']}"> <input type="hidden" name="filePath" id="filePath" value="{$c->cData['filePath']}">
<div class="container" style="font-size: 14px;"> <div class="container" style="font-size: 14px;">
<div class="row"> <div class="row">
@ -217,21 +110,16 @@ EOT;
Level: <input style="border: 1px solid black;width: 54px;" type="number" id="CharacterLevel" value="{$c->cData['CharacterLevel']}"><br> Level: <input style="border: 1px solid black;width: 54px;" type="number" id="CharacterLevel" value="{$c->cData['CharacterLevel']}"><br>
$radio $radio
$skills $skills
</div> </div>
<div class="col"><h2>Quests</h2> <div class="col">
<h2>Quests</h2>
$quests $quests
</div> </div>
<div class="col"><h2>Waypoints</h2> <div class="col">
<h2>Waypoints</h2>
$wps $wps
</div> </div>
</div> </div>
</div> </div>
<input class="btn btn-danger" style="" type="submit" value="Save Character"> <input class="btn btn-danger" style="" type="submit" value="Save Character">
</form> </form>