Working copy

This commit is contained in:
Hash Borgir 2023-05-30 12:06:58 -06:00
parent eb28039f88
commit dd662e26f6
11 changed files with 206 additions and 85 deletions

BIN
.DS_Store vendored Normal file

Binary file not shown.

View File

@ -2,5 +2,6 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/docs/ironman-dev_backup" vcs="Git" />
</component>
</project>

View File

@ -1,9 +1,5 @@
<?php
error_reporting(E_ERROR | E_PARSE);
set_time_limit(-1);
ini_set('max_input_time', '-1');
ini_set('max_execution_time', '0');
session_start();
ob_start();
@ -16,6 +12,11 @@ require_once './src/D2BitReader.php';
require_once './src/D2Item.php';
require_once './src/D2Char.php';
error_reporting(E_ERROR | E_PARSE);
set_time_limit(-1);
ini_set('max_input_time', '-1');
ini_set('max_execution_time', '0');
define('DB_FILE', $_SESSION['modname'] . ".db");
PDO_Connect("sqlite:" . DB_FILE);
@ -42,10 +43,11 @@ foreach ($ISCData as $k => $v) {
//$filePath = "D:\Diablo II\MODS\MedianXL2012\save\Test.d2s";
$filePath = "Necro.d2s";
$filePath = "Test.d2s";
$char = new D2Char($filePath);
$char->setChar("CharacterStatus", "Died", 0);
$char->setChar("CharacterStatus", "Hardcore", 1);
//$char->setChar("CharacterStatus", "Died", 0);
//$char->setChar("CharacterStatus", "Hardcore", 1);
//$char->setChar("CharacterStatus", "Expansion", 1);
//$char->setChar("LeftmousebuttonskillID", 223);
@ -53,13 +55,21 @@ $char->setAllSkills(1);
//$char->setSkill(1, 99);
//$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->setChar("CharacterStatus", "Died", 1);
$char->setChar("CharacterLevel", 99);
$char->setStat("strength", 270);
$char->setStat("energy", 270);
$char->setStat("dexterity", 270);
$char->setStat("vitality", 270);
$char->setStat("hitpoints", 100);
$char->setStat("maxhp", 150);
$char->setStat("stamina", 280);
$char->setStat("maxstamina", 290);
$char->setStat("mana", 70);
$char->setStat("maxmana", 200);
//$char->setStat("soulcounter", 80);
//
//$char->setStat("hitpoints", 70);
@ -77,4 +87,6 @@ $char->setChar("CharacterLevel", 25);
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);
var_dump($char->cData['CharacterStatus']);
var_dump($char->cData['stats']);

114
D2Reviver.php Normal file
View File

@ -0,0 +1,114 @@
<?php
require_once './src/D2Functions.php';
require_once './src/D2ByteReader.php';
require_once './src/D2BitReader.php';
$str = <<<EOT
__ __
`."._ _,",'
`. "-----, .-----" ,'
`. / \ ,'
\------. _ | |__ ,-.__L | _ _ ,---.
\ .--. \ \'-, | ,',.'. \ ._ \ | \'-'/ / .-. \
| | | | | | | J /__\ L | |_)( | | | f / \ l
| | | | | | | | | ] ] | .-. \ | | | , t \ / j
/ '--' / ,'_'. |.'_'. F F / |__)/ | / '--'/ \ '-' /
/_,----' " " | " " /,' "-----' | "-----' '---'
(' | |'" | |
_,-" "-._ _,-" "-._ (C) HCB https://im.stoned.io
<_____________> <_____________> https://d2mods.org
EOT;
$cbits = [
0 => 10, 1 => 10, 2 => 10, 3 => 10, 4 => 10, 5 => 8, 6 => 21, 7 => 21, 8 => 21, 9 => 21, 10 => 21,
11 => 21, 12 => 7, 13 => 32, 14 => 25, 15 => 25, 37 => 31, 39 => 31, 41 => 31, 43 => 31, 45 => 31,
183 => 31, 184 => 31, 185 => 31]; // CSvBits to skip
$exp = [
0, 500, 1500, 3750, 7875, 14175, 22680, 32886, 44396, 57715, 72144, 90180, 112725, 140906, 176132, 220165, 275207, 344008,
430010, 537513, 671891, 839864, 1049830, 1312287, 1640359, 2050449, 2563061, 3203826, 3902260, 4663553, 5493363, 6397855,
7383752, 8458379, 9629723, 10906488, 12298162, 13815086, 15468534, 17270791, 19235252, 21376515, 23710491, 26254525, 29027522,
32050088, 35344686, 38935798, 42850109, 47116709, 51767302, 56836449, 62361819, 68384473, 74949165, 82104680, 89904191, 98405658,
107672256, 117772849, 128782495, 140783010, 153863570, 168121381, 183662396, 200602101, 219066380, 239192444, 261129853, 285041630,
311105466, 339515048, 370481492, 404234916, 441026148, 481128591, 524840254, 572485967, 624419793, 681027665, 742730244, 809986056,
883294891, 963201521, 1050299747, 1145236814, 1248718217, 1361512946, 1484459201, 1618470619, 1764543065, 1923762030, 2097310703,
2286478756, 2492671933, 2717422497, 2962400612, 3229426756, 3520485254, 3837739017];
//$file = "D:\Diablo II\MODS\ironman-dev\save\Necro.d2s";
$file = $argv[1];
if (!file_exists($file))
die("$file does not exist!");
// create ByteReader object from file binary data
$BR = new D2ByteReader(file_get_contents($file));
$data = $BR->getData();
// offset 36
// 7 6 5 4 3 2 1 0
// unknown Expansion Character unknown Died Hardcore unknown
$statusByte = unpack('C', $data[36])[1];
$dBit = getBit($statusByte, 3);
// if Character is dead, then make it alive, reduce 5 levels as penalty
if ($dBit) {
// using {} for better readability
{
$gf = strposX($data, 'gf', 1) + 2; // find gf and skip it
$if = strposX($data, 'if', 1);
$len = $if - $gf;
// create bitstream
$stats = new D2BitReader($BR->toBits($BR->readh($gf, $len)));
// remove 0x1FF from end
$bits = $stats->getBits();
$cleanbits = substr($bits, 0, -11);
$stats->setBits($cleanbits);
$bits = $stats->getBits();
// rewind bitstream
$stats->rewind();
// get bit offsets for each stat
$offsets = [];
for ($i = 0; $i < count($cbits); $i++) {
$id = hexdec($BR->toBytesR($stats->readb(9)));
$offsets[$id] = $stats->getOffset();
$stats->skip($cbits[$id]);
}
$offsets[0] = 9;
// get current character level
$level = unpack('C', $data[43])[1];
$stats->seek($offsets[12]);
$_level = hexdec($BR->toBytesR($stats->readb(7)));
// reduce current character level by 5, no penalty if < 6
if ($_level >= 80) {
$level -= 10;
} else if ($_level >= 5 || $level <= 80) {
$level -= 5;
} else if ($_level <= 5) {
$level = 1;
}
}
$data[43] = pack('C', $level);
$statusByteNew = setBit($statusByte, 3, 0); // make character alive
$data[36] = pack('C', $statusByteNew); // make character alive
$bitsToWriteLevel = strrev(str_pad(decbin(intval($level)), $cbits[12], 0, STR_PAD_LEFT));
$bitsToWriteExp = strrev(str_pad(decbin(intval($exp[$level])), $cbits[13], 0, STR_PAD_LEFT));
$_newBitsTemp = $stats->writeBits($bits, $bitsToWriteLevel, $offsets[12]);
$newBits = $stats->writeBits($_newBitsTemp, $bitsToWriteExp, $offsets[13]) . "11111111100";
$bytes = $BR->toBytes($newBits);
$BR->setData($data);
$BR->writeBytes($gf, $bytes);
$BR->writeBytes(12, "00000000");
$checksum = checksum(unpack('C*', $BR->getData()));
$BR->writeBytes(12, $checksum);
$data = $BR->getData();
file_put_contents($file, $data);
echo "<pre>$str\n\n\nCharacter is now alive. Go take some mushrooms!\n";
echo "Previous Level: $_level\n";
echo "Revive Penalty: " . $_level - $level . "\n";
echo "New Level: $level\n";
} else {
echo "<pre>$str\n\n\nCharacter is not dead. Have you been taking mushrooms lately?";
}

BIN
bin/D2SC.exe Normal file

Binary file not shown.

View File

@ -107,7 +107,6 @@ if (file_exists(APP_DB)) {
processFilesManuallyInSqlite();
processFilesManuallyInSqliteLinux();
function toPngAll() {
$q = realpath("bin\qdc6.exe");
$iPath = $_SESSION['modpath'] . "\data\global\items";
@ -127,33 +126,35 @@ if (file_exists(APP_DB)) {
toPngAll();
/*
Process tbl files
*
*
*/
$tbl = $_SESSION['tbl'];
// ddump($tbl);
/*
$string = D2Tbl::getStrings($tbl."string.tbl");
$stringExpansion = D2Tbl::getStrings($tbl."expansionstring.tbl");
$stringPatch = D2Tbl::getStrings($tbl."patchstring.tbl");
$strings = array_merge($stringPatch, $stringExpansion, $string);
*/
$strs = ["dummy" => "dummy"];
foreach (glob($tbl . "*.tbl") as $filename) {
$strings = D2Tbl::getStrings($filename);
if (!empty($strings))
$strs = array_merge($strs, $strings);
}
$db->writeTbl($strs);
// write each table to invidual db table
$tbl = $_SESSION['tbl'];
foreach (glob($tbl . "*.tbl") as $filename) {
$tblName = pathinfo($filename)['filename'];
$strings = D2Tbl::getStrings($filename);
if (!empty($strings)) {
$db->writeTbls($tblName, $strings);
}
}
} else {
// if config db does not exist, go to configure page
header("Location: /src/D2Config.php");

View File

@ -54,6 +54,7 @@ class D2BitReader {
* @return array|string|string[]
*/
public function writeBits(string $bits, string $bitsToWrite, int $offset) {
//$this->bits = substr_replace($bits, $bitsToWrite, $offset, strlen($bitsToWrite));
return substr_replace($bits, $bitsToWrite, $offset, strlen($bitsToWrite));
}
@ -63,7 +64,7 @@ class D2BitReader {
* @return string
*/
public function readb(int $numBits = 0, bool $str = true): string {
$bits = null;
$bits = '';
for ($i = $this->offset; $i < $this->offset + $numBits; $i++) {
$str ? $bits .= $this->bits[$i] : $bits[] = $this->bits[$i];
}

View File

@ -54,7 +54,22 @@ class D2ByteReader {
* @param bool $str
* @return string
*/
public function readh(int $offset, int $numBytes, bool $str = true): string {
public function read(int $offset, int $numBytes) {
$this->seek($offset);
$bytes = null;
for ($i = $this->offset; $i < $this->offset + $numBytes; $i++) {
$bytes .= $this->data[$i];
}
return $bytes;
}
/**
* @param int $offset
* @param int $numBytes
* @param bool $str
* @return string
*/
public function readh(int $offset, int $numBytes, bool $str = true) {
$this->seek($offset);
$bytes = null;
for ($i = $this->offset; $i < $this->offset + $numBytes; $i++) {
@ -69,7 +84,7 @@ class D2ByteReader {
* @param bool $str
* @return array
*/
public function readc(int $offset, int $numBytes, bool $str = true): array {
public function readc(int $offset, int $numBytes, bool $str = true) {
$this->seek($offset);
$bytes = null;
for ($i = $this->offset; $i < $this->offset + $numBytes; $i++) {
@ -187,6 +202,7 @@ class D2ByteReader {
* @return string
*/
public function toBytesR(string $bits) : string {
$bytes = '';
foreach (str_split($bits, 8) as $byteString) {
$bytes .= strtoupper(str_pad(dechex(bindec(($byteString))), 2, 0, STR_PAD_LEFT));
}

View File

@ -219,7 +219,7 @@ WHERE sk.charclass = '$class'";
unset($this->items);
//unset($this->bData);
unset($this->sData);
unset($this->fp);
//unset($this->fp);
return $this->cData;
}
@ -292,7 +292,7 @@ WHERE sk.charclass = '$class'";
$bytes = $this->ByteReader->toBytes($bits);
$stats->rewind();
for ($i = 0; $i <= strlen($bits); $i++) {
for ($i = 0; $i <= count($this->ISC); $i++) {
$id = hexdec($this->ByteReader->toBytesR($stats->readb(9)));
if ($this->ISC[$id]['CSvBits'] !== NULL && $this->ISC[$id]['CSvBits'] !== '') {
$stats->skip($this->ISC[$id]['CSvBits']);
@ -314,8 +314,8 @@ 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['soulcounter'] = (int) round($values['soulcounter'] / 2);
$values['killcounter'] = (int) round($values['killcounter'] / 2);
// $values['soulcounter'] = (int) round($values['soulcounter'] / 2);
// $values['killcounter'] = (int) round($values['killcounter'] / 2);
$this->cData['stats'] = $values;
}
@ -463,7 +463,7 @@ WHERE sk.charclass = '$class'";
}
$stats .= "000011111111100";
dump($stats);
//dump($stats);
$gf = strposX($this->data, 'gf', 1) + 2; // find gf and skip it
$if = strposX($this->data, 'if', 1);

View File

@ -140,6 +140,24 @@ class D2Database {
$res = PDO_Execute($sql);
}
/**
* @param $data
* @return void
*/
public function writeTbls($table,$data) {
$sql = "CREATE TABLE IF NOT EXISTS `$table` (`Key` VARCHAR(255), `String` VARCHAR(255));";
$res = PDO_Execute($sql);
$sql = "INSERT INTO `$table` (`Key`,`String`) VALUES ";
foreach ($data as $k => $v) {
$sql .= "(\"$k\",\"$v\"),";
}
$sql = rtrim($sql, ", ");
$sql .= ";";
$res = PDO_Execute($sql);
}
/**
* @param $key
* @return mixed

View File

@ -1,42 +0,0 @@
<?php
/*
Diablo 2 D2S Save File Checksum Calculator
By Hash Borgir (https://www.psychedelicsdaily.com)
@date 6/4/2022
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)));
}
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));
fseek($fp, 12); // go to byte 12
fwrite($fp, pack('H8', checksum($fileData))); // write new checksum
fclose($fp);