Код:
#!/usr/bin/perl
my @arr;
my $keysec;
my @sps = (
"None", # incorrect spell
"Fire Arrow", # огненная стрела
"Fire Ball", # огненный шар
"Fire Wall", # огненная стена
"Protection from Fire", # защита от магии огня
"Ice Missile", # ледяная стрела
"Poison Cloud", # ядовитый туман
"Blizzard", # град
"Protection from Water", # защита от магии воды
"Acid Stream", # кислотная атака
"Lightning", # молния
"Prismatic Spray", # радужная молния
"Invisibility", # невидимость
"Protection from Air", # защита от магии воздуха
"Darkness", # тьма
"Light", # свечение
"Diamond Dust", # каменная стрела
"Wall of Earth", # каменная стена
"Stone Curse", # каменное проклятие
"Protection from Earth", # защита от магии огня
"Bless", # благословение
"Haste", # ускорение
"Control Spirit", # перерождение
"Teleport", # телепорт
"Heal", # исцеление
"Summon", # зов
"Drain Life", # вампиризм
"Shield", # магический щит
"Curse", # проклятие
"Slow"); # замедление
# Все тоже, что и в предыдущем, только чуть хитрее записано. Считывает данные из некого массива @arr начиная с места $idx, которое передается в качестве параметра
sub readil($) {
my $idx = shift;
$a = @arr[$idx];
$b = @arr[$idx+1];
$c = @arr[$idx+2];
$d = @arr[$idx+3];
return $a | ($b << 8) | ($c << 16) | ($d << 24);
}
#аналогично, но читает 2 байта
sub readwl($) {
my $idx = shift;
$a = @arr[$idx];
$b = @arr[$idx+1];
return $a | ($b << 8);
}
#а тут байт
sub readbl($) {
my $idx = shift;
$a = @arr[$idx];
return $a;
}
#а тут читает из файла 4 байта
sub readi($) {
my $f = shift;
read($f, $a, 1);
read($f, $b, 1);
read($f, $c, 1);
read($f, $d, 1);
return ord($a) | (ord($b) << 8) | (ord($c) << 16) | (ord($d) << 24);
}
#а тут байт из файла
sub readb($) {
my $f = shift;
read($f, $a, 1);
return ord($a);
}
# Расшифровывает каждый байт секции
sub decrypt {
$keysec = (($keysec >> 0x10) & 0xFFFF);
$k = $keysec | ($keysec << 0x10);
# print($kh);
$c = 0;
foreach $i(@arr) {
$i = $i & 0xFF;
# $ih1 = sprintf("0x%2.2X", $i);
$i = (($k >> 0x10) ^ $i) & 0xFF;
$k = ($k << 1) & 0xFFFFFFFF;
if (($c & 0x0F) == 0x0F) {
$k |= $keysec;
}
$c+=1;
# $kh = sprintf("0x%8.8X", $k);
# $ih = sprintf("0x%2.2X", $i);
# print("$ih1 $ih $kh\n");
}
}
sub testchecksum {
my $ch = 0;
foreach $i(@arr) {
$ch = (($ch << 1) & 0xFFFFFFFF) + ($i & 0xFF);
# print "ch=$ch, i=$i\n";
}
return $ch & 0xFFFFFFFF;
}
sub readpack {
#Пак находится в @arr начиная с 9_го байта
$idx = 9;
$size = scalar(@arr);
while ($idx < $size) {
#считаем 2_ухбайтный id вещи
$itid = readwl($idx); $idx+=2;
#этот байт
$b0 = readbl($idx); $idx++;
$IDh = sprintf("%.4Xh", $itid);
if (($b0 & 0x80) == 0x80) {
#Если 24_ый бит в $b0 равен 1, то...
#...$cc - это $b0 с обнуленным битом и вещь простая - немагическая, а сам $cc - её количество
$cc = $b0-0x80;
print(" found simple object: ID=$IDh, count=$cc\n");
} elsif (($b0 & 0x20) == 0x20) {
#Если иначе 18-ый бит равен 1, то считывается 4 байта - это цена магической вещи
$price = readil($idx); $idx+=4;
# $ccc - это двухбайтная переменная, младший, байт из 2_ух байт - $b0. $ccc - это количество магий, навешанных на шмотку
$ccc = $b0 & 0xF;
print(" found magic object: ID=$IDh, price=$price gold\n");
$i = 0;
while ($i++ < $ccc) {
#считываем магии, висящие на шмотке. Первый байт - код магии (например, сила+), второй ее значние (2, итого сила +2)
$eid = readbl($idx); $idx++;
$eIDh = sprintf("%.2Xh", $eid);
$evl = readbl($idx); $idx++;
print(" effect ID=$eIDh, value=$evl\n");
# if ($eid == 0x29 || ($eid >= 0x2C && $eid <=0x30)) {
# $ccc--;
# }
}
} elsif (!$b0) {
#если $b0 нулевой, то количество простых шмоток лежит в следующих двух байтах
$ccc = readwl($idx); $idx+=2;
print(" found simple object: ID=$IDh, count=$ccc\n");
}
}
}
$fn = shift;
if (!$fn) {
$fn = "test.a2c";
}
open(C, $fn) || die "Can't open character file: $!!\n";
binmode C;
# Считываем 4 байта. По всей видимости, впустую
$sig=readi(C);
#sisec - сигнатура секции
while($sigsec=readi(C) & 0xFFFFFFFF) {
#размер секции
$sizsec=readi(C) & 0xFFFFFFFF;
$keysec=readi(C) & 0xFFFFFFFF;
# crc (контрольная сумма секции)
$crcsec=readi(C) & 0xFFFFFFFF;
$sigh = sprintf("%.8Xh", $sigsec);
print("section found: $sigh\n size: $sizsec bytes\n");
#Разбор по типу сигнатуры
if ($sigsec == 0xAAAAAAAA) {
print(" main:\n");
read(C, $body, $sizsec);
# @arr - массив, в который загнали данные секции. @acc = unpack ("c*", $body) означает, что мы побайтно загнали $body в @acc, только и всего
@arr = unpack("c*", $body);
#расшифруем зашифрованную секцию
decrypt();
#считаем id, он лежит в начале секции main
$id1 = readil(0);
$id2 = readil(4);
print(" ID=$id1/$id2\n");
$idxxx = 0;
$s = "";
# После, начиная с 12_го байта лежит имя персонажа. имя персонажа кончается байтом со значением в 0
while ($c = readbl(12+$idxxx++)) {
$s = $s . chr($c & 0xFF);
}
print(" nick: '$s'\n");
#проверим контрольную сумму секции
if ($crcsec!=testchecksum()) { die "bad chksum!\n"; }
}
elsif ($sigsec == 0x55555555) {
#некая additional секция, где хз какие данные хранятся
print(" additional:\n");
read(C, $body, $sizsec);
@arr = unpack("c*", $body);
decrypt();
if ($crcsec!=testchecksum()) { die "bad chksum!\n"; }
}
elsif ($sigsec == 0x40A40A40) {
#аналогично
print(" memory:\n");
read(C, $body, $sizsec);
@arr = unpack("c*", $body);
decrypt();
if ($crcsec!=testchecksum()) { die "bad chksum!\n"; }
}
elsif ($sigsec == 0x3A5A3A5A) {
# секция, в которой хранится пак персонажа
print(" in-the-pack:\n");
read(C, $body, $sizsec);
@arr = unpack("c*", $body);
decrypt();
if ($crcsec!=testchecksum()) { die "bad chksum!\n"; }
readpack();
}
elsif ($sigsec == 0xDE0DE0DE) {
#аналогично, но хранятся вещи на персонаже
print(" on-the-body:\n");
read(C, $body, $sizsec);
@arr = unpack("c*", $body);
decrypt();
if ($crcsec!=testchecksum()) { die "bad chksum!\n"; }
readpack();
}
elsif ($sigsec == 0x41392521) {
# Секция с характеристиками персонажа
$keysec = $keysec >> 0x10;
# $keysec - ключ к расшифровке параметров персонажа. Видимо создатели а2 сильно постарались и завязали один параметр на другой,
# что б с полпинка не расшивровали. Хз как ленд допер, но факт остается фактом, расшифровка ниже
if ($keysec & 0x0001) { $tmp=readb(C);}
$monsters_kills=(readi(C) ^ 0x1529251) & 0xFFFFFFFF;
if ($keysec & 0x0002) {$tmp=readb(C);}
$players_kills=(readi(C)+ $monsters_kills *5+0x13141516) & 0xFFFFFFFF;
if ($keysec & 0x0004) {$tmp=readb(C);}
$frags = (readi(C) + $players_kills * 7 + 0xABCDEF) & 0xFFFFFFFF;
if ($keysec & 0x0008) {$tmp=readb(C);}
$deaths = (readi(C) ^ 0x17FF12AA ) & 0xFFFFFFFF;
if ($keysec & 0x0010) {$tmp=readb(C);}
$money = (readi(C) + $monsters_kills *3 - 0x21524542) & 0xFFFFFFFF;
if ($keysec & 0x0020) {$tmp=readb(C);}
$body_level = (readb(C) + $money * 0x11 + $monsters_kills * 0x13) & 0xFF;
if ($keysec & 0x0040) {$tmp=readb(C);}
$reaction_level = (readb(C) + $body_level*3) & 0xFF;
if ($keysec & 0x0080) {$tmp=readb(C);}
$mind_level = (readb(C) + $body_level + $reaction_level*5) & 0xFF;
if ($keysec & 0x0100) {$tmp=readb(C);}
$spirit_level = (readb(C) + $body_level * 7 + $mind_level*9) & 0xFF;
if ($keysec & 0x4000) {$tmp=readb(C);}
$spells = (readi(C) - 0x10121974) & 0xFFFFFFFF;
if ($keysec & 0x2000) {$tmp=readb(C);}
$active_spell = (readi(C)) & 0xFFFFFFFF;
if ($keysec & 0x0200) {$tmp=readb(C);}
$exp_fire_blade = (readi(C) ^ 0xDADEDADE) & 0xFFFFFFFF;
if ($keysec & 0x0400) {$tmp=readb(C);}
$exp_water_axe = (readi(C) - $exp_fire_blade*0x771) & 0xFFFFFFFF;
if ($keysec & 0x0800) {$tmp=readb(C);}
$exp_air_bludgeon = (readi(C) - $exp_water_axe*0x771) & 0xFFFFFFFF;
if ($keysec & 0x1000) {$tmp=readb(C);}
$exp_earth_pike = (readi(C) - $exp_air_bludgeon*0x771) & 0xFFFFFFFF;
if ($keysec & 0x2000) {$tmp=readb(C);}
$exp_astral_shooting = (readi(C) - $exp_earth_pike*0x771) & 0xFFFFFFFF;
$idxx = 0;
print(" other settings:\n");
print(" spell-list:\n");
foreach $i (@sps) {
# Если каждый байт переменной $spell отвечает за выученное заклинание, список которых выше
if ($spells & (1 << $idxx)) {
print(" $i\n");
}
$idxx++;
}
print(" fire/blade exp: $exp_fire_blade\n");
print(" water/axe exp: $exp_water_axe\n");
print(" air/bludgeon exp: $exp_air_bludgeon\n");
print(" earth/pike exp: $exp_earth_pike\n");
print(" astral/shooting exp: $exp_astral_shooting\n");
}
else {
die "invalid section's signature!\n";
}
}