Skip to content

Instantly share code, notes, and snippets.

@msg555
Created February 13, 2021 21:13
Show Gist options
  • Save msg555/71f3aae0f2aa742ef33d200dec491356 to your computer and use it in GitHub Desktop.
Save msg555/71f3aae0f2aa742ef33d200dec491356 to your computer and use it in GitHub Desktop.
Replay read/write routines for dustkid
<?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