Created
February 13, 2021 21:13
-
-
Save msg555/71f3aae0f2aa742ef33d200dec491356 to your computer and use it in GitHub Desktop.
Replay read/write routines for dustkid
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
function _substr($data, $pos, $len) { | |
if (0 <= $pos && $pos + $len <= strlen($data) && $pos <= $pos + $len) { | |
return substr($data, $pos, $len); | |
} | |
return null; | |
} | |
function _unpack_byte($data, $pos) { | |
$str = _substr($data, $pos, 1); | |
if ($str === null) { | |
return null; | |
} | |
return first(unpack("C", $str)); | |
} | |
function _unpack_le2($data, $pos) { | |
$str = _substr($data, $pos, 2); | |
if ($str === null) { | |
return null; | |
} | |
return first(unpack("s", $str)); | |
} | |
function _unpack_le4($data, $pos) { | |
$str = _substr($data, $pos, 4); | |
if ($str === null) { | |
return null; | |
} | |
return first(unpack("l", $str)); | |
} | |
function _unpack_float($data) { | |
$str = _substr($data, $pos, 4); | |
if ($str === null) { | |
return null; | |
} | |
return first(unpack("g", $str)); | |
} | |
function _pack_byte($data) { | |
return pack("C", $data); | |
} | |
function _pack_le2($data) { | |
return pack("s", $data); | |
} | |
function _pack_le4($data) { | |
return pack("l", $data); | |
} | |
function _pack_float($data) { | |
return pack("f", $data); | |
} | |
function _eat_bits_le($data, $pos, $num) { | |
$val = 0; | |
for ($i = 0; $i < $num; $i++) { | |
$off = $pos + $i; | |
if (_unpack_byte($data, (int)($off / 8)) & (1 << $off % 8)) { | |
$val |= 1 << $i; | |
} | |
} | |
return $val; | |
} | |
function _write_bits_le(&$data, $pos, $num, $val) { | |
for ($i = 0; $i < $num; $i++) { | |
$off = $pos + $i; | |
$bin = (int)($off / 8); | |
$bit = $off % 8; | |
$bt = _unpack_byte($data, $bin); | |
if ($val & 1 << $i) { | |
$bt |= 1 << $bit; | |
} else { | |
$bt &= ~(1 << $bit); | |
} | |
$data[$bin] = _pack_byte($bt); | |
} | |
} | |
function parse_replay($replay, $header_only = false) { | |
if (_substr($replay, 0, 6) != "DF_RPL") { | |
return null; | |
} | |
$version = _substr($replay, 6, 1); | |
if ($version != "1" && $version != "3" && $version != "4") { | |
return null; | |
} | |
$is_extended = $version >= 3; | |
$players = $is_extended ? _unpack_le2($replay, 7) : 1; | |
$header = array( | |
'version' => $version, | |
'players' => $players, | |
'uncompressedSize' => _unpack_le4($replay, 9), | |
'frames' => _unpack_le4($replay, 13), | |
'characters' => array(), | |
); | |
for ($i = 0; $i < $players; $i++) { | |
$header['characters'][] = _unpack_byte($replay, 17 + $i); | |
} | |
$level_len = _unpack_byte($replay, 17 + $players); | |
$header['level'] = _substr($replay, 18 + $players, $level_len); | |
if ($header['level'] === null || | |
18 + $players + $level_len > strlen($replay)) { | |
return null; | |
} | |
if ($header_only) { | |
return $header; | |
} | |
$replay = @gzuncompress(substr($replay, 18 + $players + $level_len)); | |
if ($replay === false) { | |
return null; | |
} | |
$pos = 0; | |
$inputsLen = _unpack_le4($replay, $pos); $pos += 4; | |
$inputs_all = array(); | |
$num_intents = 7; | |
if ($version >= 4) { | |
$num_intents = 11; | |
} else if ($version >= 3) { | |
$num_intents = 8; | |
} | |
for ($pl = 0; $pl < $players; $pl++) { | |
$inputs = array(); | |
for ($i = 0; $i < $num_intents; $i++) { | |
$len = _unpack_le4($replay, $pos); $pos += 4; | |
$datum = _substr($replay, $pos, $len); $pos += $len; | |
if ($datum === null) { | |
return null; | |
} | |
$state = $i < 2 ? 1 : 0; | |
$states = ""; | |
$payload_size = 4; | |
if ($i < 5) { | |
$payload_size = 2; | |
} else if ($i == 8 || $i == 9) { | |
$payload_size = 16; | |
} else if ($i == 10) { | |
$payload_size = 8; | |
} | |
for ($dpos = 0; $dpos + 8 <= 8 * strlen($datum); | |
$dpos += 8 + $payload_size) { | |
$res = _eat_bits_le($datum, $dpos, 8); | |
if ($res == 0xFF) { | |
break; | |
} | |
if ($i < 7) { | |
for ($j = ($dpos == 0 ? 1 : 0); $j <= $res; $j++) { | |
$states .= dechex($state); | |
} | |
} | |
if ($dpos + 8 + $payload_size <= 8 * strlen($datum)) { | |
$state = _eat_bits_le($datum, $dpos + 8, $payload_size); | |
} | |
} | |
if ($i < 7) { | |
$inputs[] = $states; | |
} | |
} | |
$inputs_all[] = $inputs; | |
} | |
$entityFrameContainers = array(); | |
$entityFrameContainerCount = _unpack_le4($replay, $pos); $pos += 4; | |
if ($entityFrameContainerCount === null) { | |
return null; | |
} | |
for ($i = 0; $i < $entityFrameContainerCount; $i++) { | |
$entityFrameCount = _unpack_le4($replay, $pos + 8); | |
$entityFrameContainer = array( | |
'unk1' => _unpack_le4($replay, $pos), | |
'unk2' => _unpack_le4($replay, $pos + 4), | |
'entityFrames' => array(), | |
); | |
/* Ordered by unk1. */ | |
/* unk2 makes a permutation of [1, Count]. */ | |
$pos += 12; | |
for ($j = 0; $j < $entityFrameCount; $j++) { | |
$entityFrameContainer['entityFrames'][] = array( | |
'time' => _unpack_le4($replay, $pos), | |
'xpos' => _unpack_le4($replay, $pos + 4), | |
'ypos' => _unpack_le4($replay, $pos + 8), | |
'xspeed' => _unpack_le4($replay, $pos + 12), | |
'yspeed' => _unpack_le4($replay, $pos + 16) | |
); | |
$pos += 20; | |
} | |
$entityFrameContainers[] = $entityFrameContainer; | |
} | |
if ($pos != strlen($replay)) { | |
return null; | |
} | |
return array( | |
'header' => $header, | |
'inputs' => $inputs_all, | |
'entityFrameContainers' => $entityFrameContainers, | |
); | |
} | |
function build_replay($repmeta) { | |
$is_extended = index_array('extended', $repmeta['header'], false) || | |
$repmeta['header']['players'] > 1; | |
$num_inputs = $is_extended ? 8 : 7; | |
$input_data = ""; | |
foreach ($repmeta['inputs'] as $inputs) { | |
for ($i = 0; $i < $num_inputs; $i++) { | |
$input = $inputs[$i]; | |
$parts = array(); | |
for ($j = 0; $j < strlen($input); ) { | |
$sz = 1; | |
while ($sz < 255 && $j + $sz < strlen($input) && | |
$input[$j] == $input[$j + $sz]) { | |
$sz++; | |
} | |
$parts[] = array($sz, hexdec($input[$j])); | |
$j += $sz; | |
} | |
$payload_size = $i == 5 || $i == 6 ? 4 : 2; | |
$bits = 8 + (8 + $payload_size) * count($parts); | |
$input = ""; | |
while (strlen($input) * 8 < $bits) { | |
$input .= "\xff"; | |
} | |
$input .= "\xff\xff"; | |
_write_bits_le($input, 0, 8, 0); | |
$pos = 8; | |
foreach ($parts as $part) { | |
_write_bits_le($input, $pos, $payload_size, $part[1]); | |
$pos += $payload_size; | |
_write_bits_le($input, $pos, 8, $part[0] - 1); | |
$pos += 8; | |
} | |
$input_data .= _pack_le4(strlen($input)); | |
$input_data .= $input; | |
} | |
} | |
$data = _pack_le4(strlen($input_data)); | |
$data .= $input_data; | |
$data .= _pack_le4(count($repmeta['entityFrameContainers'])); | |
foreach ($repmeta['entityFrameContainers'] as $entityFrameContainer) { | |
$data .= _pack_le4($entityFrameContainer['unk1']); | |
$data .= _pack_le4($entityFrameContainer['unk2']); | |
$data .= _pack_le4(count($entityFrameContainer['entityFrames'])); | |
foreach ($entityFrameContainer['entityFrames'] as $entityFrame) { | |
$data .= _pack_le4($entityFrame['time']); | |
$data .= _pack_le4($entityFrame['xpos']); | |
$data .= _pack_le4($entityFrame['ypos']); | |
$data .= _pack_le4($entityFrame['xspeed']); | |
$data .= _pack_le4($entityFrame['yspeed']); | |
} | |
} | |
$replay = "DF_RPL"; | |
if ($is_extended) { | |
$replay .= "3"; | |
} else { | |
$replay .= "1"; | |
} | |
$replay .= _pack_le2($repmeta['header']['players']); | |
$replay .= _pack_le4(strlen($data)); | |
$replay .= _pack_le4($repmeta['header']['frames']); | |
foreach ($repmeta['header']['characters'] as $char) { | |
$replay .= _pack_byte($char); | |
} | |
$replay .= _pack_byte(strlen($repmeta['header']['level'])); | |
$replay .= $repmeta['header']['level']; | |
$replay .= gzcompress($data); | |
return $replay; | |
} | |
function _get_pos_edge($input, $from, $to) { | |
$result = 0; | |
for ($i = 0; $i + 1 < strlen($input); $i++) { | |
if ($input[$i] == $from && $input[$i + 1] == $to) { | |
++$result; | |
} | |
} | |
return $result; | |
} | |
function _is_press($ch) { | |
return ('1' <= $ch && $ch <= '9') || $ch == 'a'; | |
} | |
function _get_press_edge($input) { | |
$result = 0; | |
for ($i = 0; $i + 1 < strlen($input); $i++) { | |
$ch = $input[$i]; | |
if (!_is_press($input[$i]) && _is_press($input[$i + 1])) { | |
++$result; | |
} | |
} | |
return $result; | |
} | |
function get_jumps($repmeta) { | |
$total = 0; | |
foreach ($repmeta['inputs'] as $inputs) { | |
$total += _get_pos_edge($inputs[2], '0', '1'); | |
} | |
return $total; | |
} | |
function get_dashes($repmeta) { | |
$total = 0; | |
foreach ($repmeta['inputs'] as $inputs) { | |
$total += _get_pos_edge($inputs[3], '0', '1'); | |
} | |
return $total; | |
} | |
function get_downdashes($repmeta) { | |
$total = 0; | |
foreach ($repmeta['inputs'] as $inputs) { | |
$total += _get_pos_edge($inputs[4], '0', '1'); | |
} | |
return $total; | |
} | |
function get_all_dashes($repmeta) { | |
$res = 0; | |
foreach ($repmeta['inputs'] as $inputs) { | |
$in3 = $inputs[3]; | |
$in4 = $inputs[4]; | |
for ($i = 0; $i < strlen($in3) || $i < strlen($in4); $i++) { | |
if (($i < strlen($in3) && $in3[$i] != '0') || | |
($i < strlen($in4) && $in4[$i] != '0')) { | |
$res++; | |
} | |
} | |
} | |
return $res; | |
} | |
function get_lights($repmeta) { | |
$total = 0; | |
foreach ($repmeta['inputs'] as $inputs) { | |
$total += _get_press_edge($inputs[5]); | |
} | |
return $total; | |
} | |
function get_heavies($repmeta) { | |
$total = 0; | |
foreach ($repmeta['inputs'] as $inputs) { | |
$total += _get_press_edge($inputs[6]); | |
} | |
return $total; | |
} | |
function could_super($repmeta) { | |
foreach ($repmeta['inputs'] as $inputs) { | |
$in5 = $inputs[5]; | |
$in6 = $inputs[6]; | |
for ($i = 0; $i < strlen($in5) && $i < strlen($in6); $i++) { | |
if ($in5[$i] != '0' && $in6[$i] != '0') { | |
return true; | |
} | |
} | |
} | |
return false; | |
} | |
function count_directions($repmeta) { | |
$result = 0; | |
foreach ($repmeta['inputs'] as $inputs) { | |
for ($i = 0; $i < 2; $i++) { | |
$in = $inputs[$i]; | |
for ($j = 55; $j < strlen($in); $j++) { | |
if ($in[$j] != '1' && $in[$j] != $in[$j - 1]) { | |
$result++; | |
} | |
} | |
} | |
} | |
return $result; | |
} | |
function count_actions($repmeta) { | |
return count_directions($repmeta) + | |
get_all_dashes($repmeta) + | |
get_jumps($repmeta) + | |
get_lights($repmeta) + | |
get_heavies($repmeta); | |
} | |
?> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment