Skip to content

Instantly share code, notes, and snippets.

@esterTion
Last active November 17, 2021 06:19
Show Gist options
  • Save esterTion/e24d7142e0fa0bcc6ef11ee586ce5235 to your computer and use it in GitHub Desktop.
Save esterTion/e24d7142e0fa0bcc6ef11ee586ce5235 to your computer and use it in GitHub Desktop.
Some sort of spine skeleton binary data to json converter
<?php
/*
This converter is not finished, and will not be finished.
If you are interested in previewing, use this modified WebGL runtime lib
https://github.com/esterTion/spine-runtimes/tree/3.6/spine-ts
Change the SkeletonJson class to SkeletonBinary to load binary skel data
Usage demo: https://redive.estertion.win/spine/
*/
/*
Some sort of skel binary to json converter
a few fields are not processed, and will throw error when encountered
Used for converting resource from priconne-redive
*/
if (count(get_included_files()) == 1) define ('TEST_SUITE', __FILE__);
abstract class Stream {
abstract protected function read($length);
abstract public function seek($position);
abstract public function position();
abstract protected function getPos();
abstract protected function setPos($pos);
public function __get($name) {
switch($name) {
case 'position': return $this->getPos();
case 'bool': return $this->readBoolean();
case 'byte': return $this->readData(1);
case 'short': return $this->readInt16();
case 'ushort': return $this->readUint16();
case 'long': return $this->readInt32();
case 'ulong': return $this->readUint32();
case 'longlong': return $this->readInt64();
case 'ulonglong': return $this->readUint64();
case 'float': return $this->readFloat();
case 'double': return $this->readDouble();
case 'string': return $this->readStringToNull();
case 'line': return $this->readStringToReturn();
default: throw new Exception("Access undefined field ${name} of class ".get_class($this));
}
}
public function __set($name, $val) {
switch($name) {
case 'position': return $this->setPos($val);
default: throw new Exception("Assign value to undefined field ${name} of class ".get_class($this));
}
}
public $littleEndian = false;
public $size;
public function readStringToNull() {
$s = '';
while (ord($char = $this->read(1)) != 0) {
$s .= $char;
}
return $s;
}
public function readStringAt($pos) {
$current = $this->position;
$this->position = $pos;
$data = $this->string;
$this->position = $current;
return $data;
}
public function readStringToReturn() {
$s = '';
while ($this->position < $this->size && ($char = $this->read(1)) != "\n") {
$s .= $char;
}
return trim($s,"\r");
}
public function readBoolean() {
return ord($this->byte)>0;
}
public function readInt16() {
$uint = $this->readUint16();
$sint = unpack('s', pack('S', $uint))[1];
return $sint;
}
public function readUint16() {
$int = $this->read(2);
if (strlen($int) != 2) return 0;
return unpack($this->littleEndian?'v':'n', $int)[1];
}
public function readInt32() {
$uint = $this->readUint32();
$sint = unpack('l', pack('L', $uint))[1];
return $sint;
}
public function readUint32() {
$int = $this->read(4);
if (strlen($int) != 4) return 0;
return unpack($this->littleEndian?'V':'N', $int)[1];
}
public function readInt64() {
$uint = $this->readUint64();
$sint = unpack('q', pack('Q', $uint))[1];
return $sint;
}
public function readUint64() {
$int = $this->read(8);
if (strlen($int) != 8) return 0;
return unpack($this->littleEndian?'P':'J', $int)[1];
}
public function readFloat() {
$int = $this->read(4);
if (strlen($int) != 4) return 0;
if (!$this->littleEndian) $int = $int[3].$int[2].$int[1].$int[0];
return unpack(/*$this->littleEndian?'g':'G'*/ 'f', $int)[1];
}
public function readDouble() {
$int = $this->read(8);
if (strlen($int) != 8) return 0;
if (!$this->littleEndian) $int = $int[7].$int[6].$int[5].$int[4].$int[3].$int[2].$int[1].$int[0];
return unpack(/*$this->littleEndian?'e':'E'*/ 'd', $int)[1];
}
public function readData($size) {
return $this->read($size);
}
public function readDataAt($pos, $size) {
$current = $this->position;
$this->position = $pos;
$data = $this->readData($size);
$this->position = $current;
return $data;
}
public function alignStream($alignment) {
$mod = $this->position % $alignment;
if ($mod != 0) {
$this->position += $alignment - $mod;
}
}
public function readAlignedString($len) {
$string = $this->readData($len);
$this->alignStream(4);
return $string;
}
}
class FileStream extends Stream {
private $f;
function __construct($file) {
$this->f = fopen($file, 'rb+');
if ($this->f === false) {
throw 'Unable to open file';
}
$this->size = filesize($file);
}
function __destruct() {
fclose($this->f);
}
protected function read($length) {
return fread($this->f, $length);
}
public function write($newData) {
fwrite($this->f, $newData);
$this->size += strlen($newData);
}
public function seek($position) {
fseek($this->f, $position);
}
public function position() {
return ftell($this->f);
}
protected function getPos() {
return ftell($this->f);
}
protected function setPos($pos) {
fseek($this->f, $pos);
}
}
function readVarInt32($pb, $optimizePositive = true) {
$b=0;
$result=0;
do{
$b=ord($pb->readData(1));
$result = $b & 0x7f;
if(!( $b & 0x80 ))
break;
$b=ord($pb->readData(1));
$result |= ($b & 0x7f)<<7;
if(!( $b & 0x80 ))
break;
$b=ord($pb->readData(1));
$result |= ($b & 0x7f)<<14;
if(!( $b & 0x80 ))
break;
$b=ord($pb->readData(1));
$result |= ($b & 0x7f)<<21;
if(!( $b & 0x80 ))
break;
$b=ord($pb->readData(1));
$result |= ($b & 0x7f)<<28;
if(!( $b & 0x80 ))
break;
for($i=0;$i<5;$i++){
$b=ord($pb->readData(1));
if(!( $b & 0x80 ))
break;
}
}while(false);
if (!$optimizePositive) $result = (($result >> 1) ^ -($result & 1));
if ($result > 0) $result = unpack('l', pack('L', $result))[1];
return $result;
}
function readString($s) {
$len = readVarInt32($s);
if ($len == 0) return NULL;
if ($len == 1) return '';
return $s->readData($len - 1);
}
function readColor($s) {
return strtoupper(bin2hex($s->readData(4)));
/*return [
ord($s->byte),
ord($s->byte),
ord($s->byte),
ord($s->byte)
];*/
}
function readCurve($fs) {
$type = ord($fs->byte);
if ($type == CURVE_BEZIER) {
return [
'type' => $type,
'c1' => $fs->float,
'c2' => $fs->float,
'c3' => $fs->float,
'c4' => $fs->float
];
} else {
return [
'type' => $type
];
}
}
function readSkin($fs, $name, &$skel) {
$skin = [];
$skin['name'] = $name;
$slotCount = readVarInt32($fs);
for ($i=0; $i<$slotCount; $i++) {
$slot = [];
$slot['index'] = readVarInt32($fs);
$attachmentCount = readVarInt32($fs);
$slot['attachment'] = [];
for ($j=0; $j<$attachmentCount; $j++) {
$attachment = [];
$attachment['placeholderName'] = readString($fs);
$attachment['name'] = readString($fs);
if ($attachment['name'] === NULL) $attachment['name'] = $attachment['placeholderName'];
$attachment['type'] = ord($fs->byte);
switch($attachment['type']) {
case ATTACHMENT_REGION: {
$attachment['path'] = readString($fs);
$attachment['rotation'] = $fs->float;
$attachment['x'] = $fs->float;
$attachment['y'] = $fs->float;
$attachment['scaleX'] = $fs->float;
$attachment['scaleY'] = $fs->float;
$attachment['width'] = $fs->float;
$attachment['height'] = $fs->float;
$attachment['color'] = readColor($fs);
break;
}
case ATTACHMENT_BOUNDING_BOX: {
$vertexCount = readVarInt32($fs);
$attachment['vertexCount'] = $vertexCount;
list($attachment['vertices'], $attachment['weighted']) = readVertex($fs, $vertexCount, true);
if ($skel['nonessential']) $attachment['color'] = readColor($fs);
break;
}
case ATTACHMENT_MESH: {
$attachment['path'] = readString($fs);
$attachment['color'] = readColor($fs);
$uvCount = readVarInt32($fs);
$attachment['uvs'] = [];
for ($k=0; $k<$uvCount; $k++) {
$attachment['uvs'][] = $fs->float;
$attachment['uvs'][] = $fs->float;
}
$triangleCount = readVarInt32($fs);
$attachment['triangles'] = [];
for ($k=0; $k<$triangleCount; $k++) {
$attachment['triangles'][] = $fs->short;
}
list($attachment['vertices'], $attachment['weighted']) = readVertex($fs, $uvCount, true);
$attachment['hull'] = readVarInt32($fs);
if ($skel['nonessential']) {
$edgeCount = readVarInt32($fs);
$attachment['edges'] = [];
for ($k=0; $k<$edgeCount; $k++) {
$attachment['edges'][] = $fs->short;
}
$attachment['width'] = $fs->float;
$attachment['height'] = $fs->float;
}
break;
}
case ATTACHMENT_LINKED_MESH: {
$attachment['path'] = readString($fs);
$attachment['color'] = readColor($fs);
$attachment['skin'] = readString($fs);
$attachment['parent'] = readString($fs);
$attachment['deform'] = $fs->bool;
if ($skel['nonessential']) {
$attachment['width'] = $fs->float;
$attachment['height'] = $fs->float;
}
break;
}
case ATTACHMENT_PATH: {
$attachment['closed'] = $fs->bool;
$attachment['constantSpeed'] = $fs->bool;
$vertexCount = readVarInt32($fs);
list($attachment['vertices'], $attachment['weighted']) = readVertex($fs, $vertexCount, true);
$attachment['lengths'] = [];
for ($k=0; $k<$vertexCount/3; $k++) {
$attachment['lengths'][] = $fs->float;
}
if ($skel['nonessential']) $attachment['color'] = readColor($fs);
break;
}
case ATTACHMENT_POINT: {
$attachment['rotation'] = $fs->float;
$attachment['x'] = $fs->float;
$attachment['y'] = $fs->float;
if ($skel['nonessential']) $attachment['color'] = readColor($fs);
break;
}
case ATTACHMENT_CLIPPING: {
$attachment['endSlot'] = readVarInt32($fs);
$vertexCount = readVarInt32($fs);
$attachment['vertexCount'] = $vertexCount;
list($attachment['vertices'], $attachment['weighted']) = readVertex($fs, $vertexCount, true);
if ($skel['nonessential']) $attachment['color'] = readColor($fs);
break;
}
}
$slot['attachment'][] = $attachment;
}
$skin[] = $slot;
}
return $skin;
}
function readVertex($fs, $count, $isSkin = false) {
$weighted = $fs->bool;
$data = [];
for ($i=0; $i<$count; $i++) {
if (!$weighted) {
$data[] = $fs->float;
$data[] = $fs->float;
} else {
$boneCount = readVarInt32($fs);
$isSkin && ($data[] = $boneCount);
for ($j=0, $bones=[]; $j<$boneCount; $j++) {
$index = readVarInt32($fs);
$x = $fs->float;
$y = $fs->float;
$weight = $fs->float;
if ($isSkin) {
$data[] = $index;
$data[] = $x;
$data[] = $y;
$data[] = $weight;
} else {
$data[] = $weight;
$data[] = $index;
$data[] = $x;
$data[] = $y;
}
}
}
}
return [$data,$weighted];
}
function readBone($fs, $nonessential, $isFirst) {
$bone = [];
$bone['name'] = readString($fs);
if ($isFirst) $bone['parent'] = readVarInt32($fs);
$bone['rotation'] = $fs->float;
$bone['x'] = $fs->float;
$bone['y'] = $fs->float;
$bone['scaleX'] = $fs->float;
$bone['scaleY'] = $fs->float;
$bone['shearX'] = $fs->float;
$bone['shearY'] = $fs->float;
$bone['length'] = $fs->float;
$bone['transformMode'] = ord($fs->byte);
if ($nonessential) $bone['color'] = readColor($fs);
return $bone;
}
function readSlot($fs) {
$slot = [];
$slot['name'] = readString($fs);
$slot['bone'] = readVarInt32($fs);
$slot['color'] = bin2hex($fs->readData(4));
if ($slot['color'] == 'ffffffff') unset($slot['color']);
$slot['dark'] = bin2hex($fs->readData(4));
if ($slot['dark'] == 'ffffffff') unset($slot['dark']);
$slot['attachment'] = readString($fs);
$slot['blend'] = ord($fs->byte);
return $slot;
}
function GetSkinAttachment(&$skin, $slotIndex, $attachmentName) {
$slot = &$skin[$slotIndex];
for ($i=0, $n=count($slot['attachment']); $i<$n; $i++) {
if ($slot['attachment'][$i]['placeholderName'] == $attachmentName) return $slot['attachment'][$i];
}
return NULL;
}
function readAnimation($fs, &$skel) {
$animation = [];
$animation['name'] = readString($fs);
$slotCount = readVarInt32($fs);
$animation['slot'] = [];
for ($j=0; $j<$slotCount; $j++) {
$slot = [];
$slot['index'] = readVarInt32($fs);
$timelineCount = readVarInt32($fs);
$slot['timelineCount'] = $timelineCount;
$slot['timeline'] = [];
for ($k=0; $k<$timelineCount; $k++) {
$timeline = [];
$timeline['type'] = ord($fs->byte);
$frameCount = readVarInt32($fs);
$timeline['frame'] = [];
for ($l=0; $l<$frameCount; $l++) {
switch ($timeline['type']) {
case SLOT_ATTACHMENT: {
$frame = [
'time' => $fs->float,
'name' => readString($fs)
];
$timeline['frame'][] = $frame;
break;
}
case SLOT_COLOR: {
$frame = [
'time' => $fs->float,
'color' => readColor($fs)
];
if ($l < $frameCount - 1) $frame['curve'] = readCurve($fs);
$timeline['frame'][] = $frame;
break;
}
case SLOT_TWO_COLOR: {
$frame = [
'time' => $fs->float,
'light' => readColor($fs),
'dark' => readColor($fs)
];
if ($l < $frameCount - 1) $frame['curve'] = readCurve($fs);
$timeline['frame'][] = $frame;
break;
}
}
}
$slot['timeline'][] = $timeline;
}
$animation['slot'][] = $slot;
}
$boneCount = readVarInt32($fs);
$animation['bone'] = [];
for ($j=0; $j<$boneCount; $j++) {
$bone = [];
$bone['index'] = readVarInt32($fs);
$timelineCount = readVarInt32($fs);
$bone['timelineCount'] = $timelineCount;
$bone['timeline'] = [];
for ($k=0; $k<$timelineCount; $k++) {
$timeline = [];
$timeline['type'] = ord($fs->byte);
$frameCount = readVarInt32($fs);
$timeline['frame'] = [];
for ($l=0; $l<$frameCount; $l++) {
switch ($timeline['type']) {
case BONE_ROTATE: {
$frame = [
'time' => $fs->float,
'rotation' => $fs->float
];
if ($l < $frameCount - 1) $frame['curve'] = readCurve($fs);
$timeline['frame'][] = $frame;
break;
}
case BONE_TRANSLATE: case BONE_SCALE: case BONE_SHEAR: {
$frame = [
'time' => $fs->float,
'x' => $fs->float,
'y' => $fs->float
];
if ($l < $frameCount - 1) $frame['curve'] = readCurve($fs);
$timeline['frame'][] = $frame;
break;
}
}
}
$bone['timeline'][] = $timeline;
}
$animation['bone'][] = $bone;
}
$ikCount = readVarInt32($fs);
if ($ikCount) {
$animation['ik'] = [];
for ($i=0; $i<$ikCount; $i++) {
$ik = [];
$ik['index'] = readVarInt32($fs);
$ik['frame'] = [];
for ($ii=0, $nn=readVarInt32($fs); $ii<$nn; $ii++) {
$frame = [
'time' => $fs->float,
'mix' => $fs->float,
'bendPositive' => $fs->bool
];
if ($ii < $nn - 1) $frame['curve'] = readCurve($fs);
$ik['frame'][] = $frame;
}
$animation['ik'][] = $ik;
}
}
$transformCount = readVarInt32($fs);
if ($transformCount) {
$animation['transform'] = [];
for ($i=0; $i<$transformCount; $i++) {
$transform = [];
$transform['index'] = readVarInt32($fs);
$transform['frame'] = [];
for ($ii=0, $nn=readVarInt32($fs); $ii<$nn; $ii++) {
$frame = [
'time' => $fs->float,
'rotateMix' => $fs->float,
'translateMix' => $fs->float,
'scaleMix' => $fs->float,
'shearMix' => $fs->float
];
if ($l < $frameCount - 1) $frame['curve'] = readCurve($fs);
$transform['frame'][] = $frame;
}
$animation['transform'][] = $transform;
}
}
$pathCount = readVarInt32($fs);
if ($pathCount) {
throw new Exception('has path timeline data');
}
$deformCount = readVarInt32($fs);
if ($deformCount) {
$animation['deform'] = [];
for ($i=0; $i<$deformCount; $i++) {
$deform=[];
$deform['skin'] = readVarInt32($fs);
$skin = &$skel['skin'][$deform['skin']];
$deform['slots'] = [];
for ($ii=0, $nn=readVarInt32($fs); $ii<$nn; $ii++) {
$slot = [];
$slot['index'] = readVarInt32($fs);
$slot['mesh'] = [];
for ($iii=0, $nnn=readVarInt32($fs); $iii<$nnn; $iii++) {
$mesh = [];
$mesh['attachment'] = readString($fs);
$attachment = GetSkinAttachment($skin, $slot['index'], $mesh['attachment']);
$weighted = $attachment['weighted'];
$vertices = $attachment['vertices'];
$deformLength = $weighted ? count($vertices) /3*2 : count($vertices);
$frameCount = readVarInt32($fs);
$mesh['frame'] = [];
for ($iiii=0; $iiii<$frameCount; $iiii++) {
$frame = [
'time' => $fs->float,
'end' => readVarInt32($fs)
];
if ($frame['end'] == 0) {
$frame['vertices'] = $weighted ? array_fill(0, $deformLength, 0) : $vertices;
} else {
$deforms = array_fill(0, $deformLength, 0);
$start = readVarInt32($fs);
$end = $frame['end'];
$end += $start;
// scale???
for ($v=$start; $v<$end; $v++) {
$deforms[$v] = $fs->float;
}
if (!$weighted) {
for ($v=0; $v<$deformLength; $v++) {
$deforms[$v] += $vertices[$v];
}
}
$frame['vertices'] = $deforms;
}
$v = count($frame['vertices']) - 1;
while ($v>=0 && $frame['vertices'][$v] == 0) {
unset($frame['vertices'][$v--]);
}
if ($iiii < $frameCount - 1) $frame['curve'] = readCurve($fs);
$mesh['frame'][] = $frame;
}
$slot['mesh'][] = $mesh;
}
$deform['slots'][] = $slot;
}
$animation['deform'][] = $deform;
}
}
$drawOrderCount = readVarInt32($fs);
if ($drawOrderCount) {
$animation['drawOrder'] = [];
for ($j=0; $j<$drawOrderCount; $j++) {
$drawOrder = [
'time' => $fs->float,
'change' => []
];
$changeCount = readVarInt32($fs);
for ($k=0; $k<$changeCount; $k++) {
$drawOrder['change'][] = [
'index' => readVarInt32($fs),
'amount' => readVarInt32($fs)
];
}
$animation['drawOrder'][] = $drawOrder;
}
}
$eventCount = readVarInt32($fs);
if ($eventCount) {
$animation['event'] = [];
for ($j=0; $j<$eventCount; $j++) {
$event = [
'time' => $fs->float,
'index' => readVarInt32($fs),
'int' => readVarInt32($fs, false),
'float' => $fs->float
];
$hasString = $fs->bool;
if ($hasString) $event['string'] = readString($fs);
$animation['event'][] = $event;
}
}
return $animation;
}
{
// define constant
define('ATTACHMENT_REGION', 0);
define('ATTACHMENT_BOUNDING_BOX', 1);
define('ATTACHMENT_MESH', 2);
define('ATTACHMENT_LINKED_MESH', 3);
define('ATTACHMENT_PATH', 4);
define('ATTACHMENT_POINT', 5);
define('ATTACHMENT_CLIPPING', 6);
define('BLEND_MODE_NORMAL', 0);
define('BLEND_MODE_ADDITIVE', 1);
define('BLEND_MODE_MULTIPLY', 2);
define('BLEND_MODE_SCREEN', 3);
define('CURVE_LINEAR', 0);
define('CURVE_STEPPED', 1);
define('CURVE_BEZIER', 2);
define('BONE_ROTATE', 0);
define('BONE_TRANSLATE', 1);
define('BONE_SCALE', 2);
define('BONE_SHEAR', 3);
define('TRANSFORM_NORMAL', 0);
define('TRANSFORM_ONLY_TRANSLATION', 1);
define('TRANSFORM_NO_ROTATION_OR_REFLECTION', 2);
define('TRANSFORM_NO_SCALE', 3);
define('TRANSFORM_NO_SCALE_OR_REFLECTION', 4);
define('SLOT_ATTACHMENT', 0);
define('SLOT_COLOR', 1);
define('SLOT_TWO_COLOR', 2);
define('PATH_POSITION', 0);
define('PATH_SPACING', 1);
define('PATH_MIX', 2);
define('PATH_POSITION_FIXED', 0);
define('PATH_POSITION_PERCENT', 1);
define('PATH_SPACING_LENGTH', 0);
define('PATH_SPACING_FIXED', 1);
define('PATH_SPACING_PERCENT', 2);
define('PATH_ROTATE_TANGENT', 0);
define('PATH_ROTATE_CHAIN', 1);
define('PATH_ROTATE_CHAIN_SCALE', 2);
}
function exportSkel($file) {
$fs = new FileStream($file);
$out = pathinfo($file, PATHINFO_DIRNAME).'/'.pathinfo($file, PATHINFO_FILENAME).'.json';
// read binary. some not implemented
$skel = ['skeleton'=>[]];
$skel['skeleton']['hash'] = readString($fs);
$skel['skeleton']['version'] = readString($fs);
$skel['skeleton']['width'] = $fs->float;
$skel['skeleton']['height'] = $fs->float;
$skel['nonessential'] = $fs->bool;
$nonessential = $skel['nonessential'];
if ($nonessential) {
$skel['skeleton']['fps'] = $fs->float;
$skel['skeleton']['images'] = readString($fs);
}
$boneCount = readVarInt32($fs);
if ($boneCount) {
$skel['bone'] = [];
for ($i=0; $i<$boneCount; $i++) {
$skel['bone'][] = readBone($fs, $nonessential, $i!=0);
}
}
$slotCount = readVarInt32($fs);
if ($slotCount) {
$skel['slot'] = [];
for ($i=0; $i<$slotCount; $i++) {
$skel['slot'][] = readSlot($fs);
}
}
$ikCount = readVarInt32($fs);
if ($ikCount) {
$skel['ik'] = [];
for ($i=0; $i<$ikCount; $i++) {
$ik = [];
$ik['name'] = readString($fs);
$ik['order'] = readVarInt32($fs);
$boneCount = readVarInt32($fs);
$ik['bone'] = [];
for ($ii=0; $ii<$boneCount; $ii++) {
$ik['bone'][] = readVarInt32($fs);
}
$ik['target'] = readVarInt32($fs);
$ik['mix'] = $fs->float;
$ik['bendPositive'] = $fs->bool;
$skel['ik'][] = $ik;
}
}
$transformCount = readVarInt32($fs);
if ($transformCount) {
$skel['transform'] = [];
for ($i=0; $i<$transformCount; $i++) {
$next = readString($fs);
$next = readVarInt32($fs);
$len = readVarInt32($fs);
for ($k=0; $k<$len; $k++) $next = readVarInt32($fs);
$next = readVarInt32($fs);
$next = $fs->bool;
$next = $fs->bool;
$next = $fs->float;
$next = $fs->float;
$next = $fs->float;
$next = $fs->float;
$next = $fs->float;
$next = $fs->float;
$next = $fs->float;
$next = $fs->float;
$next = $fs->float;
$next = $fs->float;
}
throw new Exception('has transform data');
}
$pathCount = readVarInt32($fs);
if ($pathCount) {
$skel['path'] = [];
throw new Exception('has path data');
}
$skel['skin'] = [
readSkin($fs, 'default', $skel)
];
$skinCount = readVarInt32($fs);
for ($i=0; $i<$skinCount; $i++) {
$name = readString($fs);
$skel['skin'][] = readSkin($fs, $name, $skel);
}
$eventCount = readVarInt32($fs);
if ($eventCount) {
$skel['event'] = [];
for ($i=0; $i<$eventCount; $i++) {
$skel['event'][] = [
'name' => readString($fs),
'int' => readVarInt32($fs, false),
'float' => $fs->float,
'string' => readString($fs)
];
}
}
$animationCount = readVarInt32($fs);
if ($animationCount) {
$skel['animation'] = [];
for ($i=0; $i<$animationCount; $i++) {
$skel['animation'][] = readAnimation($fs, $skel);
}
}
$json = processToJson($skel);
$i=0;
foreach ($json['skins']['default'] as $k=>&$v) {
//echo $k."\n";
//if (++$i<35)unset($json['skins']['default'][$k]);
//else if (in_array($k, ['hair_1', 'hair_3']))unset($json['animations'][$k]);
}
//unset($json['animations']);
//unset($json['skins']);
file_put_contents($out, json_encode($json));
}
function processToJson($skel) {
// process to json
$json = [
'skeleton' => $skel['skeleton']
];
if (isset($skel['bone'])) {
$json['bones'] = array_map(function ($i) use(&$json) {
// unset default 0 prop
foreach (['x','y','shearX','shearY','length'] as $prop) {
if ($i[$prop] == 0) unset($i[$prop]);
}
// unset default 1 prop
foreach (['scaleX','scaleY'] as $prop) {
if ($i[$prop] == 1) unset($i[$prop]);
}
if (isset($i['transformMode'])) {
$i['transform'] = [
TRANSFORM_NORMAL => 'normal',
TRANSFORM_ONLY_TRANSLATION => 'onlyTranslation',
TRANSFORM_NO_ROTATION_OR_REFLECTION => 'noRotationOrReflection',
TRANSFORM_NO_SCALE => 'noScale',
TRANSFORM_NO_SCALE_OR_REFLECTION => 'noScaleOrReflection'
][$i['transformMode']];
unset($i['transformMode']);
if ($i['transform'] == 'normal') unset($i['transform']);
}
return $i;
}, $skel['bone']);
foreach ($json['bones'] as &$i) {
if (isset($i['parent'])) {
$i['parent'] = $json['bones'][ $i['parent'] ]['name'];
}
}
}
if (isset($skel['slot'])) {
$json['slots'] = array_map(function ($i) use(&$json) {
$i['bone'] = $json['bones'][$i['bone']]['name'];
if ($i['attachment'] === NULL) unset($i['attachment']);
$i['blend'] = [
BLEND_MODE_NORMAL => 'normal',
BLEND_MODE_ADDITIVE => 'additive',
BLEND_MODE_MULTIPLY => 'multiply',
BLEND_MODE_SCREEN => 'screen'
][$i['blend']];
if ($i['blend'] == 'normal') unset($i['blend']);
return $i;
}, $skel['slot']);
}
if (isset($skel['ik'])) {
$json['ik'] = array_map(function ($ik) use(&$json) {
$ik = [
'name' => $ik['name'],
'order' => $ik['order'],
'bones' => array_map(function ($i)use(&$json) { return $json['bones'][ $i ]['name']; }, $ik['bone']),
'target' => $json['bones'][ $ik['target'] ]['name'],
'mix' => $ik['mix'],
'bendPositive' => $ik['bendPositive']
];
if ($ik['mix'] == 1) unset($ik['mix']);
if ($ik['bendPositive'] === false) unset($ik['bendPositive']);
return $ik;
}, $skel['ik']);
}
// transform path
if (isset($skel['skin'])) {
$json['skins'] = [];
foreach ($skel['skin'] as $skin) {
$procSkin = [];
$skinname = $skin['name'];
unset($skin['name']);
foreach ($skin as $slot) {
$procSlot = [];
foreach ($slot['attachment'] as $attachment) {
// unset default 0 prop
foreach (['x','y','rotation'] as $prop) {
if (isset($attachment[$prop]) && $attachment[$prop] == 0) unset($attachment[$prop]);
}
// unset default 1 prop
foreach (['scaleX','scaleY'] as $prop) {
if (isset($attachment[$prop]) && $attachment[$prop] == 1) unset($attachment[$prop]);
}
$attachment['type'] = [
ATTACHMENT_REGION => 'region',
ATTACHMENT_BOUNDING_BOX => 'boundingbox',
ATTACHMENT_MESH => 'mesh',
ATTACHMENT_LINKED_MESH => 'linkedmesh',
ATTACHMENT_PATH => 'path',
ATTACHMENT_POINT => 'point',
ATTACHMENT_CLIPPING => 'clipping'
][$attachment['type']];
if (in_array($attachment['type'], ['region','mesh','linkedmesh']) && $attachment['path'] === null) unset($attachment['path']);
if (isset($attachment['color']) && $attachment['color'] === 'FFFFFFFF') unset($attachment['color']);
if ($attachment['type'] == 'clipping') {
$attachment['end'] = $json['slots'][ $attachment['endSlot'] ]['name'];
unset($attachment['endSlot']);
}
$procSlot[ $attachment['placeholderName'] ] = $attachment;
}
$procSkin[ $json['slots'][ $slot['index'] ]['name'] ] = $procSlot;
}
$json['skins'][$skinname] = $procSkin;
}
}
// event
if (isset($skel['animation'])) {
$json['animations'] = [];
foreach ($skel['animation'] as $animname=>$anim) {
$procAnim = [];
if (isset($anim['bone'])) {
$procAnim['bones'] = [];
foreach ($anim['bone'] as $bone) {
$procBone = [];
foreach ($bone['timeline'] as $timeline) {
$procFrame = [];
$frameType = [
BONE_ROTATE => 'rotate',
BONE_TRANSLATE => 'translate',
BONE_SCALE => 'scale',
BONE_SHEAR => 'shear'
][$timeline['type']];
foreach ($timeline['frame'] as $frame) {
if (isset($frame['rotation'])) {
$frame['angle'] = $frame['rotation'];
unset($frame['rotation']);
}
if (isset($frame['curve'])) {
if ($frame['curve']['type'] == CURVE_BEZIER) {
$frame['curve'] = [
$frame['curve']['c1'],
$frame['curve']['c2'],
$frame['curve']['c3'],
$frame['curve']['c4']
];
} else if ($frame['curve']['type'] == CURVE_STEPPED) {
$frame['curve'] = 'stepped';
} else {
unset($frame['curve']);
}
}
$procFrame[] = $frame;
}
$procBone[$frameType] = $procFrame;
}
$procAnim['bones'][ $json['bones'][ $bone['index'] ]['name'] ] = $procBone;
}
}
if (isset($anim['slot'])) {
$procAnim['slots'] = [];
foreach ($anim['slot'] as $slot) {
$procSlot = [];
foreach ($slot['timeline'] as $timeline) {
$procFrame = [];
$frameType = [
SLOT_ATTACHMENT => 'attachment',
SLOT_COLOR => 'color',
SLOT_TWO_COLOR => 'two_color'
][$timeline['type']];
foreach ($timeline['frame'] as $frame) {
if (isset($frame['rotation'])) {
$frame['angle'] = $frame['rotation'];
unset($frame['rotation']);
}
if (isset($frame['curve'])) {
if ($frame['curve']['type'] == CURVE_BEZIER) {
$frame['curve'] = [
$frame['curve']['c1'],
$frame['curve']['c2'],
$frame['curve']['c3'],
$frame['curve']['c4']
];
} else if ($frame['curve']['type'] == CURVE_STEPPED) {
$frame['curve'] = 'stepped';
} else {
unset($frame['curve']);
}
}
$procFrame[] = $frame;
}
$procSlot[$frameType] = $procFrame;
}
$procAnim['slots'][ $json['slots'][ $slot['index'] ]['name'] ] = $procSlot;
}
}
if (isset($anim['ik'])) {
$procAnim['ik'] = [];
foreach ($anim['ik'] as $ik) {
$procAnim['ik'][ $skel['ik'][ $ik['index'] ]['name'] ] = array_map(function ($f) {
if ($f['mix'] == 1) unset($f['mix']);
if ($f['bendPositive'] === false) unset($f['bendPositive']);
if (isset($f['curve'])) {
if ($f['curve']['type'] == CURVE_BEZIER) {
$f['curve'] = [
$f['curve']['c1'],
$f['curve']['c2'],
$f['curve']['c3'],
$f['curve']['c4']
];
} else if ($f['curve']['type'] == CURVE_STEPPED) {
$f['curve'] = 'stepped';
} else {
unset($f['curve']);
}
}
return $f;
}, $ik['frame']);
}
}
if (isset($anim['transform'])) {
/*$procAnim['transform'] = [];
foreach ($anim['transform'] as $transform) {
$procAnim['transform'][ $skel['transform'][ $transform['index'] ]['name'] ] = array_map(function ($f) {
if ($f['rotateMix'] == 1) unset($f['rotateMix']);
if ($f['translateMix'] == 1) unset($f['translateMix']);
if ($f['scaleMix'] == 1) unset($f['scaleMix']);
if ($f['shearMix'] == 1) unset($f['shearMix']);
return $f;
}, $transform['frame']);
}*/
}
if (isset($anim['deform'])) {
$procAnim['deform'] = [];
foreach ($anim['deform'] as $skin) {
$procSkin = [];
foreach ($skin['slots'] as $slot) {
$procSlot = [];
foreach ($slot['mesh'] as $mesh) {
$procMesh = [];
foreach ($mesh['frame'] as $frame) {
unset($frame['end']);
if (isset($frame['curve'])) {
if ($frame['curve']['type'] == CURVE_BEZIER) {
$frame['curve'] = [
$frame['curve']['c1'],
$frame['curve']['c2'],
$frame['curve']['c3'],
$frame['curve']['c4']
];
} else if ($frame['curve']['type'] == CURVE_STEPPED) {
$frame['curve'] = 'stepped';
} else {
unset($frame['curve']);
}
}
$procMesh[] = $frame;
}
$procSlot[$mesh['attachment']] = $procMesh;
}
$procSkin[ $json['slots'][ $slot['index'] ]['name'] ] = $procSlot;
}
$procAnim['deform'][ $skel['skin'][ $skin['skin'] ]['name'] ] = $procSkin;
}
}
// event
if (isset($anim['drawOrder'])) {
$procAnim['draworder'] = [];
foreach ($anim['drawOrder'] as $drawOrder) {
$procdraw = [];
$procdraw['time'] = $drawOrder['time'];
$procdraw['offsets'] = array_map(function ($i) use (&$json) {
return [
'slot' => $json['slots'][ $i['index'] ]['name'],
'offset' => $i['amount']
];
}, $drawOrder['change']);
$procAnim['draworder'][] = $procdraw;
}
}
$json['animations'][ $anim['name'] ] = $procAnim;
}
}
return $json;
}
function readCyspSkeleton($skelFile) {
$fs = new FileStream($skelFile);
// check file format
$fs->littleEndian = true;
if (!(
$fs->readData(4) == 'cysp' &&
$fs->long === 0 &&
$fs->long === 1
)) {
throw new Exception('invalid cysp format');
}
$bodyCount = $fs->long;
$fs->littleEndian = false;
$fs->position = ($bodyCount + 1) * 32;
// read binary. some not implemented
$skel = ['skeleton'=>[]];
$skel['skeleton']['hash'] = readString($fs);
$skel['skeleton']['version'] = readString($fs);
$skel['skeleton']['width'] = $fs->float;
$skel['skeleton']['height'] = $fs->float;
$skel['nonessential'] = $fs->bool;
$nonessential = $skel['nonessential'];
if ($nonessential) {
$skel['skeleton']['fps'] = $fs->float;
$skel['skeleton']['images'] = readString($fs);
}
$boneCount = readVarInt32($fs);
if ($boneCount) {
$skel['bone'] = [];
for ($i=0; $i<$boneCount; $i++) {
$skel['bone'][] = readBone($fs, $nonessential, $i!=0);
}
}
$slotCount = readVarInt32($fs);
if ($slotCount) {
$skel['slot'] = [];
for ($i=0; $i<$slotCount; $i++) {
$skel['slot'][] = readSlot($fs);
}
}
$ikCount = readVarInt32($fs);
if ($ikCount) {
$skel['ik'] = [];
throw new Exception('has ik data');
}
$transformCount = readVarInt32($fs);
if ($transformCount) {
$skel['transform'] = [];
throw new Exception('has transform data');
}
$pathCount = readVarInt32($fs);
if ($pathCount) {
$skel['path'] = [];
throw new Exception('has path data');
}
$skel['skin'] = [
readSkin($fs, 'default', $skel)
];
$skinCount = readVarInt32($fs);
for ($i=0; $i<$skinCount; $i++) {
$name = readString($fs);
$skel['skin'][] = readSkin($fs, $name, $skel);
}
$eventCount = readVarInt32($fs);
if ($eventCount) {
$skel['event'] = [];
for ($i=0; $i<$eventCount; $i++) {
$skel['event'][] = [
'name' => readString($fs),
'int' => readVarInt32($fs, false),
'float' => $fs->float,
'string' => readString($fs)
];
}
}
return $skel;
$json = processToJson($skel);
file_put_contents($out, json_encode($json['animations'], JSON_UNESCAPED_SLASHES));
//file_put_contents($out, json_encode($json, JSON_UNESCAPED_SLASHES));
}
function readCyspAnimation($animFile, $skel) {
$animFs = new FileStream($animFile);
$animation = [];
$animFs->littleEndian = true;
if (!(
$animFs->readData(4) == 'cysp' &&
$animFs->long === 0 &&
$animFs->long === 1
)) {
throw new Exception('invalid cysp format');
}
$animationCount = $animFs->long;
$animFs->littleEndian = false;
$animFs->position = ($animationCount + 1) * 32;
if ($animationCount) {
for ($i=0; $i<$animationCount; $i++) {
$animation[] = readAnimation($animFs, $skel);
}
}
return $animation;
}
if (defined('TEST_SUITE') && TEST_SUITE == __FILE__) {
//exportSkel('106311.skel');
//exportSkel('106331.skel');
chdir(__DIR__);
//echo "muse/1_rock_main_show.skel\n";
exportSkel('muse/1_rock_main_show.skel');
//echo "muse/1_rock_victory.skel\n";
//exportSkel('muse/1_rock_victory.skel');
//echo "muse/char_1_rock.skel\n";
//exportSkel('muse/char_1_rock.skel');
exit;
//exportSkel('alien-pro.skel');
//exportCyspSkel('000000_CHARA_BASE.cysp', '106301_BATTLE.cysp', '106331_BATTLE.json');
$baseSkel = readCyspSkeleton('common/000000_CHARA_BASE.cysp');
file_put_contents('common/000000_CHARA_BASE.json', json_encode(processToJson($baseSkel), JSON_UNESCAPED_SLASHES));
$baseSkel['animation'] = readCyspAnimation('106301_BATTLE.cysp', $baseSkel);
$json = processToJson($baseSkel)['animations'];
file_put_contents('106301_BATTLE.json', json_encode($json, JSON_UNESCAPED_SLASHES));
chdir('common');
foreach (glob('*.cysp.txt') as $file) {
echo "$file\n";
/*$skel = ['skeleton'=>[],'animation'=>[]];
$animFs = new FileStream($file);
$animFs->littleEndian = true;
$animFs->position = 12;
$animationCount = $animFs->long;
$animFs->littleEndian = false;
$animFs->position = ($animationCount + 1) * 32;
for ($i=0; $i<$animationCount; $i++) {
$skel['animation'][] = readAnimation($animFs);
}*/
$baseSkel['animation'] = readCyspAnimation($file, $baseSkel);
$json = processToJson($baseSkel)['animations'];
file_put_contents(
pathinfo(pathinfo($file, PATHINFO_FILENAME), PATHINFO_FILENAME) . '.json',
json_encode($json, JSON_UNESCAPED_SLASHES)
);
//exportCyspSkel('../000000_CHARA_BASE.cysp', $file, pathinfo(pathinfo($file, PATHINFO_FILENAME), PATHINFO_FILENAME) . '.json');
}
/*$fs = new FileStream('106301_BATTLE.cysp');
$fs->position = 32*5;
print_r(readAnimation($fs));*/
}
@esterTion
Copy link
Author

@Krulu

Converter is discontinued.
Check the steps in comment at the beginning for local viewing.

@NerdyBingas
Copy link

Are we able to use .cysp files in the modified WebGL runtime

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment