1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PSO2NGSのシンボルアートをパースするライブラリ「Symbol Art Parser」の技術仕様書

1
Last updated at Posted at 2026-05-20

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];

2

サウンド関係は、enum型だが、TypeScriptにおいては冗長なコードとして出力されてしまうためunion型として記述した。

JSON Schemaによる表現

独自のJSONを作るとVScodeなどで編集したりするときに、コード補完やjson構造が正しいかの検証をしたいというユースケースが発生するだろう。もちろん前述のinterfaceで型定義でも十分な場合が多いが、ライブラリという性質上、可用性を高めるため、本ライブラリでは、Json Schemaも定めた。

Json schemaとは、IETFという標準化団体のもとで標準化されているJSONデータの構造を定義するための記述方法で、それそものもJsonで記述する。これにより、アプリケーション間のコミュニケーションの生産性・品質を向上させる。

著名な利用例だとSwagger(OpenAPI)が有名である。

普段お世話になっているであろうpackage.jsonや、composer.jsongeojsonなども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を書き換えることで作者を匿名化させる
  1. このライブラリを開発したのは2022年ぐらい。

  2. PSO2NGSのシンボルアートは2Dに見えるが、実際は平面に配置しているのではなく、3D空間に平行投影(アクソノメトリック)で描画している。
    豆知識として、シンボルアートは定義上、約1677万色使えるが、NGS前の旧PSO2ではPS Vitaとの兼ね合いからか、65536色しか使えなかった。PSO2から、PSO2NGSへの過渡期に正確なカラーコードを入れても色が丸め込まれてしまっていたのはその名残である。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?