latest, d2s stuff

This commit is contained in:
Hash Borgir
2022-07-03 04:13:50 -06:00
parent e51e40175e
commit f3b24de2a8
11 changed files with 393 additions and 239 deletions

View File

@@ -6,31 +6,40 @@ require_once 'D2BitReader.php';
require_once 'D2Strings.php';
require_once 'D2Item.php';
require_once 'D2ByteReader.php';
require_once 'D2Functions.php';
class D2Char {
public $cData = null; // char data output
public $items = null; // char item data
public $ByteReader = null; // put $data into bytereader
public $filePath = null; // .d2s file path
private $sData = null; // char file structure data
private $bData = null; // char binary data from d2s
private $filePath = null; // .d2s file path
private $fp = null; // file pointer
private $data = null; // full d2s file loaded in $data
private $ByteReader = null; // put $data into bytereader
private $ISC = null;
private $skillData = null;
public function save() {
file_put_contents($this->filePath, $this->data);
checksumFix($this->filePath);
}
public function __construct($file) {
$this->sData = new D2CharStructureData();
$this->filePath = $_SESSION['savepath'] . $file;
$this->fp = fopen($this->filePath, "r+b");
$this->data = file_get_contents($this->filePath);
$this->ByteReader = new D2ByteReader($this->data);
$data = file_get_contents($this->filePath);
$this->ByteReader = new D2ByteReader($data);
$this->data = $this->ByteReader->getData();
$sql = "SELECT ID,Stat,CSvBits FROM itemstatcost WHERE Saved=1";
$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
@@ -40,6 +49,18 @@ class D2Char {
$this->bData[$k] = fread($this->fp, $v);
}
$classes = array_flip(['ama' => 0, 'sor' => 1, 'nec' => 2, 'pal' => 3, 'bar' => 4, 'dru' => 5, 'ass' => 6]);
$class = $classes[hexdec($this->ByteReader->readh(40, 1))];
$sql = "SELECT sk.id,sk.Skill,sk.skilldesc,`str name`,strings.`String`,skilldesc.SkillPage,skilldesc.SkillRow,skilldesc.SkillColumn,skilldesc.ListRow,skilldesc.ListPool,skilldesc.IconCel
FROM skills as sk
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);
foreach ($sd as $k => $v) {
$this->skillData[$k + 1] = $v;
}
return $this->parseChar(); // end of parseChar() calls parseItems(), parseStats, etc.
}
@@ -85,7 +106,8 @@ class D2Char {
$skills = (unpack('l16', $this->bData[56]));
foreach ($skills as $skill) {
$cData['Assignedskills'][] = $this->sData->skills[$skill];
$cData['Assignedskills'][] = ($this->sData->skills[$skill]);
$cData['Assignedskills'] = array_filter($cData['Assignedskills']);
}
$cData['LeftmousebuttonskillID'] = $this->sData->skills[unpack('i', $this->bData[120])[1]];
@@ -101,7 +123,7 @@ class D2Char {
$onDifficulty['Normal'] = $x[0][0];
$onDifficulty['NM'] = $x[1][0];
$onDifficulty['Hell'] = $x[2][0];
$cData['Difficulty'] = ($onDifficulty); // $diff;
$cData['Difficulty'] = array_filter($onDifficulty); // $diff;
// Map ID. This value looks like a random number, but it corresponds with one of the longwords
// found in the character.map file, according to the difficulty being played. Not needed
//$cData['MapID'] = $this->bData[171];
@@ -123,45 +145,83 @@ class D2Char {
$cData['items'] = $this->items; // cData[items] will be $this->items
$this->cData = $cData;
// parse stats
$this->parseStats();
$this->parseStats();
$this->cData['skills'] = $this->parseSkills();
unset($this->items);
unset($this->bData);
unset($this->sData);
unset($this->fp);
unset($this->items);
unset($this->bData);
unset($this->sData);
unset($this->ByteReader);
unset($this->data);
unset($this->fp);
unset($this->ISC);
return $this->cData;
}
public function parseSkills() {
$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)));
foreach ($skills as $k => $v) {
if ($this->skillData[$k]['String']) {
$cData['skills'][$k] = [
'skill' => $this->skillData[$k]['String'],
'points' => $v,
'page' => $this->skillData[$k]['SkillPage'],
'row' => $this->skillData[$k]['ListRow'],
'col' => $this->skillData[$k]['SkillColumn'],
'icon' => $this->skillData[$k]['IconCel'],
];
}
}
$cData['skills'] = array_values($cData['skills']);
return $cData;
}
public function setAllSkills(int $points) {
$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->data = $this->ByteReader->getData();
$this->save();
}
public function setSkill(int $skill, int $points) {
$skill -= 1;
$if = strposX($this->data, 'if', 1) + 2; // find if and skip it
$jm = strposX($this->data, 'JM', 1);
// set $kill to $points
$this->ByteReader->writeByte($if + $skill, $points);
$this->data = $this->ByteReader->getData();
$this->save();
}
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();
$bits = $stats->getBits();
$cleanbits = substr($bits, 0, -11);
$stats->setBits($cleanbits);
$bits = $stats->getBits();
$bytes = $this->ByteReader->toBytes($bits);
$stats->rewind();
for($i=0; $i <= strlen($bits); $i++) {
for ($i = 0; $i <= strlen($bits); $i++) {
$id = hexdec($this->ByteReader->toBytesR($stats->readb(9)));
if($this->ISC[$id]['CSvBits'] !== NULL){
if ($this->ISC[$id]['CSvBits'] !== NULL) {
$stats->skip($this->ISC[$id]['CSvBits']);
}
$ids[$id] = $id;
}
$stats->rewind();
foreach($ids as $id){
foreach ($ids as $id) {
$stats->skip(9);
if($this->ISC[$id]['CSvBits'] !== NULL){
if ($this->ISC[$id]['CSvBits'] !== NULL) {
$val = $stats->readb($this->ISC[$id]['CSvBits']);
}
}
$stat = $this->ISC[$id]['Stat'];
$values[$stat] = hexdec($this->ByteReader->toBytesR($val));
}
@@ -172,10 +232,126 @@ class D2Char {
$values['stamina'] = (int) round($values['stamina'] >> 11);
$values['maxstamina'] = (int) round($values['maxstamina'] >> 11);
$values['killcounter'] = (int) round($values['killcounter'] >> 1);
$this->cData['stats'] = $values;
}
public function setChar(string $stat, mixed $val) {
switch ($stat) {
case 'CharacterName':
if (strlen($val) < 1 || strlen($val) > 15) {
return false;
}
$pack = $this->ByteReader->bitsToHexString($this->ByteReader->toBits(pack('Z16', $val)));
$this->ByteReader->writeBytes(20, $pack);
$this->data = $this->ByteReader->getData();
$this->save();
rename($this->filePath, $_SESSION['savepath'] . "$val.d2s");
break;
case "CharacterClass":
$this->ByteReader->writeByte(40, $val);
$this->data = $this->ByteReader->getData();
$this->save();
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;
}
}
public function generateAllStats() {
// 003C08E081000F067860C001071C0008020800F040020064A000000A2C00C0030C000D000000006E05000000FE3F
$stats = '';
foreach ($this->ISC as $i) {
$id = strrev(str_pad((decbin($i['ID'])), 9, 0, STR_PAD_LEFT));
$val = strrev(str_pad((decbin(20)), (int) $i['CSvBits'], 0, STR_PAD_LEFT));
$stat = $id . $val;
$stats .= $stat;
}
$gf = strposX($this->data, 'gf', 1) + 2; // find gf and skip it
$if = strposX($this->data, 'if', 1);
$len = $if - $gf;
$statall = $stats . "11111111100";
$bytes = $this->ByteReader->toBytes($statall);
// refresh this 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);
$this->data = $data;
$this->save();
$filedata = file_get_contents($this->filePath);
$this->ByteReader = new D2ByteReader($filedata);
$this->data = $this->ByteReader->getData();
}
public function setStat(string $stat, mixed $val) {
$gf = strposX($this->data, 'gf', 1) + 2; // find gf and skip it
$if = strposX($this->data, 'if', 1);
$len = $if - $gf;
$stats = new D2BitReader($this->ByteReader->toBits($this->ByteReader->readh($gf, $len)));
$bits = $stats->getBits();
$cleanbits = substr($bits, 0, -11);
$stats->setBits($cleanbits);
$bits = $stats->getBits();
$stats->rewind();
$_offsets = [];
for ($i = 0; $i <= strlen($bits); $i++) {
$id = hexdec($this->ByteReader->toBytesR($stats->readb(9)));
$_offsets[$id] = $stats->getOffset();
$stats->skip($this->ISC[$id]['CSvBits']);
}
$_offsets[0] = 9;
$offsets = null;
foreach ($_offsets as $k => $v) {
$_stat = $this->ISC[$k]['Stat'];
$_stats[$_stat] = $this->ISC[$k]['Stat'];
$csvbits[$_stat] = $this->ISC[$k]['CSvBits'];
$maxValues[$_stat] = pow(2, $this->ISC[$k]['CSvBits']) - 1;
$offsets[$_stat] = $v;
}
if ($stat == 'hitpoints' || $stat == 'maxhp' || $stat == 'mana' || $stat == 'maxmana' || $stat == 'stamina' || $stat == 'maxstamina') {
$val = (int) ($val << 8);
}
// << 8 multiplication, if the value is larger than what the stat can hold, set it to the max.
if ($val > $maxValues[$stat]) {
$val = $maxValues[$stat];
}
$bitsToWrite = strrev(str_pad(decbin(intval($val)), $csvbits[$_stats[$stat]], 0, STR_PAD_LEFT));
$statOffset = $offsets[$_stats[$stat]];
$newBits = $stats->writeBits($bits, $bitsToWrite, $statOffset) . "11111111100"; // 0x1FF padding
$stats->setBits($newBits);
$bytes = $this->ByteReader->toBytes($newBits);
$this->ByteReader->writeBytes($gf, $bytes);
$this->data = $this->ByteReader->getData();
$this->save();
}
public function getQuestData($file) {
$questsNorm = null;
$questsNM = null;