CardWirthNextのセル機能対応

Issue #98 duplicate
k4nagatsuki repo owner created an issue

現在のCardWirthNextには次の機能が実装されているようです(詳細は未見)。

  • PCをセルに表示
  • セルの画像埋め込み
  • カードより手前に描画するセル
  • イベントコンテント「背景位置・サイズ変更」「背景置換」「背景削除」を追加

対応作業は大雑把に3段階に分けられます。

  1. それぞれの機能のデータ構造を調べる
  2. XMLでの記述形式を決める
  3. データの変換を実装する
  4. 挙動を実装する

何もコメントがないうちは未着手です。作業してみようという方がいらっしゃったらぜひどうぞ。

Comments (10)

  1. k4nagatsuki reporter

    データ構造を調べました。おおむねこれで会っているはず。

    各セルは名前や前景にするなどの情報がつくとデータバージョン70000、そうでない場合は(画像セル以外は)60000になるようです。CWのデータにおけるデータバージョンのルールが乱れていますが、そういう構造になっている以上仕方ありません。

    exint, exstring, eximageについてですが、exstringはバイト数を表すuint32の代わりにexintを使った文字列型、eximageはバイナリサイズを表すuint32の代わりにexintを使ったバイナリ型です。exintは少々複雑なので後述。

    すべてフラグ名の後に不明な1バイトがありますが、これは1.28以前のままです。

    [Included/Named Image Cell]
    x(dword)
    y(dword)
    width+70000(dword) (Data Version: 7)
    height(dword)
    type=0(byte)
    mask(byte)
    background=0,foreground=1(byte)
    included=1(byte)
    if included=1:
            binary image(eximage)
    else:
            filename(string)
    flag(string)
    ? 00(byte)
    name(exstring)
    
    [Named Text Cell]
    x(dword)
    y(dword)
    width+70000(dword) (Data Version: 7)
    height(dword)
    type=2(byte)
    mask?(byte)
    background=0,foreground=1(byte)
    text(string)
    ... (See also: Data Version: 6)
    flag(string)
    ? 00(byte)
    name(exstring)
    
    [Named Color Cell]
    x(dword)
    y(dword)
    width+70000(dword) (Data Version: 7)
    height(dword)
    type=3(byte)
    mask?(byte)
    background=0,foreground=1(byte)
    blend mode(byte)
    ... (See also: Data Version: 6)
    flag(string)
    ? 00(byte)
    name(exstring)
    
    [PC Cell]
    x(dword)
    y(dword)
    width+60000(dword) (Data Version: 6)
    height(dword)
    type=4(byte)
    mask?(byte)
    pc number(byte)
    flag(string)
    ? 00(byte)
    
    [Named PC Cell]
    x(dword)
    y(dword)
    width+70000(dword) (Data Version: 7)
    height(dword)
    type=4(byte)
    mask?(byte)
    background=0,foreground=1(byte)
    pc number(byte)
    flag(string)
    ? 00(byte)
    name(exstring)
    
    [Move Cell Content]
    type=74(byte)
    name(string)
    next content number+40000(dword)
    ... next contents ...
    cell name(exstring)
    move=0x01 | move=0x02(byte)
    if move:
            move type abs=0,rel=1,per=2
            move x(exint)
            move y(exint)
    if resize:
            resize type abs=0,rel=1,per=2
            resize width(exint)
            resize height(exint)
    
  2. k4nagatsuki reporter

    exintについて。exintというのは可変長のバイト列で表されている整数に対して私が勝手につけた名前で、どこかに正式な規格があるかもしれません。

    通常、整数型はそれを表現するためのバイト数が決まっていますが(例えばdwordなら4バイト)、これを数に応じて伸縮させる事ができるようにした表現形式のようです。

    どうしてCWの内部データの作法から外れるこんな形式を使ったのかは謎です。下手をすると周辺ツールの作成者の詰みポイントになりかねないので、ここに仕様を書いておきます。

    具体的には、1つの数値を次のようにしてバイト列に変換します。

    1. 数値全体を1ビット左へローテートさせ、符号をデータの先頭から末尾へ移動する。
    2. 全体を7bitずつに区切る。
    3. 7bitのデータの先頭に、次のバイトを引き続き数値表現に使用するかどうかを表す1bitを加えて1バイトのデータにする(1=次のバイトも使用する、0=このバイトで終了)。
    4. 3.でできたバイト列をリトルエンディアンで並べる。

    バイト列からの取得はこの手順を逆にすればOKです。

    ついでなのでD言語の実装サンプルも載せておきます。

    /// valueを可変長整数としてbytesへ書き込む。
    /// 可変長整数は整数値を7ビットずつに区切り、それぞれの頭に
    /// 「後続の値があるか(1)無いか(0)」を示す1ビットを加えて1バイトとし、
    /// リトルエンディアンで並べたもの。
    /// この形式では符号をビット群の先頭に置けないため、7ビットずつに
    /// 分割する前に全体を1ビット左へシフトし、右端に符号ビットを置く。
    /// offsetにはbytesのどの位置から書き込みを開始するかを指定し、
    /// 実際に読み込まれたバイト数が加算される。
    /// See_Also: readExInt()
    void writeExInt(int value, ubyte[] bytes, ref size_t offset) {
        ubyte b;
        uint value2;
        if (value < 0) {
            value2 = -(value + 1) << 1;
            value2 |= 0x1;
        } else {
            value2 = value << 1;
        }
        do {
            b = value2 & 0x7F;
            value2 >>= 7;
            if (value2) b |= 0x80;
            bytes[offset] = b;
            offset++;
        } while (value2);
    }
    ///
    unittest {
        ubyte[] write(int value) {
            auto bytes = new ubyte[8];
            size_t offset = 0;
            writeExInt(value, bytes, offset);
            return bytes[0..offset];
        }
        assert (write(5430)    == [cast(ubyte)0xEC, 0x54]);
        assert (write(78)      == [cast(ubyte)0x9C, 0x01]);
        assert (write(134)     == [cast(ubyte)0x8C, 0x02]);
        assert (write(1094)    == [cast(ubyte)0x8C, 0x11]);
        assert (write(102)     == [cast(ubyte)0xCC, 0x01]);
        assert (write(124)     == [cast(ubyte)0xF8, 0x01]);
        assert (write(1822798) == [cast(ubyte)0x9C, 0xC1, 0xDE, 0x01]);
        assert (write(510)     == [cast(ubyte)0xFC, 0x07]);
        assert (write(1)       == [cast(ubyte)0x02]);
        assert (write(0)       == [cast(ubyte)0x00]);
        assert (write(-1)      == [cast(ubyte)0x01]);
        assert (write(-9)      == [cast(ubyte)0x11]);
        assert (write(-5000)   == [cast(ubyte)0x8F, 0x4E]);
        assert (write(-9999)   == [cast(ubyte)0x9D, 0x9C, 0x01]);
    }
    
    /// bytesから可変長整数を読み込む。
    /// offsetにはbytesのどの位置から読み込みを開始するかを指定する。
    /// See_Also: writeExInt()
    int readExInt(in ubyte[] bytes, ref size_t offset) {
        uint r = 0;
        size_t i = 0;
        uint b, b2;
        while (true) {
            b = bytes[offset];
            offset++;
            b2 = b & 0x7F;
            r |= b2 << i;
            if ((b & 0x80) == 0) {
                break;
            }
            i += 7;
        }
        if (r & 0x1) {
            return -(r >> 1) - 1;
        } else {
            return r >> 1;
        }
    }
    ///
    unittest {
        size_t offset = 0;
        offset = 0; assert (readExInt([cast(ubyte)0xEC, 0x54], offset) == 5430);
        offset = 0; assert (readExInt([cast(ubyte)0x9C, 0x01], offset) == 78);
        offset = 0; assert (readExInt([cast(ubyte)0x8C, 0x02], offset) == 134);
        offset = 0; assert (readExInt([cast(ubyte)0x8C, 0x11], offset) == 1094);
        offset = 0; assert (readExInt([cast(ubyte)0xCC, 0x01], offset) == 102);
        offset = 0; assert (readExInt([cast(ubyte)0xF8, 0x01], offset) == 124);
        offset = 0; assert (readExInt([cast(ubyte)0x9C, 0xC1, 0xDE, 0x01], offset) == 1822798);
        offset = 0; assert (readExInt([cast(ubyte)0xFC, 0x07], offset) == 510);
        offset = 0; assert (readExInt([cast(ubyte)0x02], offset) == 1);
        offset = 0; assert (readExInt([cast(ubyte)0x00], offset) == 0);
        offset = 0; assert (readExInt([cast(ubyte)0x01], offset) == -1);
        offset = 0; assert (readExInt([cast(ubyte)0x11], offset) == -9);
        offset = 0; assert (readExInt([cast(ubyte)0x8F, 0x4E], offset) == -5000);
        offset = 0; assert (readExInt([cast(ubyte)0x9D, 0x9C, 0x01], offset) == -9999);
    }
    
  3. takuto_cw

    ……これはすごい。文章にすれば簡単そうでも、この法則を「調べました」の一言の裏に、どれだけ労力がかかったのか…。本当に、お疲れ様です。

    この issue は歯が立ちませんでしたが、自分もあと数日したら何か出せそうです。←でも途中でダメになるのが怖くて担当者選択できない

  4. k4nagatsuki reporter

    そういうのは作業が重複してしまう可能性があるので、自分の担当にしておくか、一言書いておくのがお勧めです。もちろんダメだった場合はギブアップを追記しないとデッドロック状態になってしまうのですが。

    それはそれとして、なんであれ作業していただけるのは本当にありがたいです。ありがとうございます。

  5. takuto_cw

    作業が重複してしまう可能性があるので

    むむ、確かにそうですね。

    一応「言いだしっぺである自分がやります」宣言してあるので、他の方の作業が無駄になるようなことはない、はず。ああでも担当者設定してあった方が、新しく来た人にも作業中の人がいることが一目で分かっていいかも。ちょっといじってきます…。

  6. k4nagatsuki reporter

    名前付きカラーセルが間違っていました。訂正版。

    [Named Color Cell]
    x(dword)
    y(dword)
    width+70000(dword) (Data Version: 7)
    height(dword)
    type=3(byte)
    blend mode(byte)
    background=0,foreground=1(byte)
    gradient direction(byte)
    ... (See also: Data Version: 6)
    flag(string)
    ? 00(byte)
    name(exstring)
    
  7. k4nagatsuki reporter

    よく見たら途中で切れてるのでイベントコンテント部分を再掲。

    実は当初背景置換コンテントの仕様を一箇所見誤っており、背景の数がbyteだと思っていたのだがexuintともいうべき型でした。これは符号無しのexintともいうべきもので、内容としてはexintの手順1.の符号に関する処理を抜いたものと思って間違いないはずです。

    [Move Cell Content]
    type=74(byte)
    name(string)
    next content number+40000(dword)
    ... next contents ...
    cell name(exstring)
    move=0x01 | move=0x02(byte)
    if move:
        move type abs=0,rel=1,per=2
        move x(exint)
        move y(exint)
    if resize:
        resize type abs=0,rel=1,per=2
        resize width(exint)
        resize height(exint)
    
    [Replace Cell Content]
    type=76(byte)
    name(string)
    next content number+40000(dword)
    ... next contents ...
    cell name(exstring)
    cell number(exuint)
    ... cell data ...
    
    [Delete Cell Content]
    type=75(byte)
    name(string)
    next content number+40000(dword)
    ... next contents ...
    cell name(exstring)
    
  8. k4nagatsuki reporter

    仕様が変更されたようです。おそらく上記のデータから変わっていると思われます。

  9. Log in to comment