Checksum code fixed. Added DocBlocks to D2Classes. TODO: Fill in docblocks, refactor, code cleanup

This commit is contained in:
Hash Borgir 2022-07-06 17:41:24 -06:00
parent f3b24de2a8
commit eb28039f88
28 changed files with 1285 additions and 340 deletions

8
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

13
.idea/D2Modder.iml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/vendor/composer" />
<excludeFolder url="file://$MODULE_DIR$/vendor/formr/formr" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/D2Modder.iml" filepath="$PROJECT_DIR$/.idea/D2Modder.iml" />
</modules>
</component>
</project>

12
.idea/php.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/formr/formr" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="7.4">
<option name="suggestChangeDefaultLanguageLevel" value="false" />
</component>
</project>

6
.idea/vcs.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -42,23 +42,39 @@ foreach ($ISCData as $k => $v) {
//$filePath = "D:\Diablo II\MODS\MedianXL2012\save\Test.d2s";
$filePath = "Test.d2s";
$filePath = "Necro.d2s";
$char = new D2Char($filePath);
//$char->setAllSkills(56);
$char->setChar("CharacterStatus", "Died", 0);
$char->setChar("CharacterStatus", "Hardcore", 1);
//$char->setChar("CharacterStatus", "Expansion", 1);
//$char->setChar("LeftmousebuttonskillID", 223);
$char->setAllSkills(1);
//$char->setSkill(1, 99);
//$char->setChar("CharacterClass", 1); // 127
$char->setChar("CharacterLevel", 0);
$char->setStat("strength", 30);
$char->setStat("energy", 30);
$char->setStat("dexterity", 30);
$char->setStat("vitality", 30);
$char->setStat("mana", 120);
$char->setStat("maxmana", 200);
$char->setStat("stamina", 80);
$char->setStat("maxstamina", 120);
//$char->setChar("CharacterClass", "Necromancer"); // 127
//$char->setChar("CharacterProgression", 1); // 0 in normal, 1 finished normal, 2 finished nm, 3 finished hell
$char->setChar("CharacterLevel", 25);
//$char->setStat("strength", 70);
//$char->setStat("energy", 70);
//$char->setStat("dexterity", 70);
//$char->setStat("vitality", 70);
//$char->setStat("mana", 200);
//$char->setStat("maxmana", 200);
//$char->setStat("soulcounter", 80);
//
//$char->setStat("hitpoints", 70);
//$char->setStat("maxhp", 70);
//$char->setStat("stamina", 70);
//$char->setStat("maxstamina", 200);
//
//
//$char->setStat("gold", 0);
//$char->setStat("soulcounter", 40273409479012734098712903479012374091827349081273490172093478);
unset($char); // destroy $char so we can read it again after writing to it to get updated stats
$char = new D2Char($filePath);
var_dump($char->cData['stats']);
var_dump($char->cData);

BIN
bin/d2cr.exe Normal file

Binary file not shown.

Binary file not shown.

BIN
img/bg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

11
ironman-dev.sqbpro Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,15 +1,31 @@
<?php
/**
*
*/
class D2BitReader {
/**
* @var string
*/
private string $bits = '';
/**
* @var int
*/
private int $offset = 0;
/**
* @param string $bits
*/
public function __construct(string $bits = '') {
$this->bits = $bits;
return true;
}
/**
* @param int $numBits
* @return int
*/
public function skip(int $numBits): int {
$this->offset += $numBits;
return $this->offset;
@ -17,6 +33,11 @@ class D2BitReader {
/* read X number of bits, like fread */
/**
* @param int $numBits
* @param bool $str
* @return string
*/
public function read(int $numBits = 0, bool $str = true): string {
$bits = null;
for ($i = $this->offset; $i < $this->offset + $numBits; $i++) {
@ -26,10 +47,21 @@ class D2BitReader {
return $bits;
}
/**
* @param string $bits
* @param string $bitsToWrite
* @param int $offset
* @return array|string|string[]
*/
public function writeBits(string $bits, string $bitsToWrite, int $offset) {
return substr_replace($bits, $bitsToWrite, $offset, strlen($bitsToWrite));
}
/**
* @param int $numBits
* @param bool $str
* @return string
*/
public function readb(int $numBits = 0, bool $str = true): string {
$bits = null;
for ($i = $this->offset; $i < $this->offset + $numBits; $i++) {
@ -39,6 +71,10 @@ class D2BitReader {
return strrev(str_pad($bits, 16, 0, STR_PAD_RIGHT));
}
/**
* @param int $numBits
* @return string
*/
public function readr(int $numBits = 0): string {
$bits = null;
for ($i = $this->offset; $i < $this->offset + $numBits; $i++) {
@ -50,6 +86,10 @@ class D2BitReader {
/* seek to offset (like fseek) */
/**
* @param int $pos
* @return bool
*/
public function seek(int $pos): bool {
if ($pos < 0 || $pos > strlen($this->bits)) {
return false;
@ -60,6 +100,9 @@ class D2BitReader {
/* rewind offset to 0 */
/**
* @return bool
*/
public function rewind(): bool {
$this->offset = 0;
return true;
@ -67,6 +110,10 @@ class D2BitReader {
/* Get Bit */
/**
* @param string $bitNum
* @return int
*/
public function getBit(string $bitNum): int {
if ($bitNum < 0 || $bitNum > strlen($this->bits)) {
return false;
@ -76,6 +123,11 @@ class D2BitReader {
/* Set Bit */
/**
* @param int $bitNum
* @param int $bitVal
* @return bool
*/
public function setBit(int $bitNum, int $bitVal): bool {
if ($bitVal < 0 || $bitVal > 1) {
return false;
@ -84,18 +136,32 @@ class D2BitReader {
return true;
}
/**
* @return string
*/
public function getBits(): string {
return $this->bits;
}
/**
* @param string $bits
* @return void
*/
public function setBits(string $bits) {
$this->bits = $bits;
}
/**
* @return int
*/
public function getOffset(): int {
return $this->offset;
}
/**
* @param int $offset
* @return bool
*/
public function setOffset(int $offset): bool {
if ($offset < 0 || $offset > strlen($this->bits))
return false;

View File

@ -3,17 +3,33 @@
require_once './src/D2Functions.php';
require_once './src/D2BitReader.php';
/**
*
*/
class D2ByteReader {
/**
* @var string
*/
private string $data = '';
/**
* @var int
*/
private int $offset = 0;
/**
* @param string $data
*/
public function __construct(string $data) {
if (!$data)
return false;
$this->data = $data;
}
/**
* @param int $numBytes
* @return bool
*/
public function skip(int $numBytes): bool {
if ($numBytes < 0 || $numBytes > strlen($this->data))
return false;
@ -21,6 +37,10 @@ class D2ByteReader {
return $true;
}
/**
* @param int $pos
* @return bool
*/
public function seek(int $pos): bool {
if ($pos < 0 || $pos > strlen($this->data))
return false;
@ -28,6 +48,12 @@ class D2ByteReader {
return true;
}
/**
* @param int $offset
* @param int $numBytes
* @param bool $str
* @return string
*/
public function readh(int $offset, int $numBytes, bool $str = true): string {
$this->seek($offset);
$bytes = null;
@ -37,6 +63,12 @@ class D2ByteReader {
return unpack('H*', $bytes)[1];
}
/**
* @param int $offset
* @param int $numBytes
* @param bool $str
* @return array
*/
public function readc(int $offset, int $numBytes, bool $str = true): array {
$this->seek($offset);
$bytes = null;
@ -46,15 +78,28 @@ class D2ByteReader {
return unpack('C*', $bytes);
}
/**
* @return bool
*/
public function rewind(): bool {
$this->offset = 0;
return true;
}
/**
* @param int $offset
* @param int $byte
* @return void
*/
public function writeByte(int $offset, int $byte) {
$this->data[$offset] = pack('C', $byte);
}
/**
* @param int $offset
* @param string $bytes
* @return false|void
*/
public function writeBytes(int $offset, string $bytes) {
if ($offset < 0 || $offset > strlen($this->data) || $bytes == '')
return false;
@ -64,26 +109,46 @@ class D2ByteReader {
$this->data[$pos] = pack('H*', $byte);
}
}
/**
* @param int $offset
* @param string $bytes
* @return false|void
*/
public function insertBytes(int $offset, string $bytes) {
if ($offset < 0 || $offset > strlen($this->data) || $bytes == '')
return false;
$data = bin2hex($this->data); // convert to hex bytes string
$newData = substr_replace($data, $bytes, $offset, 0);
$this->data = hex2bin($newData);
}
}
/**
* @return false|string
*/
public function getData() {
return $this->data ? $this->data : false;
}
/**
* @param $data
* @return void
*/
public function setData($data){
$this->data = $data;
}
/**
* @return int
*/
public function getOffset(): int {
return $this->offset;
}
/**
* @param string $str
* @return bool
*/
public function isHexString(string $str): bool {
if (strlen($str) % 2 == 0 && (ctype_xdigit($str))) {
return true;
@ -91,6 +156,10 @@ class D2ByteReader {
return false;
}
/**
* @param $input
* @return string
*/
public function toBits($input): string {
$output = '';
if ($this->isHexString($input)) {
@ -113,13 +182,21 @@ class D2ByteReader {
}
}
/**
* @param string $bits
* @return string
*/
public function toBytesR(string $bits) : string {
foreach (str_split($bits, 8) as $byteString) {
$bytes .= strtoupper(str_pad(dechex(bindec(($byteString))), 2, 0, STR_PAD_LEFT));
}
return $bytes;
}
/**
* @param string $bits
* @return string
*/
public function toBytes(string $bits) : string {
foreach (str_split($bits, 8) as $byteString) {
$bytes .= strtoupper(str_pad(dechex(bindec(strrev($byteString))), 2, 0, STR_PAD_LEFT));
@ -127,6 +204,10 @@ class D2ByteReader {
return $bytes;
}
/**
* @param string $bits
* @return string
*/
public function bitsToHexString(string $bits): string {
$bytes = '';
foreach (str_split($bits, 8) as $byte) {
@ -135,6 +216,10 @@ class D2ByteReader {
return $bytes;
}
/**
* @param string $bits
* @return array
*/
public function bitsToHexArray(string $bits): array {
$bytes = [];
foreach (str_split($bits, 8) as $byte) {
@ -143,6 +228,10 @@ class D2ByteReader {
return $bytes;
}
/**
* @param string $bits
* @return array
*/
public function bitsToIntArray(string $bits): array {
$bytes = [];
foreach (str_split($bits, 8) as $byte) {
@ -151,18 +240,21 @@ class D2ByteReader {
return $bytes;
}
/*
@return Byte with Nth bit set to X
/**
* @param int $byte
* @param int $pos
* @param bool $bit
* @return int
*/
public function setBit(int $byte, int $pos, bool $bit) {
return ($bit ? ($byte | (1 << $pos)) : ($byte & ~(1 << $pos)) );
}
/*
@return Bit at Nth position in Byte
/**
* @param int $byte
* @param int $pos
* @return int
*/
public function getBit(int $byte, int $pos): int {
return intval(($byte & (1 << $pos)) != 0);
}

View File

@ -8,24 +8,70 @@ require_once 'D2Item.php';
require_once 'D2ByteReader.php';
require_once 'D2Functions.php';
/**
*
*/
class D2Char {
/**
* @var null
*/
public $cData = null; // char data output
/**
* @var null
*/
public $items = null; // char item data
/**
* @var D2ByteReader|null
*/
public $ByteReader = null; // put $data into bytereader
/**
* @var string|null
*/
public $filePath = null; // .d2s file path
/**
* @var D2CharStructureData
*/
private $sData = null; // char file structure data
/**
* @var null
*/
private $bData = null; // char binary data from d2s
/**
* @var false|resource
*/
private $fp = null; // file pointer
/**
* @var false|string
*/
private $data = null; // full d2s file loaded in $data
/**
* @var null
*/
private $ISC = null;
/**
* @var null
*/
private $skillData = null;
/**
* @return void
*/
public function save() {
file_put_contents($this->filePath, $this->data);
checksumFix($this->filePath);
$this->ByteReader->setData($this->data); // update bytereader data
$this->ByteReader->writeBytes(12, "00000000"); // clear old checksum
$this->data = $this->ByteReader->getData(); // update this data to what we get from bytereader after clearing checksum
$checksum = checksum(unpack('C*', $this->data)); // get new checksum
$this->ByteReader->setData($this->data); // update bytereader data
$this->ByteReader->writeBytes(12, $checksum); // write new checksum
$this->data = $this->ByteReader->getData(); // update this data
file_put_contents($this->filePath, $this->data); // write file
}
/**
* @param $file
*/
public function __construct($file) {
$this->sData = new D2CharStructureData();
$this->filePath = $_SESSION['savepath'] . $file;
@ -52,7 +98,7 @@ class D2Char {
$classes = array_flip(['ama' => 0, 'sor' => 1, 'nec' => 2, 'pal' => 3, 'bar' => 4, 'dru' => 5, 'ass' => 6]);
$class = $classes[hexdec($this->ByteReader->readh(40, 1))];
$sql = "SELECT sk.id,sk.Skill,sk.skilldesc,`str name`,strings.`String`,skilldesc.SkillPage,skilldesc.SkillRow,skilldesc.SkillColumn,skilldesc.ListRow,skilldesc.ListPool,skilldesc.IconCel
FROM skills as sk
FROM skills as sk
LEFT JOIN skilldesc ON sk.skilldesc = skilldesc.skilldesc
LEFT JOIN strings on skilldesc.`str name` = strings.Key
WHERE sk.charclass = '$class'";
@ -64,6 +110,9 @@ WHERE sk.charclass = '$class'";
return $this->parseChar(); // end of parseChar() calls parseItems(), parseStats, etc.
}
/**
* @return void
*/
public function parseItems() {
$i_TotalOffset = strpos($this->data, "JM");
fseek($this->fp, $i_TotalOffset + 2);
@ -83,6 +132,9 @@ WHERE sk.charclass = '$class'";
}
}
/**
* @return array|null
*/
public function parseChar() {
$cData = null;
$cData['Identifier'] = bin2hex($this->bData[0]);
@ -90,30 +142,46 @@ WHERE sk.charclass = '$class'";
$cData['VersionID'] = ($this->sData->version[unpack('l', $this->bData[4])[1]]);
// 1.41 KB (1,447 bytes) - checks out
$cData['Filesize'] = round(unpack('l', $this->bData[8])[1] / 1024, 2) . " KB";
//$cData['Filesize'] = round(unpack('l', $this->bData[8])[1] / 1024, 2) . " KB";
$cData['Filesize'] = unpack('L', $this->bData[8])[1];
$cData['Checksum'] = bin2hex($this->bData[12]);
$cData['Activeweapon'] = unpack('l', $this->bData[16]);
$cData['Activeweapon'] = unpack('L', $this->bData[16]);
$cData['CharacterName'] = str_replace("\0", "", $this->bData[20]);
$cData['CharacterStatus'] = array_filter(str_split(strtobits($this->bData[36])));
foreach ($cData['CharacterStatus'] as $k => $v) {
$str .= ($characterStatus[$k]) . " ";
$characterStatus = array_filter(str_split(strrev(strtobits($this->bData[36]))));
foreach ($characterStatus as $k => $v) {
$str .= $this->sData->characterStatus[$k] . " ";
}
$cData['CharacterStatus'] = $str;
$cData['Characterprogression'] = bindec($this->bData[37]);
$cData['CharacterStatus'] = trim($str);
$progression = hexdec(bin2hex($this->bData[37]));
$cData['CharacterProgression'] = $this->sData->characterProgressionClassic[$progression];
if ($cData['CharacterStatus'] == 'Hardcore Expansion') {
$cData['CharacterProgression'] = $this->sData->characterProgressionExpHC[$progression];
}
if ($cData['CharacterStatus'] == "Expansion") {
$cData['CharacterProgression'] = $this->sData->characterProgressionExp[$progression];
}
if ($cData['CharacterStatus'] == "Hardcore") {
$cData['CharacterProgression'] = $this->sData->characterProgressionClassicHC[$progression];
}
$cData['CharacterClass'] = $this->sData->class[unpack('C', $this->bData[40])[1]];
$cData['CharacterLevel'] = unpack('C', $this->bData[43])[1];
$cData['Lastplayed'] = gmdate("Y-m-d\TH:i:s\Z", unpack('I', $this->bData[48])[0]);
$cData['Lastplayed'] = gmdate("Y-m-d\TH:i:s\Z", unpack('L', $this->bData[48])[0]);
$skills = (unpack('l16', $this->bData[56]));
// Hotkey assigned skills
foreach ($skills as $skill) {
$cData['Assignedskills'][] = ($this->sData->skills[$skill]);
$cData['Assignedskills'] = array_filter($cData['Assignedskills']);
}
$cData['LeftmousebuttonskillID'] = $this->sData->skills[unpack('i', $this->bData[120])[1]];
$cData['RightmousebuttonskillID'] = $this->sData->skills[unpack('i', $this->bData[124])[1]];
$cData['LeftswapmousebuttonskillID'] = $this->sData->skills[unpack('i', $this->bData[128])[1]];
$cData['RightswapmousebuttonskillID'] = $this->sData->skills[unpack('i', $this->bData[132])[1]];
$cData['LeftmousebuttonskillID'] = $this->sData->skills[unpack('L', $this->bData[120])[1]];
$cData['RightmousebuttonskillID'] = $this->sData->skills[unpack('L', $this->bData[124])[1]];
$cData['LeftswapmousebuttonskillID'] = $this->sData->skills[unpack('L', $this->bData[128])[1]];
$cData['RightswapmousebuttonskillID'] = $this->sData->skills[unpack('L', $this->bData[132])[1]];
// Char menu appearance not needed
// $cData['Charactermenuappearance'] = unpack('i', $this->bData[136]);
@ -128,7 +196,7 @@ WHERE sk.charclass = '$class'";
// found in the character.map file, according to the difficulty being played. Not needed
//$cData['MapID'] = $this->bData[171];
$cData['MercenaryDead'] = unpack('i', $this->bData[177])[1];
$cData['MercenaryDead'] = unpack('i-', $this->bData[177])[1];
// This looks like a random ID for your mercenary.
// $cData['MercenaryID'] = unpack('H*', $this->bData[179]);
$cData['MercenaryNameID'] = unpack('S', $this->bData[183])[1];
@ -149,13 +217,16 @@ WHERE sk.charclass = '$class'";
$this->cData['skills'] = $this->parseSkills();
unset($this->items);
unset($this->bData);
//unset($this->bData);
unset($this->sData);
unset($this->fp);
return $this->cData;
}
/**
* @return array
*/
public function parseSkills() {
$if = strposX($this->data, 'if', 1) + 2; // find if and skip it
$jm = strposX($this->data, 'JM', 1);
@ -177,6 +248,10 @@ WHERE sk.charclass = '$class'";
return $cData;
}
/**
* @param int $points
* @return void
*/
public function setAllSkills(int $points) {
$if = strposX($this->data, 'if', 1) + 2; // find if and skip it
$jm = strposX($this->data, 'JM', 1);
@ -188,6 +263,11 @@ WHERE sk.charclass = '$class'";
$this->save();
}
/**
* @param int $skill
* @param int $points
* @return void
*/
public function setSkill(int $skill, int $points) {
$skill -= 1;
$if = strposX($this->data, 'if', 1) + 2; // find if and skip it
@ -198,6 +278,9 @@ WHERE sk.charclass = '$class'";
$this->save();
}
/**
* @return void
*/
public function parseStats() {
$gf = strposX($this->data, 'gf', 1) + 2; // find gf and skip it
$if = strposX($this->data, 'if', 1);
@ -205,13 +288,13 @@ WHERE sk.charclass = '$class'";
$stats = new D2BitReader($this->ByteReader->toBits($this->ByteReader->readh($gf, $len)));
$bits = $stats->getBits();
$bytes = $this->ByteReader->toBytes($bits);
$bytes = $this->ByteReader->toBytes($bits);
$stats->rewind();
for ($i = 0; $i <= strlen($bits); $i++) {
$id = hexdec($this->ByteReader->toBytesR($stats->readb(9)));
if ($this->ISC[$id]['CSvBits'] !== NULL) {
if ($this->ISC[$id]['CSvBits'] !== NULL && $this->ISC[$id]['CSvBits'] !== '') {
$stats->skip($this->ISC[$id]['CSvBits']);
}
$ids[$id] = $id;
@ -219,11 +302,11 @@ WHERE sk.charclass = '$class'";
$stats->rewind();
foreach ($ids as $id) {
$stats->skip(9);
if ($this->ISC[$id]['CSvBits'] !== NULL) {
if ($this->ISC[$id]['CSvBits'] !== NULL && $this->ISC[$id]['CSvBits'] !== '') {
$val = $stats->readb($this->ISC[$id]['CSvBits']);
$stat = $this->ISC[$id]['Stat'];
$values[$stat] = hexdec($this->ByteReader->toBytesR($val));
}
$stat = $this->ISC[$id]['Stat'];
$values[$stat] = hexdec($this->ByteReader->toBytesR($val));
}
$values['hitpoints'] = (int) round($values['hitpoints'] >> 11);
$values['maxhp'] = (int) round($values['maxhp'] >> 11);
@ -231,12 +314,19 @@ WHERE sk.charclass = '$class'";
$values['maxmana'] = (int) round($values['maxmana'] >> 11);
$values['stamina'] = (int) round($values['stamina'] >> 11);
$values['maxstamina'] = (int) round($values['maxstamina'] >> 11);
$values['killcounter'] = (int) round($values['killcounter'] >> 1);
$values['soulcounter'] = (int) round($values['soulcounter'] / 2);
$values['killcounter'] = (int) round($values['killcounter'] / 2);
$this->cData['stats'] = $values;
}
public function setChar(string $stat, mixed $val) {
/**
* @param string $stat
* @param mixed $val
* @param mixed|null $val2
* @return false|void
*/
public function setChar(string $stat, mixed $val, mixed $val2 = null) {
switch ($stat) {
case 'CharacterName':
if (strlen($val) < 1 || strlen($val) > 15) {
@ -244,15 +334,20 @@ WHERE sk.charclass = '$class'";
}
$pack = $this->ByteReader->bitsToHexString($this->ByteReader->toBits(pack('Z16', $val)));
$this->ByteReader->writeBytes(20, $pack);
$this->data = $this->ByteReader->getData();
$this->save();
rename($this->filePath, $_SESSION['savepath'] . "$val.d2s");
break;
case "CharacterClass":
$this->ByteReader->writeByte(40, $val);
$this->data = $this->ByteReader->getData();
$this->save();
$classes = [
'Amazon' => 0,
'Sorceress' => 1,
'Necromancer' => 2,
'Paladin' => 3,
'Barbarian' => 4,
'Druid' => 5,
'Assassin' => 6
];
$this->ByteReader->writeByte(40, $classes[$val]);
break;
case "CharacterLevel":
if ($val > 99) {
@ -271,26 +366,111 @@ WHERE sk.charclass = '$class'";
$res = PDO_FetchOne($sql);
$this->setStat('experience', $res);
break;
case 'CharacterStatus':
switch ($val) {
case 'Died':
$status = (strtobits($this->data[36]));
$status[3] = $val2;
$byte = $this->ByteReader->bitsToHexString($status);
$this->ByteReader->writeByte(36, hexdec($byte));
break;
case 'Hardcore':
$status = (strtobits($this->data[36]));
$status[2] = $val2;
$byte = $this->ByteReader->bitsToHexString($status);
$this->ByteReader->writeByte(36, hexdec($byte));
break;
case 'Expansion':
$status = strrev(strtobits($this->data[36]));
$status[5] = $val2;
$byte = $this->ByteReader->bitsToHexString($status);
$this->ByteReader->writeByte(36, hexdec($byte));
break;
}
break;
case 'CharacterProgression': // 0 in normal, 1 finished normal, 2 finished nm, 3 finished hell
switch ($val) {
case 0: // in normal
$this->data[37] = pack('C', 3);
break;
case 1: // finished normal
$this->data[37] = pack('C', 8);
break;
case 2: // finished nm
$this->data[37] = pack('C', 13);
break;
case 3: // finished hell
$this->data[37] = pack('C', 15);
break;
$this->save();
}
break;
case 'LeftmousebuttonskillID':
$this->ByteReader->writeBytes(120, dechex($val));
break;
case 'RightmousebuttonskillID':
$this->ByteReader->writeBytes(124, dechex($val));
break;
case 'LeftswapmousebuttonskillID':
$this->ByteReader->writeBytes(128, dechex($val));
break;
case 'RightswapmousebuttonskillID':
$this->ByteReader->writeBytes(132, dechex($val));
break;
}
// finally save char data to d2s file
$this->data = $this->ByteReader->getData();
$this->save();
}
/**
* @return void
*/
public function resetFileSize() {
$filesize = strlen($this->data);
$this->fp = fopen($this->filePath, "r+b");
fseek($this->fp, 8);
fwrite($this->fp, pack('L', $filesize));
fclose($this->fp);
$this->fp = fopen($this->filePath, "r+b");
checksumFix($this->filePath);
fseek($this->fp, 8);
//ddump(unpack('L', fread($this->fp, 4))[1]);
}
/**
* @return false|string
*/
public function generateAllStats() {
// 003C08E081000F067860C001071C0008020800F040020064A000000A2C00C0030C000D000000006E05000000FE3F
$stats = '';
foreach ($this->ISC as $i) {
$id = strrev(str_pad((decbin($i['ID'])), 9, 0, STR_PAD_LEFT));
$val = strrev(str_pad((decbin(20)), (int) $i['CSvBits'], 0, STR_PAD_LEFT));
for ($i = 0; $i < 16; $i++) {
$id = strrev(str_pad((decbin((int) $this->ISC[$i]['ID'])), 9, 0, STR_PAD_LEFT));
$val = strrev(str_pad((decbin(20)), (int) $this->ISC[$i]['CSvBits'], 0, STR_PAD_LEFT));
// dump($id);
// dump($val);
$stat = $id . $val;
$stats .= $stat;
}
$stats .= "000011111111100";
dump($stats);
$gf = strposX($this->data, 'gf', 1) + 2; // find gf and skip it
$if = strposX($this->data, 'if', 1);
$len = $if - $gf;
$statall = $stats . "11111111100";
$bytes = $this->ByteReader->toBytes($statall);
$bytes = $this->ByteReader->toBytes($stats);
// refresh this data
$data = $this->ByteReader->getData();
// delete everything between GF---and---IF
@ -303,19 +483,27 @@ WHERE sk.charclass = '$class'";
$this->data = $data;
$this->save();
$this->resetFileSize();
$filedata = file_get_contents($this->filePath);
$this->ByteReader = new D2ByteReader($filedata);
$this->data = $this->ByteReader->getData();
return $this->data;
}
/**
* @param string $stat
* @param mixed $val
* @return void
*/
public function setStat(string $stat, mixed $val) {
$gf = strposX($this->data, 'gf', 1) + 2; // find gf and skip it
$if = strposX($this->data, 'if', 1);
$len = $if - $gf;
$stats = new D2BitReader($this->ByteReader->toBits($this->ByteReader->readh($gf, $len)));
$bits = $stats->getBits();
$bits = $stats->getBits();
$cleanbits = substr($bits, 0, -11);
$stats->setBits($cleanbits);
$bits = $stats->getBits();
@ -324,7 +512,9 @@ WHERE sk.charclass = '$class'";
for ($i = 0; $i <= strlen($bits); $i++) {
$id = hexdec($this->ByteReader->toBytesR($stats->readb(9)));
$_offsets[$id] = $stats->getOffset();
$stats->skip($this->ISC[$id]['CSvBits']);
if ($this->ISC[$id]['CSvBits'] !== null && $this->ISC[$id]['CSvBits'] !== '') {
$stats->skip($this->ISC[$id]['CSvBits']);
}
}
$_offsets[0] = 9;
$offsets = null;
@ -332,7 +522,9 @@ WHERE sk.charclass = '$class'";
$_stat = $this->ISC[$k]['Stat'];
$_stats[$_stat] = $this->ISC[$k]['Stat'];
$csvbits[$_stat] = $this->ISC[$k]['CSvBits'];
$maxValues[$_stat] = pow(2, $this->ISC[$k]['CSvBits']) - 1;
if ($this->ISC[$k]['CSvBits'] !== null && $this->ISC[$k]['CSvBits'] !== '') {
$maxValues[$_stat] = pow(2, $this->ISC[$k]['CSvBits']) - 1;
}
$offsets[$_stat] = $v;
}
if ($stat == 'hitpoints' || $stat == 'maxhp' || $stat == 'mana' || $stat == 'maxmana' || $stat == 'stamina' || $stat == 'maxstamina') {
@ -344,14 +536,22 @@ WHERE sk.charclass = '$class'";
}
$bitsToWrite = strrev(str_pad(decbin(intval($val)), $csvbits[$_stats[$stat]], 0, STR_PAD_LEFT));
$statOffset = $offsets[$_stats[$stat]];
$newBits = $stats->writeBits($bits, $bitsToWrite, $statOffset) . "11111111100"; // 0x1FF padding
$stats->setBits($newBits);
$bytes = $this->ByteReader->toBytes($newBits);
$this->ByteReader->writeBytes($gf, $bytes);
$this->data = $this->ByteReader->getData();
$this->save();
if (!array_key_exists($stat, $_stats)) {
$this->ByteReader->toBits($this->generateAllStats());
} else {
$newBits = $stats->writeBits($bits, $bitsToWrite, $statOffset) . "11111111100"; // 0x1FF padding
$stats->setBits($newBits);
$bytes = $this->ByteReader->toBytes($newBits);
$this->ByteReader->writeBytes($gf, $bytes);
$this->data = $this->ByteReader->getData();
$this->save();
}
}
/**
* @param $file
* @return array
*/
public function getQuestData($file) {
$questsNorm = null;
$questsNM = null;
@ -390,6 +590,10 @@ WHERE sk.charclass = '$class'";
return $quests;
}
/**
* @param $file
* @return array
*/
public function getWaypointsData($file) {
$wp = null;
fseek($this->fp, $this->sData->wpOffsetsNorm);

View File

@ -2,84 +2,306 @@
require_once 'D2BitReader.php';
/**
*
*/
class D2CharItem {
/**
* @var string
*/
private string $bits;
/**
* @var string
*/
public $basename = ''; //name of the base item
/**
* @var string
*/
public $item_name = ''; //name string
/**
* @var string
*/
public $item_rank = ''; //normal/exceptional/elite
/**
* @var string
*/
public $item_type = ''; //basic type: armor/weapon/misc
//flags
/**
* @var int
*/
public $identified = 0;
/**
* @var int
*/
public $sockets = 0;
/**
* @var int
*/
public $ear = 0;
/**
* @var int
*/
public $starter = 0;
/**
* @var int
*/
public $compact = 0;
/**
* @var
*/
public $ethereal;
/**
* @var
*/
public $personalized;
/**
* @var
*/
public $runeword;
/**
* @var
*/
public $version;
/**
* @var string
*/
public $runeword_name = '';
/**
* @var string
*/
public $runes_title = '';
//placement
/**
* @var int
*/
public $location = 0;
/**
* @var int
*/
public $body = 0;
/**
* @var int
*/
public $col = 0;
/**
* @var int
*/
public $row = 0;
/**
* @var int
*/
public $container = 0;
/**
* @var
*/
public $parent;
/**
* @var
*/
public $storage;
/**
* @var
*/
public $bodypart;
/**
* @var int
*/
public $invH = 0;
/**
* @var int
*/
public $invW = 0;
/**
* @var int
*/
public $beltrows = 1;
//features
/**
* @var string
*/
public $item_code = ''; //3 letter code from txt
/**
* @var int
*/
public $SocketsFilled = 0;
/**
* @var int
*/
public $SocketsNum = 0;
/**
* @var bool
*/
public $socketable = false; //gem, rune, jewel
/**
* @var string
*/
public $fingerprint = '';
/**
* @var int
*/
public $itemlvl = 0; //item level
/**
* @var int
*/
public $quality = 0;
/**
* @var bool
*/
public $isCharm = false;
/**
* @var bool
*/
public $isJewel = false;
/**
* @var string
*/
public $magic_rank = 'Normal'; //normal/magic/rare/crafted/set/unique
/**
* @var int
*/
public $set_id = 0; //set item id from txt
/**
* @var string
*/
public $set_item = '';
/**
* @var int
*/
public $set_name = 0; //set name, if it is set item,
/**
* @var string
*/
public $personname = '';
/**
* @var int
*/
public $questdif = -1;
/**
* @var
*/
public $gold;
/**
* @var string
*/
public $GUID = '';
/**
* @var int
*/
public $defense = 0;
/**
* @var int
*/
public $mindam = 0;
/**
* @var int
*/
public $maxdam = 0;
/**
* @var int
*/
public $mindam2 = 0;
/**
* @var int
*/
public $maxdam2 = 0;
/**
* @var int
*/
public $mindammi = 0;
/**
* @var int
*/
public $maxdammi = 0;
/**
* @var int
*/
public $MaxDur = 0;
/**
* @var int
*/
public $CurDur = 0;
/**
* @var int
*/
public $reqlvl = 0;
/**
* @var int
*/
public $reqstr = 0;
/**
* @var int
*/
public $reqdex = 0;
/**
* @var int
*/
public $speed = 0;
/**
* @var int
*/
public $throwing = 0;
/**
* @var int
*/
public $stackable = 0;
/**
* @var string
*/
public $charName = ''; //ear's name
/**
* @var
*/
public $gfx; //graphic file name
/**
* @var int
*/
public $baseTrans = -1; //transform indexes for colour remap base item
/**
* @var int
*/
public $magicTrans = -1; //transform indexes for colour remap magic item
/**
* @var
*/
public $type; //type column from txt
/**
* @var string
*/
public $spelldesc = ''; //desc for potions
/**
* @var
*/
public $ditem; //link to item properties from txt
//mods
/**
* @var int
*/
public $dammult = 100; //damage multiply
/**
* @var int
*/
public $damminadd = 0; //damage add
/**
* @var int
*/
public $dammaxadd = 0; //damage add
/**
* @var int
*/
public $defmult = 100; //defense multiply
/**
* @var int
*/
public $defadd = 0; //defense multiply
/**
* @var int[]
*/
public $resist = array(0, 0, 0, 0, 0, 0); //phy, mag, fire, light, cold, poison
/**
* @var int[]
*/
public $attributes = array(0, 0, 0, 0); //str, dex, vit, ene
//arrays
@ -87,18 +309,33 @@ class D2CharItem {
//socketed items, collected in above function,
//because item has only data for itselt, and
//gems/runes/jewels are standalone
public $SocketItems = array();
/**
* @var array
*/
public $SocketItems = array();
/**
* @var array
*/
public $properties = array(); //item variable properties
/**
* @var array
*/
public $propids = array(); //properties ids list
/**
* @param string $bits
*/
public function __construct(string $bits){
if ($bits == '') return false;
$this->bits = $bits;
return $this->parseItem();
}
/**
* @return void
*/
public function parseItem(){
}

View File

@ -7,12 +7,24 @@ ini_set('max_execution_time', '0');
session_start();
ob_start();
/**
*
*/
define('DB_FILE', $_SESSION['modname'] . ".db");
PDO_Connect("sqlite:" . DB_FILE);
/**
*
*/
class D2CharStructureData {
/**
* @var
*/
public $skills;
/**
* @var string[]
*/
public $class = [
0 => 'Amazon',
1 => 'Sorceress',
@ -22,6 +34,9 @@ class D2CharStructureData {
5 => 'Druid',
6 => 'Assassin'
];
/**
* @var string[]
*/
public $characterStatus = [
0 => '',
1 => '',
@ -32,6 +47,93 @@ class D2CharStructureData {
6 => 'Ladder',
7 => ''
];
/**
* @var string[]
*/
public $characterProgressionClassicHC = [
0 => '',
1 => '',
2 => '',
3 => '',
4 => 'Count / Countess',
5 => 'Count / Countess',
6 => 'Count / Countess',
7 => 'Count / Countess',
8 => 'Duke / Duchess',
9 => 'Duke / Duchess',
10 => 'Duke / Duchess',
11 => 'Duke / Duchess',
12 => 'Duke / Duchess',
13 => '',
14 => '',
15 => '',
];
/**
* @var string[]
*/
public $characterProgressionClassic = [
0 => '',
1 => '',
2 => '',
3 => '',
4 => 'Sir / Dame',
5 => 'Sir / Dame',
6 => 'Sir / Dame',
7 => 'Sir / Dame',
8 => 'Lord / Lady',
9 => 'Lord / Lady',
10 => 'Lord / Lady',
11 => 'Lord / Lady',
12 => 'Baron / Baroness',
13 => '',
14 => '',
15 => '',
];
/**
* @var string[]
*/
public $characterProgressionExp = [
0 => '',
1 => '',
2 => '',
3 => '',
4 => '',
5 => 'Slayer',
6 => 'Slayer',
7 => 'Slayer',
8 => 'Slayer',
9 => '',
10 => 'Champion',
11 => 'Champion',
12 => 'Champion',
13 => 'Champion',
14 => 'Patriarch / Matriarch ',
15 => 'Patriarch / Matriarch ',
];
/**
* @var string[]
*/
public $characterProgressionExpHC = [
0 => '',
1 => '',
2 => '',
3 => '',
4 => '',
5 => 'Destroyer',
6 => 'Destroyer',
7 => 'Destroyer',
8 => 'Destroyer',
9 => '',
10 => 'Conqueror',
11 => 'Conqueror',
12 => 'Conqueror',
13 => 'Conqueror',
14 => 'Guardian',
15 => 'Guardian',
];
/**
* @var int[]
*/
public $offsets = [
0 => 4, // Identifier
4 => 4, // Version ID
@ -67,6 +169,9 @@ class D2CharStructureData {
633 => 81, // Waypoints
714 => 51, // NPC Introductions
];
/**
* @var string[]
*/
public $qNorm = [
345 => 'introWarriv',
347 => 'Den_Of_Evil',
@ -95,14 +200,14 @@ class D2CharStructureData {
393 => 'introToAct4',
395 => 'The_Fallen_Angel',
397 => 'Terrors_End',
399 => 'Hell_Forge',
399 => 'Hell_Forge',
401 => 'traveledToAct5',
//403 => 'empty31',
//405 => 'empty32',
//407 => 'empty33',
409 => 'completedTerrorsEnd',
//411 => 'empty21',
//413 => 'empty22',
//413 => 'empty22',
415 => 'Siege_On_Harrogath',
417 => 'Rescue_On_MountArreat',
419 => 'Prison_Of_Ice',
@ -110,8 +215,10 @@ class D2CharStructureData {
423 => 'Rite_Of_Passage',
425 => 'Eve_Of_Destruction',
// read 425, pointer at 427, + 14 = 441 qNM offset
];
/**
* @var string[]
*/
public $qNM = [
441 => 'introWarrivNM',
443 => 'Den_Of_Evil_NM',
@ -147,7 +254,7 @@ class D2CharStructureData {
//503 => 'empty33',
505 => 'completedTerrorsEnd',
//507 => 'empty21',
//509 => 'empty22',
//509 => 'empty22',
511 => 'Siege_On_Harrogath',
513 => 'Rescue_On_MountArreat',
515 => 'Prison_Of_Ice',
@ -155,9 +262,10 @@ class D2CharStructureData {
519 => 'Rite_Of_Passage',
521 => 'Eve_Of_Destruction',
// read 521, pointer at 523, + 14 = 537 qHell offset
];
/**
* @var string[]
*/
public $qHell = [
537 => 'introWarriv',
539 => 'Den_Of_Evil_Hell',
@ -193,15 +301,17 @@ class D2CharStructureData {
//599 => 'empty33',
601 => 'completedTerrorsEnd',
//603 => 'empty21',
//605 => 'empty22',
//605 => 'empty22',
607 => 'Siege_On_Harrogath',
609 => 'Rescue_On_MountArreat',
611 => 'Prison_Of_Ice',
613 => 'Betrayal_Of_Harrogath',
615=> 'Rite_Of_Passage',
615 => 'Rite_Of_Passage',
617 => 'Eve_Of_Destruction',
];
/**
* @var string[]
*/
public $version = [
71 => "1.00 through v1.06",
87 => "1.07 or Expansion Set v1.08",
@ -209,12 +319,21 @@ class D2CharStructureData {
92 => "v1.09 (both the standard game and the Expansion Set.)",
96 => "v1.10+"
];
/**
* @var int
*/
public $wpOffsetsNorm = 643;
/**
* @var int
*/
public $wpOffsetsNM = 667;
/**
* @var int
*/
public $wpOffsetsHell = 691;
/**
* @var string[]
*/
public $wpNames = [
0 => 'Act 1 - Rogue_Encampment',
1 => 'Act 1 - Cold_Plains',
@ -255,18 +374,23 @@ class D2CharStructureData {
36 => 'Act 5 - Frozen_Tundra',
37 => "Act 5 - The_Ancients_Way",
38 => 'Act 5 - Worldstone_Keep_level_2'
];
];
/**
* @var int[]|string[]
*/
public $_qNorm;
/**
* @var int[]|string[]
*/
public $_qNM;
/**
* @var int[]|string[]
*/
public $_qHell;
/*
Initialize Skills From Skills.txt
*/
/**
*
*/
public function __construct() {
$sql = "
SELECT
@ -285,11 +409,11 @@ class D2CharStructureData {
foreach ($res as $r) {
$this->skills[$r['Id']] = $r['String'];
}
$this->_qNorm = array_flip($this->qNorm);
$this->_qNM = array_flip($this->qNM);
$this->_qHell = array_flip($this->qHell);
$this->_qHell = array_flip($this->qHell);
}
}

View File

@ -42,13 +42,24 @@
*/
/**
*
*/
class D2Database {
public function __construct() {
/**
*
*/
public function __construct() {
PDO_Connect("sqlite:" . DB_FILE);
}
public function createTables($file, $data) {
/**
* @param $file
* @param $data
* @return void
*/
public function createTables($file, $data) {
$tableName = basename($file);
$tableName = strtolower(substr($tableName, 0, -4));
$sql = '';
@ -68,7 +79,12 @@ class D2Database {
}
}
public function fillsTables($file, $data) {
/**
* @param $file
* @param $data
* @return void
*/
public function fillsTables($file, $data) {
$tableName = basename($file);
$tableName = strtolower(substr($tableName, 0, -4));
@ -106,7 +122,11 @@ class D2Database {
}
}
public function writeTbl($data) {
/**
* @param $data
* @return void
*/
public function writeTbl($data) {
$sql = 'CREATE TABLE IF NOT EXISTS `strings` (`Key` VARCHAR(255), `String` VARCHAR(255));';
$res = PDO_Execute($sql);
@ -120,7 +140,11 @@ class D2Database {
$res = PDO_Execute($sql);
}
public function getString($key) {
/**
* @param $key
* @return mixed
*/
public function getString($key) {
$sql = "SELECT String FROM `strings` WHERE `Key`='$key'";
$res = PDO_FetchRow($sql);

View File

@ -42,8 +42,14 @@
*/
/**
*
*/
class D2DocGenerator {
/**
*
*/
public function __construct() {
require_once './config.php';
require_once './_pdo.php';
@ -61,6 +67,9 @@ class D2DocGenerator {
$idata = new D2ItemData();
}
/**
* @return array
*/
public function getIscProps() {
$sql = "
SELECT p.`code` as prop,
@ -103,6 +112,9 @@ class D2DocGenerator {
return $isc;
}
/**
* @return array
*/
public function getStrings() {
// load strings
$sql = 'SELECT * FROM strings';
@ -110,6 +122,9 @@ class D2DocGenerator {
return $strings;
}
/**
* @return array
*/
public function getItemTypesTbl() {
// load itemtypes table in memory
$sql = "SELECT ItemType,Code FROM itemtypes";
@ -118,6 +133,9 @@ class D2DocGenerator {
return $itemtypesTbl;
}
/**
* @return array
*/
public function getNameStr() {
// load namestr from 3 files
$sql = "SELECT code,namestr FROM armor
@ -130,6 +148,10 @@ class D2DocGenerator {
return $namestr;
}
/**
* @param $code
* @return string
*/
public function getImage($code) {
$sql = "SELECT invfile FROM armor WHERE `code`=\"$code\" OR `type`=\"$code\" OR `type2`=\"$code\"";
$img = PDO_FetchOne($sql);
@ -144,6 +166,10 @@ class D2DocGenerator {
return $img = (!empty($img)) ? "$img.png" : "1.png";
}
/**
* @param $code
* @return false
*/
public function getItemName($code) {
$sql = "SELECT name FROM armor WHERE `code`=\"$code\" OR `type`=\"$code\" OR `type2`=\"$code\"";
$name = PDO_FetchOne($sql);
@ -158,6 +184,9 @@ class D2DocGenerator {
return $name;
}
/**
* @return void
*/
public function generateDocs() {
}

View File

@ -42,11 +42,23 @@
*/
/**
*
*/
class D2Files {
/**
* @var array
*/
public $files = [];
/**
* @var
*/
public $charFiles;
/**
*
*/
public function __construct() {
$filesToIgnore = [
"aiparms.txt",
@ -64,6 +76,9 @@ class D2Files {
return $this->files;
}
/**
* @return mixed
*/
public function getSaveFiles() {
$glob = glob($_SESSION['savepath'] . '*.d2s');

View File

@ -42,6 +42,10 @@
*/
/**
* @param $var
* @return void
*/
function ddump($var) {
//echo "<pre>";
var_dump($var);
@ -51,6 +55,10 @@ function ddump($var) {
die();
}
/**
* @param $var
* @return void
*/
function dump($var) {
//echo "<pre>";
var_dump($var);
@ -59,6 +67,10 @@ function dump($var) {
//echo json_encode($var, JSON_INVALID_UTF8_IGNORE | JSON_PRETTY_PRINT);
}
/**
* @param string $str
* @return string
*/
function strtobits(string $str): string {
$ret = "";
for ($i = 0; $i < strlen($str); ++$i) {
@ -74,14 +86,29 @@ function strtobits(string $str): string {
return $ret;
}
/**
* @param string $hex
* @return string
*/
function swapEndianness(string $hex) {
return implode('', array_reverse(str_split($hex, 2)));
}
/**
* @param int $n
* @param int $p
* @param bool $b
* @return int
*/
function setBit(int $n, int $p, bool $b) {
return ($b ? ($n | (1 << $p)) : ($n & ~(1 << $p)) );
}
/**
* @param int $b
* @param int $p
* @return int
*/
function getBit(int $b, int $p) {
return intval(($b & (1 << $p)) !== 0);
}
@ -91,30 +118,22 @@ function getBit(int $b, int $p) {
* @param $data
* @return string
*/
//function checksum($data) {
// $nSignature = 0;
// $checksum = 0;
// foreach ($data as $k => $byte) {
//// if ($k == 12 || $k == 13 || $k == 14 || $k == 15) {
//// $byte = 0;
//// }
// $nSignature = ((($nSignature << 1) | ($nSignature >> 31)) + $byte) & 0xFFFFFFFF;
// $checksum = (($checksum << 1) & 0xffffffff) + $byte + (($checksum & 0x80000000) != 0 ? 1 : 0);
// }
// dump(swapEndianness(dechex($nSignature)));
// dump(swapEndianness(dechex($checksum)));
// //return swapEndianness(dechex($nSignature));
// //return swapEndianness(dechex($checksum)); // all of a sudden started returning wrong value, no longer works
//}
//
//function checksumFix(object $ByteReader, string $filePath) {
// $ByteReader->writeBytes(12, 00000000); // zero old checksum
// $newChecksum = checksum(unpack('C*', $ByteReader->getData())); // get new checksum
// $ByteReader->writeBytes(12, $newChecksum); // write new checksum
// file_put_contents($filePath, $ByteReader->getData()); // write bytestream to file
//}
function checksum($fileData) {
$nSignature = 0;
foreach ($fileData as $k => $byte) {
if ($k == 12 || $k == 13 || $k == 14 || $k == 15) {
$byte = 0;
}
$nSignature = ((($nSignature << 1) | ($nSignature >> 31)) + $byte & 0xFFFFFFFF);
}
return swapEndianness(str_pad(dechex($nSignature), 8, 0, STR_PAD_LEFT));
}
function checksumFix(string $filePath) {
/**
* @param string $filePath
* @return string
*/
function checksumFix(string $filePath) {
return trim(shell_exec("bin\d2scs.exe \"$filePath\""));
}
@ -135,10 +154,18 @@ function strposX($haystack, $needle, $number) {
}
}
/**
* @param string $bit
* @return int
*/
function isBit(string $bit): int {
return ((int) $bit ? 1 : 0 );
}
/**
* @param $input
* @return string
*/
function toBits($input): string {
$output = '';
if (is_string($input)) {
@ -156,6 +183,9 @@ function toBits($input): string {
}
}
/**
* @return void
*/
function print_mem() {
/* Currently used memory */
$mem_usage = memory_get_usage();
@ -167,6 +197,11 @@ function print_mem() {
echo 'Peak usage: <strong>' . round($mem_peak / 1024 / 1024) . 'MB</strong> RAM.<br><br>';
}
/**
* @param $src
* @param $dst
* @return void
*/
function rcopy($src, $dst) {
// open the source directory
$dir = opendir($src);
@ -185,6 +220,10 @@ function rcopy($src, $dst) {
closedir($dir);
}
/**
* @param $src
* @return void
*/
function rrmdir($src) {
$dir = opendir($src);
while (false !== ( $file = readdir($dir))) {

View File

@ -3,11 +3,23 @@
require_once 'D2BitReader.php';
require_once 'D2ItemStructureData.php';
/**
*
*/
class D2Item {
/**
* @var null
*/
private $bits = null;
/**
* @var null
*/
public $iData = null;
/**
* @param $bits
*/
public function __construct($bits) {
if ($bits == '')
return false;
@ -20,6 +32,9 @@ class D2Item {
* @return array of item details
*/
/**
* @return null
*/
private function parseItem() {
$b = new D2BitReader($this->bits);

View File

@ -1,10 +1,22 @@
<?php
/**
*
*/
class D2ItemData {
/**
* @var
*/
public $strings;
/**
* @var
*/
public $images;
/**
* @return void
*/
public function getImages() {
$sql = "SELECT code,uniqueinvfile,invfile,type,type2 FROM armor
UNION ALL
@ -24,6 +36,11 @@ class D2ItemData {
unset($isc2);
}
/**
* @param $index
* @param $doc
* @return false|string
*/
public function uniqueItems($index, $doc = FALSE) {
if (empty($this->images)) {
$this->getImages();
@ -897,6 +914,10 @@ class D2ItemData {
}
}
/**
* @param $iscStat
* @return mixed
*/
public function getIscStrings($iscStat) {
if (empty($this->strings)) {
$this->getStrings();
@ -904,6 +925,9 @@ class D2ItemData {
return ($this->strings[$iscStat]);
}
/**
* @return void
*/
public function getStrings() {
$sql = "
SELECT p.`code` as prop,

View File

@ -42,6 +42,9 @@
*/
/**
*
*/
class D2ItemDesc {
/*
@ -135,14 +138,26 @@ class D2ItemDesc {
*/
public $str = '';
/**
* @var string
*/
public $str = '';
// takes value, param.
// value == Value to show on this stat
//descfunc 14
public $skilltabsDesc;
public $skilltabsDescClean;
public $skilltabs = [
/**
* @var
*/
public $skilltabsDesc;
/**
* @var
*/
public $skilltabsDescClean;
/**
* @var string[]
*/
public $skilltabs = [
//ama
'0' => 'StrSklTabItem3',
'1' => 'StrSklTabItem2',
@ -172,7 +187,10 @@ class D2ItemDesc {
'19' => 'StrSklTabItem20',
'20' => 'StrSklTabItem21'
];
public $charClass = [
/**
* @var string[]
*/
public $charClass = [
"ama" => "Amazon",
"sor" => "Sorceress",
"nec" => "Necromancer",
@ -245,7 +263,13 @@ class D2ItemDesc {
*
*/
public function prep($par, $min, $max) {
/**
* @param $par
* @param $min
* @param $max
* @return void
*/
public function prep($par, $min, $max) {
}
@ -268,16 +292,19 @@ class D2ItemDesc {
*/
public function __construct() {
/**
*
*/
public function __construct() {
}
public function getDesc($params = []) {
/**
* @param $params
* @return false|string
*/
public function getDesc($params = []) {
if (empty($params))
return false;

View File

@ -1,40 +1,112 @@
<?php
/**
*
*/
class D2ItemLocation {
//location
/**
*
*/
const STORED = 0;
/**
*
*/
const EQUIPPED = 1;
/**
*
*/
const BELTI = 2;
/**
*
*/
const CURSOR = 4;
/**
*
*/
const ITEM = 6;
}
/**
*
*/
class D2ItemLocationStored {
//storage
/**
*
*/
const NONE = 0;
/**
*
*/
const INVENTORY = 1;
/**
*
*/
const CUBE = 4;
/**
*
*/
const STASH = 5;
}
/**
*
*/
class D2ItemLocationBody {
//body parts
/**
*
*/
const HELMET = 1;
/**
*
*/
const AMULET = 2;
/**
*
*/
const ARMOR = 3;
/**
*
*/
const WEAPONR = 4;
/**
*
*/
const WEAPONL = 5;
/**
*
*/
const RINGR = 6;
/**
*
*/
const RINGL = 7;
/**
*
*/
const BELT = 8;
/**
*
*/
const BOOTS = 9;
/**
*
*/
const GLOVES = 10;
/**
*
*/
const WEAPONR2 = 11;
/**
*
*/
const WEAPONL2 = 12;
}

View File

@ -42,11 +42,22 @@
*/
/**
*
*/
class D2SaveTXT {
public $path;
public function save($file, $data) {
/**
* @var mixed
*/
public $path;
/**
* @param $file
* @param $data
* @return void
*/
public function save($file, $data) {
$fp = fopen($this->path.DIRECTORY_SEPARATOR.$file, 'a+');
$this->saveBackup($file);
@ -54,7 +65,11 @@ class D2SaveTXT {
}
public function saveBackup($file){
/**
* @param $file
* @return void
*/
public function saveBackup($file){
// if dir doesn't exist, create it
if (!is_dir($this->path.DIRECTORY_SEPARATOR."backup")) mkdir($this->path."backup", 0700);
@ -71,8 +86,12 @@ class D2SaveTXT {
echo "Failed to create backup of $file...\n";
}
}
public function saveTblEnries($filename) {
/**
* @param $filename
* @return void
*/
public function saveTblEnries($filename) {
$post = $_POST;
if (!is_dir($this->path."tblEntries")) mkdir($this->path."tblEntries", 0700);
@ -81,9 +100,12 @@ class D2SaveTXT {
$str = '"'.$post['index'].'"'."\t".'"'.$post['index'].'"'.PHP_EOL;
$file = $this->path."\\tblEntries\\$filename";
file_put_contents($file, $str, FILE_APPEND);
}
public function __construct() {
}
/**
*
*/
public function __construct() {
$this->path = TXT_PATH;
}

View File

@ -1,8 +1,17 @@
<?php
/**
*
*/
class D2Strings {
/**
* @var array
*/
public array $strings;
/**
*
*/
public function __construct(){
$sql = "SELECT * FROM strings";
$this->strings = PDO_FetchAssoc($sql);

View File

@ -41,20 +41,41 @@
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
*
*/
class D2TxtParser {
public $path = TXT_PATH;
/**
* @var mixed
*/
public $path = TXT_PATH;
public $db;
public function __construct() {
/**
* @var
*/
public $db;
/**
*
*/
public function __construct() {
}
public function parseFile($file) {
/**
* @param $file
* @return array
*/
public function parseFile($file) {
return $this->parseData($file);
}
function filterProps($file) {
/**
* @param $file
* @return array
*/
function filterProps($file) {
$data = $this->parseData($file);
$propsToFilter = file(FILTER_PROPERTIES_FILE, FILE_IGNORE_NEW_LINES);
foreach ($data as $d) {
@ -65,7 +86,11 @@ class D2TxtParser {
return $filteredProps;
}
public function parseData($file) {
/**
* @param $file
* @return array
*/
public function parseData($file) {
$file = $this->path . $file;
$rows = array_map(function ($v) {
return str_getcsv($v, "\t");

View File

@ -47,7 +47,7 @@
<div>
<div class="center item_desc" style="">
<div style="height: 116px;background: url(/img/items/bg.png) center top no-repeat;">
<div style="height: 116px;background: url(/img/bg.png) center top no-repeat;">
<a href="" target="_blank" class="item_debug_link">
<img style="" class="item img-fluid" src="">
</a>

219
test.php
View File

@ -1,195 +1,42 @@
<?php
// from unpack('H*', file_get_contents("Char.d2s")) for 3v4l.org to read d2s file here
//$data = hex2bin('55aa55aa60000000f1060000b5bc59b300000000536f72630000000000000000000000002400000001103863000000003cc7ae62ffffffffffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff0000ffff000000000000570300000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8000005e3e9b11000000008888d5f80100000035b70300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000576f6f21060000002a0101000190fd9ffe9ffd9ffe9ffd9f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000057530100000050000201ffffffff7f00000000000000000000000000000000000201ffffffff7f00000000000000000000000000000000000201ffffffff7f0000000000000000000000000000000000017734002e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000067660028083082000a06644000b10252067094c1018082800020292400480a0a0030c202008cc060dcc0f0ca3afa160a0000e03f696600000000000000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000004a4d32004a4d100082006500069226160382af144fb080e03f4a4d1000820065000a9226260302791c00c480e03f4a4d10008000654415c076c606821c7ffff58080010a06fe034a4d1000800065008013268303827e82896086dccb000066c5bcfeffff014a4d1000820065000022f6860782b067b7ec80e03f4a4d1000820065001a922636038249b154da80a025040000c07f4a4d1000a0006500a442665363034a4d1000a0006500fe13367703024a4d1000a0006500de13362703024a4d1000a0006500be13361703024a4d1000a00065009e13360703024a4d1000a00065007e1336a707024a4d1000a00065005e13369707024a4d1000a00065003e1336f706024a4d1000a0006500c013e65606024a4d1000a00065006012d68607024a4d1000a00065000e12d60603024a4d1000a00065006412564306024a4d1000800065001e83179303029920441686e03f4a4d1000a0006500fe12d69606024a4d1000a0006500de12d68606024a4d1000a0006500be12d67606024a4d1000a00065009e12e67606024a4d1000a00065007e12e69606024a4d1000a0006500fc13e6a606024a4d1000a0006500dc13c68603024a4d1000a0006500bc13e6b606024a4d1000800065009c83272303826f9aed3186e03f4a4d1000800065007c8317730302acd0c26e86e03f4a4d1000a00065005c13560603024a4d1000a00065003c13569607024a4d1000a00065001c13464603024a4d1000a00065001212d66607024a4d1000a00865001412d66607024a4d1000a0006500fc1226f606024a4d1000a0006500dc12265607024a4d1000a0086500bc12265607024a4d1000a00865009c12265607024a4d1000a00865007c12265607024a4d1000a0086500fa13265607024a4d1000a0006500da43665363034a4d1000a0086500ba43665363034a4d1000a00065009a53468333064a4d10008000650016421616038245fc36c6822cff014a4d100080006500a032166707028c6e88690101000408052c30c0c00054e03f4a4d10008000650411c026c60602385900c78080010a08fe034a4d1000800065004632166707822c1b5e930001000498042c30c0c00054e03f4a4d1000800065007a8337130382a07c2a5786e03f4a4d1008800065000ec326c60602915c92ea8280010a07e23f4a4d100880046500b23237470612e9b107ed80104b83c140fc3776ece0f80f4a4d1000a00065180010365603024a4d00006a664a4d00006b6600');
/*
Diablo 2 D2S Save File Checksum Calculator
By Hash Borgir (https://www.psychedelicsdaily.com)
@date 6/4/2022
require_once './src/D2Functions.php';
require_once './src/D2BitReader.php';
class D2ByteReader {
private string $data;
private int $offset = 0;
public function __construct(string $data) {
if (!$data)
return false;
$this->data = $data;
}
public function skip(int $numBytes): bool {
if ($numBytes < 0 || $numBytes > strlen($this->data))
return false;
$this->offset += $numBytes;
return $true;
}
public function seek(int $pos): bool {
if ($pos < 0 || $pos > strlen($this->data))
return false;
$this->offset = $pos;
return true;
}
public function readh(int $offset, int $numBytes, bool $str = true): string {
$this->seek($offset);
$bytes = null;
for ($i = $this->offset; $i < $this->offset + $numBytes; $i++) {
$str ? $bytes .= $this->data[$i] : $bytes[] = $this->data[$i];
}
return unpack('H*', $bytes)[1];
}
public function readc(int $offset, int $numBytes, bool $str = true): array {
$this->seek($offset);
$bytes = null;
for ($i = $this->offset; $i < $this->offset + $numBytes; $i++) {
$str ? $bytes .= $this->data[$i] : $bytes[] = $this->data[$i];
}
return unpack('C*', $bytes);
}
public function rewind(): bool {
$this->offset = 0;
return true;
}
public function writeByte(int $offset, int $byte) {
$this->data[$offset] = pack('C', $byte);
}
public function writeBytes(int $offset, string $bytes) {
if ($offset < 0 || $offset > strlen($this->data) || $bytes == '')
return false;
$_bytes = str_split($bytes, 2);
foreach ($_bytes as $k => $byte) {
$pos = $offset + $k;
$this->data[$pos] = pack('H*', $byte);
}
}
public function getData() {
return $this->data ? $this->data : false;
}
public function getOffset(): int {
return $this->offset;
}
public function isHexString(string $str): bool {
if (strlen($str) % 2 == 0 && (ctype_xdigit($str))) {
return true;
}
return false;
}
public function toBits($input): string {
$output = '';
if ($this->isHexString($input)) {
foreach (str_split($input, 2) as $byte) {
$output .= (strrev(str_pad(decbin(hexdec($byte)), 8, 0, STR_PAD_LEFT)));
}
return $output;
} else if (is_string($input)) {
foreach (str_split($input) as $i) {
$output .= strrev(str_pad(decbin(ord($i)), 8, 0, STR_PAD_LEFT));
}
return $output;
} else if (is_int($input)) {
return strrev(str_pad(decbin($input), 8, 0, STR_PAD_LEFT));
} else if (is_array($input)) {
foreach ($input as $i) {
$output .= $this->tobits($i);
}
return $output;
}
}
public function toBytes(string $bits) {
foreach (str_split($bits, 8) as $byteString) {
$bytes[] = (bindec(strrev($byteString)));
}
foreach ($bytes as $byte) {
dump($byte);
}
}
public function bitsToHexString(string $bits): string {
$bytes = '';
foreach (str_split($bits, 8) as $byte) {
$bytes .= (str_pad(dechex((bindec(strrev($byte)))), 2, 0, STR_PAD_LEFT));
}
return $bytes;
}
public function bitsToHexArray(string $bits): array {
$bytes = [];
foreach (str_split($bits, 8) as $byte) {
$bytes[] = (str_pad(dechex((bindec(strrev($byte)))), 2, 0, STR_PAD_LEFT));
}
return $bytes;
}
public function bitsToIntArray(string $bits): array {
$bytes = [];
foreach (str_split($bits, 8) as $byte) {
$bytes[] = (int) (str_pad(dechex((bindec(strrev($byte)))), 2, 0, STR_PAD_LEFT));
}
return $bytes;
}
/*
@return Byte with Nth bit set to X
*/
public function setBit(int $byte, int $pos, bool $bit) {
return ($bit ? ($byte | (1 << $pos)) : ($byte & ~(1 << $pos)) );
}
/*
@return Bit at Nth position in Byte
*/
public function getBit(int $byte, int $pos): int {
return intval(($byte & (1 << $pos)) != 0);
}
Checksum field is at byte 12.
Bytes 12/13/14/15 as a uint32.
Set this to 0.
After clearing the checksum field add up the values of all the bytes in the file and rotate the running total one bit to the left before adding the next byte.
*/
function swapEndianness(string $hex) {
return implode('', array_reverse(str_split($hex, 2)));
}
$filename = "D:\Diablo II\MODS\ironman-dev\save\Barb.d2s";
function checksum($fileData) {
$nSignature = 0;
foreach ($fileData as $k => $byte) {
if ($k == 12 || $k == 13 || $k == 14 || $k == 15) {
$byte = 0;
}
$nSignature = ((($nSignature << 1) | ($nSignature >> 31)) + $byte & 0xFFFFFFFF);
}
return swapEndianness(str_pad(dechex($nSignature), 8, 0, STR_PAD_LEFT));
}
$filename = "D:\Diablo II\MODS\ironman-dev\save\Necro.d2s";
$fp = fopen($filename, "r+b");
fseek($fp, 12); // go to byte 12
fwrite($fp, pack('I', 0)); // clear the checksum field uInt32
$fileData = unpack('C*', file_get_contents($filename)); // open file and unpack
var_dump(checksum($fileData));
//$file = "D:\Diablo II\MODS\ironman-dev\save\Barb.d2s";
//$data = file_get_contents($file);
//
//$c = new D2ByteReader($data);
//
//$checksum = checksum(unpack('C*', $c->getData()));
//
//ddump($checksum);
//$bytes = $c->readh(643, 5);
//$bits = $c->toBits($bytes);
//
//$bits[4] = 0;
//$bits[5] = 0;
//$bits[8] = 0;
//$newBytes = $c->bitsToHexString($bits);
//$c->writeBytes(643, $newBytes);
//
//$c->writeBytes(12, "00000000"); // zero old checksum
//
//dump($c->readh(12, 4));
//
//$newChecksum = checksum(unpack('C*', $c->getData())); // get new checksum
//$c->writeBytes(12, $newChecksum); // write new checksum
//
//dump($c->readh(12, 4));
//
//file_put_contents($file, $c->getData()); // write bytestream to file
fseek($fp, 12); // go to byte 12
fwrite($fp, pack('H8', checksum($fileData))); // write new checksum
fclose($fp);