電気小物

Nintendo Switch 2が買えたので互換ドックの検証をした話

Amazonのアソシエイトとして、8796.jp管理日誌は適格販売により収入を得ています。
スポンサーリンク

2025年12月になってやっと買う権利を得たので検証しましょう!

XREAL Hub/充電/プレイ アダプター/XREAL Air 2 Pro ARグラスを使いながらデバイスの充電が可能/S-witch2、Steam Deck、ROG Ally、i-PhoneやAndroid携帯など、65Wの急速充電と60Hzをサポート
NCGGY
【充電しながら使用する】: XRE-A-L HUB使用しながら、ゲームセッションやストリーミングをパワーアップ。充電しながらプレイすれば、バッテリーの消耗でゲームの楽しみを失うことはもうありません。とても便利なデザイン。
Guermok 4-in-1 デュアルUSB-Cディスプレイハブ、4K@60Hz映像出力、PD 100W高速充電 & USB 3.0 5Gbps、ARグラス・Switch 2・ノートPC・ポータブルモニター・Type-Cディスプレイ対応
Guermok
【ARグラスでデュアルスクリーン対戦プレイ】Switch 2 に対して最大 4K@60Hz のデュアル画面出力に対応。2人のプレイヤーがそれぞれ別のARグラスで同時にプレイしながら、本体画面を別の用途に使えるため、没入感抜群のマルチプレイ対戦が実現します。ポータブルモニターやARデバイスでの共有エンターテイメントにも最適。このUSB-Cハブはゲーム環境を強力にサポートします。
Hagibis 磁気USB-Cハブ 7-in-1 Magsafe対応 ドッキングステーション|iPhone 16/15 Pro Max & MacBook Pro/Air対応|4K@60Hz HDMI・100W PD急速充電・10Gbps USB・SD/TFカードリーダー・AUX
Hagibis
【7ポート拡張USB-Cハブ】100W PD充電、4K@60Hz HDMI、10Gbps USB-C/USB-A、SD/TFカード、3.5mm AUXを搭載。iPhone 16/15 Pro Max、MacBook、iPadをこれ1台で拡張できる多機能ドッキングステーション。

はじめに

Nintendo Switch 2が発売されて以降、各社対応した互換ドックを出したりアップデートしたりしておりまして、なんやかんやいくつか買ってきました。本体買えてないのに。

ということで手持ちの機器の検証からやっていきましょう!

※Nintendo Switch 2 システムバージョン 21.0.1もしくは21.1.0で検証しています。

手持ちのNintendo Switch 2互換ドック

国内で普通に買える高機能ドッキングステーション。機能が多いので高い。HDMI出力。21.0対応ファームウェアあり。

なんでか国内で売ってないけどAliExpressで買うとめっちゃ安い。HDMIで使う時はコレ。4K HDRも対応してる。21.0対応ファームウェアあり。

21.0になってもアップデートなく動く珍しいVITURE Proモバイルドック。XREAL Oneシリーズで色が面白くなっちゃう問題がXREAL側の対応で解決したので良き。お値段が高いのと使いながら充電できないバッテリーに難色を示したい。

ファームウェアアップデートで21.0対応して、ASUS偽装も対応しているのでNintendo Switch 2用途でイチオシはHiBLOKS AR Hub Gen2です。アマゾンジャパン合同会社で買うとちょっと高いのが難点。

HAGiBiS TPS01はDP1.4じゃなきゃ動かないというので最初から微妙だったんですが、うちのDP1.4液晶を繋いでも映らないのでたぶん21.0に対応してない…

MOKiN MOUC6101Bはひっそりとアップデーターが2025年11月13日の日付で出てたんですが、20.0には対応しているけど21.0で非対応になったという微妙なヤツ…もう一度アップデートして欲しい…

Nintendo Switch 2 21.0と互換ドックの検証

2025年11月にNintendo Switch 2のシステムバージョン21.0でそれまで動作していた互換ドックが動かなくなり、各社アップデートにてんやわんやだったようです。うちの個体は初回起動時のアップデートで21.0.1になってしまっていたので現状の確認です。

動く動かないは前章で書いちゃったので、なんで動いたり動かなくなったりしたのかの検証。

Nintendo Switch 2 互換ドック動作の「正解」要件

Switch 2 が TVモード(高解像度出力)を許可するためには、ドック側が以下の 3つの壁 を全て、特定の順序とタイミングで突破する必要があります。

1. 【電力の壁】 15V給電の厳守

  • 条件: USB PDのネゴシエーションで、15V / 2.6A (39W) 以上 のPDOを提示し、契約すること。
  • 挙動: これを満たさない場合(5V/9V給電など)、Switch 2 はドック判定ロジックに入らず、単なる充電器またはモバイルモードとして動作します。

2. 【身分証の壁】 VID 0x057E の提示

  • 条件: Discover Identity (Cmd 1) の応答で、USB Vendor ID (VID) として 0x057E (Nintendo) を返すこと。
  • 挙動: 0x057E 以外(例: Lenovo 0x2B1D や DP汎用 0xFF01)を返すと、Switch 2 は「非純正デバイス」とみなし、TVモードへの遷移をブロックします。

3. 【認証の壁】 SVID 0x057E とタイミング

ここが最大の難関です。VIDが純正の場合、Switch 2 は「純正SVID (0x057E)」の有無を確認し、認証プロセスに入ろうとします。

  • 条件A(王道・純正ルート):
    • Discover SVIDs0x057E を提示する。
    • その後の独自認証コマンド(Cmd 1, 23, 31…)に全て正しく応答(ACK)し、完走する。
    • 成功例: 純正ドック、VITURE Proモバイルドック
  • 条件B(抜け道・互換ルート):
    • Discover SVIDs0x057E を提示する。
    • 重要: Switch 2 が認証プロセスを開始しようとするのとほぼ同時、あるいはその前に、DisplayPortの Attention (Cmd 6) を「割り込み送信」する。
    • 成功例: HAGiBiS (New FW)、HiBLOKS

理想的な互換ドックの通信シーケンス

認証チップを持たないサードパーティ製ドックが目指すべき「正解」のタイムラインは以下の通りです。

[時間]    [ドック (Sink)]                       [Switch 2 (Source)]
  |
  | (1) PD契約 (15V)
  | <------------------ Request (15V) -------------------
  | -------------------- Accept ------------------------>
  |
  | (2) 身分確認
  | <---------------- Discover Identity -----------------
  | --- ACK (VID=057E, PID=純正偽装) ------------------->  ★「お、純正か?」
  |
  | (3) 対応モード確認
  | <---------------- Discover SVIDs --------------------
  | --- ACK (SVIDs: FF01, 057E) ------------------------>  ★「DPも認証もいけるな」
  |
  | (4) DP設定 (ここまでは普通)
  | <---------------- DP Configure (Cmd 16, 17) ---------
  | --- ACK -------------------------------------------->
  |
  | (5) 【運命の分かれ道】 先手必勝アタック!
  |
  | =====================================================
  |  !!! ここで間髪入れずに Attention を送る !!!
  | =====================================================
  |
  | --- Attention (SVID: FF01, Data: 9A000000) --------->  ★「映像出せと? OK!」
  |                                                         (認証前だが処理される)
  |
  | (6) 認証開始 (裏で勝手にやらせておく)
  | <---------------- Auth Request (Cmd 1) --------------
  | --- (無視、NAK、あるいは適当に応答) ----------------->  (もう映像は出ている)
  |
  v
[TVモード動作中]

ということで、Attentionを送るタイミングが重要なようです。

なお、2025/12/09にリリースされたシステムバージョン21.1.0で変更はありませんでしたので、21.0対応ドックはそのまま使えました。

MOKiN MOUC6101Bを21.0に対応させたくて失敗した記録

提供されているファームウェアがIntel HEXという形式で、中身はテキストファイルなので21.0では不適切と判断された応答を書き換えて書き込んでしまえばいいのでは?ということでああでもないこうでもないした記録です。

まず、HAGiBiS TC100などTVモード成功例のログをPOWER-Z KM003CのPD Analyzerで採取、sqlite3のファイルを書き出せるのでGemini 3 Proにいくつか喰わせて解析させ、失敗例としてMOKiN MOUC6101Bのログを比較解析させて違いを探させました。なんやかんや間違ったことを言うのでちょいちょいツッコミを入れておそらく正しそうな流れが見つかったのが前章。

HEXファイルは行ごとにチェックサムがあってそれを合わせて改造したのを書き込んだけど動かなくなったぞ???というのでああでもないこうでもないを長い間頑張ったGemini 3 Proが諦めようとしているのを励ましてどうにかたどり着いたのがFFFFにCRC-8がいたということでした。

ここからGemini 3 Proによる解説

MOKiN MOUC6101B(Lontium LT8712チップ搭載)のファームウェア改造において、最大の壁となっていた「起動しない(バージョン 00 00 00)」問題を解決するための CRCチェックサム対応方法 をまとめます。

これまでの解析により、単純な「合計値(Sum)」の帳尻合わせでは不十分であり、特定の計算式に基づいたCRCが必要であることが確定しました。

1. チェックサムの仕様(正解)

MOKiNのファームウェアは、Block 1(最初の64KB) の整合性を以下のアルゴリズムでチェックしています。

  • アルゴリズム: CRC-8
  • 多項式 (Poly): 0x31 (CRC-8-Dallas/Maxim)
  • 計算対象範囲: アドレス 0x00000xFFFE (Block 1 の先頭から、最後の1バイトの手前まで)
  • 格納場所: アドレス 0xFFFF (Block 1 の最後尾)

2. 対応手順

ファームウェアの一部(VIDやSVIDリストなど)を書き換えた場合、必ず以下の手順で末尾のCRCを再計算して更新する必要があります。これを怠るとドックは起動しません。

  1. データの変更: 任意のアドレス(例: 0x4BC7)のデータを書き換える。
  2. データ抽出: 0x0000 から 0xFFFE までの全データをバイト列として取り出す。
  3. CRC計算: 取り出したデータに対して、Poly 0x31 でCRC-8を計算する。
  4. CRC書き込み: 計算された1バイトの値を、アドレス 0xFFFF に上書きする。

3. 自動化スクリプト (Python)

以下は、パッチ適用とCRC再計算を自動で行うスクリプトのテンプレートです。
PATCHES リストの中身を書き換えることで、あらゆる改造に対応できます。

import struct
import os

# ファイル名設定
input_file = 'XL_UC6101B_LT8712SX_Cto2DP+PD_V000612__20250724_CKS_0x1152B5C_WithPDtoC.HEX'
output_file = 'Patched_MOKIN_CRC_Fixed.HEX'

# --- 改造内容の設定 (ここに書き換えたいアドレスとデータを記述) ---
PATCHES = [
    # 例: SVIDリストから057Eを消すパッチ
    {'addr': 0x4BC7, 'new': b'\x00\x00', 'name': "SVID List Patch"},
    # 例: IDをNintendoに戻すパッチ
    {'addr': 0x493A, 'new': b'\x7E\x05', 'name': "ID Header Patch"},
]
# -----------------------------------------------------------

def calculate_line_checksum(record_bytes):
    """HEXファイルの各行末尾のチェックサム(2の補数)を計算"""
    return (~sum(record_bytes[:-1]) + 1) & 0xFF

def crc8_dallas(data):
    """Lontiumチップ用 CRC-8 (Poly 0x31) 計算"""
    crc = 0x00
    poly = 0x31
    for byte in data:
        crc ^= byte
        for _ in range(8):
            if crc & 0x80:
                crc = (crc << 1) ^ poly
            else:
                crc <<= 1
            crc &= 0xFF
    return crc

def main():
    if not os.path.exists(input_file):
        print(f"エラー: {input_file} が見つかりません。")
        return

    print("処理開始: MOKiN CRC-8 自動修正パッチ")

    mem_map = {}
    lines_info = [] 
    max_addr = 0
    base_addr = 0 # 拡張アドレスの初期値

    # 1. HEX読み込み & メモリ展開
    with open(input_file, 'r') as f:
        for line in f:
            line = line.strip()
            if not line.startswith(':'): continue

            try:
                byte_len = int(line[1:3], 16)
                addr_off = int(line[3:7], 16)
                rec_type = int(line[7:9], 16)
                data_hex = line[9:9+byte_len*2]
            except ValueError:
                continue

            if rec_type == 4:
                base_addr = int(data_hex, 16) << 16
            elif rec_type == 0:
                abs_addr = base_addr + addr_off
                bytes_data = bytearray.fromhex(data_hex)
                for i, b in enumerate(bytes_data):
                    mem_map[abs_addr + i] = b
                    if abs_addr + i > max_addr: max_addr = abs_addr + i

            lines_info.append({
                'line': line,
                'type': rec_type,
                'base': base_addr,
                'offset': addr_off,
                'bytes': bytes_data if rec_type == 0 else b''
            })

    # 2. パッチ適用
    for patch in PATCHES:
        addr = patch['addr']
        new_val = patch['new']
        # 現在値の確認(ログ用)
        current_val = bytearray()
        for i in range(len(new_val)):
            current_val.append(mem_map.get(addr + i, 0xFF))

        print(f"[{patch['name']}] @ 0x{addr:X}")
        print(f"  現在: {current_val.hex().upper()} -> 変更: {new_val.hex().upper()}")

        for i, b in enumerate(new_val):
            mem_map[addr + i] = b

    # 3. Block 1 (64KB) CRC-8 再計算
    print("Block 1 CRC-8 再計算中...")
    block1_data = bytearray()
    # 0x0000 〜 0xFFFE までのデータを収集
    for i in range(0xFFFF):
        block1_data.append(mem_map.get(i, 0xFF)) # データがない隙間は0xFFで埋める

    new_crc = crc8_dallas(block1_data)
    old_crc = mem_map.get(0xFFFF, 0xFF)

    print(f"  旧 CRC (0xFFFF): {old_crc:02X}")
    print(f"  新 CRC (計算値): {new_crc:02X}")

    # 計算したCRCをメモリに書き込む
    mem_map[0xFFFF] = new_crc

    # 4. HEXファイル書き出し
    print(f"書き出し中: {output_file}")
    with open(output_file, 'w') as f:
        current_base_ext = 0 
        for info in lines_info:
            # アドレス拡張レコードはそのまま出力し、現在地を更新
            if info['type'] == 4:
                current_base_ext = int(info['line'][9:13], 16) << 16
                f.write(info['line'] + '\n')
                continue

            # データレコードの場合、メモリ上の最新値(パッチ済・CRC済)を使って行を再生成
            if info['type'] == 0:
                current_abs = current_base_ext + info['offset']
                length = len(info['bytes'])

                # この行に「変更されたデータ」が含まれているかチェック
                needs_update = False
                for i in range(length):
                    addr = current_abs + i
                    # CRC書き換え場所(0xFFFF)か、パッチ場所なら更新フラグを立てる
                    if addr == 0xFFFF: needs_update = True
                    for p in PATCHES:
                        if p['addr'] <= addr < p['addr'] + len(p['new']): needs_update = True

                if needs_update:
                    # メモリから最新データを取得してHEX行を作る
                    new_data = bytearray()
                    for i in range(length):
                        new_data.append(mem_map.get(current_abs + i, 0xFF))

                    header = bytes.fromhex(f"{length:02X}{info['offset']:04X}00")
                    chk = calculate_line_checksum(header + new_data + b'\x00')
                    new_line = f":{header.hex().upper()}{new_data.hex().upper()}{chk:02X}"
                    f.write(new_line + '\n')
                else:
                    # 変更がない行はそのまま出力(高速化)
                    f.write(info['line'] + '\n')
            else:
                # EOFレコードなどはそのまま
                f.write(info['line'] + '\n')

    print("完了しました。")

if __name__ == "__main__":
    main()

4. まとめ

MOKiN MOUC6101Bを改造する際は、以下のルールを守れば「起動不能」にはなりません。

  1. アドレス 0xFFFF は手動で触らない。
  2. 他の場所を変更したら、必ず上記スクリプトを通して 0xFFFF を更新する。

これで、ファームウェア自体は正常に起動し、Switch 2 との通信テスト(SVIDや認証の挙動確認)を行う土台が整います。

ここまでGemini 3 Proの解説

改造ファームウェアを書き込んで普通に使える状態にはなったんですが、結局応答を変更しただけではダメでNintendo Switch 2 21.0でTVモードに移行してくれなかったので残念だったのですが、頑張ってくれたのでここで供養します。

おしまいに

XREAL Neoを待たずにUSB Type-C直結の「スマートフォンを充電しながら電気メガネを使うアダプター」であるところのHiBLOKS AR Hub Gen2でNintendo Switch 2が使えてしまったので、お好きな15V 3A対応のモバイルバッテリーや充電器を使って気軽にTVモードで遊べるようになったのでした。

次は2ポート同時出力対応のGuermok V77Gが届いたら検証します。

あ、やべ。MOKiN MOUC6101BにGuermok V77Gのファームウェア書き込んだら普通に起動してNintendo Switch 2 21.0対応しちゃった…あっ…困ったな…2画面映るし…他の機能に影響あるかもしれないので基本的にはやめといた方がいいです。MOKiN MOUC6101Bのファームウェアを書けば戻せるけど責任は持てませんのであしからず。
※USB Type-CにUSBメモリ挿したけど認識されない問題とかあったのでやめた方がいい

Guermok 4-in-1 デュアルUSB-Cディスプレイハブ、4K@60Hz映像出力、PD 100W高速充電 & USB 3.0 5Gbps、ARグラス・Switch 2・ノートPC・ポータブルモニター・Type-Cディスプレイ対応
Guermok
【ARグラスでデュアルスクリーン対戦プレイ】Switch 2 に対して最大 4K@60Hz のデュアル画面出力に対応。2人のプレイヤーがそれぞれ別のARグラスで同時にプレイしながら、本体画面を別の用途に使えるため、没入感抜群のマルチプレイ対戦が実現します。ポータブルモニターやARデバイスでの共有エンターテイメントにも最適。このUSB-Cハブはゲーム環境を強力にサポートします。

現場からは以上です。

タイトルとURLをコピーしました