Symbol Art Parser仕様書
https://www.npmjs.com/package/symbol-art-parser
本ライブラリは、PSO2NGSのシンボルアート(*.sar)のバイナリをJSONに相互変換するものである。1
あくまでも、読み書きを目的とし描画処理は実装しない。
「シンボルアート」とは、この画像のようにゲームの中で、「シンボル」と呼ばれるパーツを駆使して、自分で絵を書き、下記のようにチャットの吹き出しに貼ることができるゲームの機能である。
ライブラリの設計思想
- SynbolArtクラスとして完全にカプセル化する
- dataパラメータで
ArrayBuffer形式として読み書き - jsonパラメータで
JSON形式として読み書き
動作サンプル
https://logue.dev/symbol-art-parser/
任意のsarファイル(C:\Users\(PCのユーザー名)\Documents\SEGA\PHANTASYSTARONLINE2\symbolarts\import内にある*.sarファイル)を読み込ませると、使用されているシンボルと色などがダンプされる。
使用方法
本ライブラリではファイル実体へのアクセスはサポートせず、ArrayBufferとして読み書きをする。SymbolArtというクラスでシンボルアートを抽象化し、dataパラメータで透過的にアクセスできる。
import SymbolArt from 'symbol-art-parser';
const sar = new SymbolArt();
const reader = new FileReader();
reader.onload = () => {
// シンボルアートを読み込ませる
sar.data = reader.result;
};
reader.readAsArrayBuffer('[*.sar file]');
// シンボルアートをJSONとしてダンプする場合
const json = sar.json;
// JSONからシンボルアートに書き込む場合
sar.json = json;
// シンボルアートをArrayBufferとして保存する
const data = sar.data;
JSON化されたシンボルアートの形式
interface SymbolArtInterface {
/** PSO2のアカウントID */
authorId: number;
/** シンボルアート名 */
name: string;
/** シンボルアートのサイズ */
size: {
/** 高さ */
height: number;
/** 幅 */
width: number;
};
/** ゲーム内でシンボルアートが表示されたときにならすサウンドのID */
sound: SoundType;
/** レイヤー */
layers: LayerInterface[];
}
interface LayerInterface {
/** シンボルのID */
symbol: number;
/** 表示フラグ */
isVisible: boolean;
/** シンボルの位置 */
position: {
topLeft: PositionType;
bottomLeft: PositionType;
topRight: PositionType;
bottomRight: PositionType;
};
/** 透過度 */
a: number;
/** 赤 */
r: number;
/** 緑 */
g: number;
/** 青 */
b: number;
/** X方向の歪み */
x: number;
/** Y方向の歪み */
y: number;
/** Z方向の歪み */
z: number;
}
interface PositionType {
x: number;
y: number;
}
/** Sound Effects */
const Sounds: Record<string, number> = {
None: 1,
Normal: 2,
Joy: 3,
Anger: 4,
Sorrow: 5,
Anxiety: 6,
Surprised: 7,
Doubt: 8,
Whistle: 9,
Shy: 10,
Success: 11,
} as const;
/** Sound Effect Type */
export type SoundType = (typeof Sounds)[keyof typeof Sounds];
サウンド関係は、enum型だが、TypeScriptにおいては冗長なコードとして出力されてしまうためunion型として記述した。
JSON Schemaによる表現
独自のJSONを作るとVScodeなどで編集したりするときに、コード補完やjson構造が正しいかの検証をしたいというユースケースが発生するだろう。もちろん前述のinterfaceで型定義でも十分な場合が多いが、ライブラリという性質上、可用性を高めるため、本ライブラリでは、Json Schemaも定めた。
Json schemaとは、IETFという標準化団体のもとで標準化されているJSONデータの構造を定義するための記述方法で、それそものもJsonで記述する。これにより、アプリケーション間のコミュニケーションの生産性・品質を向上させる。
著名な利用例だとSwagger(OpenAPI)が有名である。
普段お世話になっているであろうpackage.jsonや、composer.json、geojsonなどもJSON Schemaによる厳格な定義がある。
VSCodeでJSONなのにコード補完ができたり、パラメータの説明がツールチップで表示されるのは、この仕組みが働いているからである。この手の型定義に興味ある人はJSON Schema Storeに行ってみることをオススメする。
{
"$id": "https://github.com/logue/symbol-art-parser/raw/master/schema.json",
"$schema": "http://json-schema.org/draft/2020-12/schema",
"title": "Symbol Art JSON",
"description": "Phantasy Star Online 2 Symbol Art Json file schema.",
"type": "object",
"properties": {
"authorId": {
"title": "Symbol art author's SEGA Account ID. Not compatible between JP accounts and global accounts.",
"type": "integer"
},
"name": {
"title": "Symbol art name.",
"type": "string"
},
"sound": {
"title": "Set play effect sound id when symbol art displayed.",
"type": "integer",
"minimum": 0,
"maximum": 12
},
"size": {
"title": "Size of Symbol Art",
"type": "object",
"additionalProperties": false,
"properties": {
"height": {
"type": "integer",
"minimum": 64,
"maximum": 128
},
"width": {
"type": "integer",
"minimum": 64,
"maximum": 193
}
},
"required": ["height", "width"]
},
"layers": {
"title": "Layer",
"type": "array",
"description": "Layers",
"items": {
"type": "object",
"additionalProperties": false,
"properties": {
"symbol": {
"title": "Symbol id",
"type": "integer",
"minimum": 1,
"maximum": 754
},
"isVisible": {
"title": "Visibility Flag",
"type": "boolean"
},
"position": {
"title": "Draw Position",
"$ref": "#/$defs/position"
},
"r": {
"title": "Red",
"type": "integer",
"minimum": 0,
"maximum": 64
},
"g": {
"title": "Green",
"type": "integer",
"minimum": 0,
"maximum": 64
},
"b": {
"title": "Blue",
"type": "integer",
"minimum": 0,
"maximum": 64
},
"a": {
"title": "Alpha",
"type": "integer",
"minimum": 0,
"maximum": 7
},
"x": {
"type": "integer",
"minimum": 0,
"maximum": 64
},
"y": {
"type": "integer",
"minimum": 0,
"maximum": 64
},
"z": {
"type": "integer",
"minimum": 0,
"maximum": 64
}
},
"required": [
"symbol",
"isVisible",
"position",
"r",
"g",
"b",
"a",
"x",
"y",
"z"
]
}
}
},
"required": ["authorId", "layers", "name", "size", "sound"],
"$defs": {
"position": {
"title": "Symbol parts drawing position.",
"type": "object",
"additionalProperties": false,
"properties": {
"topLeft": {
"$ref": "#/$defs/axis"
},
"bottomLeft": {
"$ref": "#/$defs/axis"
},
"topRight": {
"$ref": "#/$defs/axis"
},
"bottomRight": {
"$ref": "#/$defs/axis"
}
},
"required": ["bottomLeft", "bottomRight", "topLeft", "topRight"]
},
"axis": {
"title": "Axis",
"type": "object",
"additionalProperties": false,
"properties": {
"x": {
"description": "X (Horizontal) coordinates",
"type": "integer"
},
"y": {
"description": "Y (Vertical) coordinates",
"type": "integer"
}
},
"required": ["x", "y"]
}
}
}
ユースケース
- ゲームを起動することなくシンボルアートを自分で作成したり編集する
- ゲームのキャッシュフォルダにあるシンボルアートファイルの内容の確認
- 集めたシンボルアートのギャラリーを自分で作る
- SarファイルのアカウントIDを書き換えることで作者を匿名化させる

