Doloc TownにXUnity.AutoTranslatorを導入したらショートカットキーが効かなかったので、AIにplugin書かせて対処した記録
ChatGPT o3くんと一緒に土曜の午前中を使って対処に勤しんだ記録
成果物
前置き
Doloc Townはいいぞ
Doloc Town + BepInEx 5.4.23.3 + XUnity.AutoTranslator 5.4.5 の組み合わせでAutoTranslatorのショートカットキーが動作しなくて困っている人へ
- GitHub ReleaseからdllをダウンロードしてBepInEx/pluginsにぶち込んでね
- 確認はしてないけどたぶんBepInEx 5.4.x以降なら動くよ
- 心配ならソースから自分でビルドしてね
ALT+0,ALT+1で開くUIはこれ入れても動作しないよALT+R(翻訳ファイルリロード)とALT+T(翻訳のトグル) が動けば良い人向け
- 以降は読む必要ないよ
この問題の技術的詳細が知りたい人へ
- ほぼ全部AIによろしくしているので、嘘を書いていても責任は取りません
- 筆者はウェッブアプリケーッションエンジニャーでありUnityのことは一切何も知らない
この解決法の応用範囲
- 該当versionのBepInExとAutoTranslatorを利用しており、
ENABLE_LEGACY_INPUT_MANAGER=falseでビルドされたUnity製のゲームの組み合わせでたぶん同じことが起こる - それにも適用できると思われる
本題
起きていた事象
- Doloc TownにBepInExとXUnity.AutoTranslatorを導入した
- 翻訳機能自体は効いているが、翻訳ファイルを再読み込みしたり翻訳をトグルするキーが効かない
- ログには以下のように出ている
[Warning:XUnity.AutoTranslator] Input API is not available!
System.InvalidOperationException: You are trying to read Input using the UnityEngine.Input class, but you have switched active Input handling to Input System package in Player Settings.
at (wrapper managed-to-native) UnityEngine.Input.GetKeyInt(UnityEngine.KeyCode)
at UnityEngine.Input.GetKey (UnityEngine.KeyCode key) [0x00001] in <6a4057336492450e9285cede3e4bf821>:0
at XUnity.AutoTranslator.Plugin.Core.AutoTranslationPlugin.HandleInput () [0x00000] in <cd27375817094ee0902fac53c283e1a5>:0
at XUnity.AutoTranslator.Plugin.Core.AutoTranslationPlugin.HandleInputSafe () [0x00007] in <cd27375817094ee0902fac53c283e1a5>:0
ChatGPT o3に聞いてみた
BepInEx 5.4.23.3 + XUnity.AutoTranslator 5.4.5 をDoloc Townというゲームに適用しています。 機能は働くんですが、AutoTranslatorに備わっているショートカットキーが効きません。 以下のようなログが出ています。BepInExを6.xに変えたりAutoTranslatorのversionを変更したりは既に試しました。 ゲーム側で古いInput APIが無効化されており、BepInExかAutoToranslatorのどちらかがこれに対応していないというのが原因だと思いますが、これを動かせるようにする方法を教えてください
原因
原因は「ゲーム本体が Active Input Handling を Input System (New) のみでビルドしているため、 UnityEngine.Input.* を呼ぶ XUnity.AutoTranslator のホットキー処理が例外を出して停止している」ことです BepInEx 5.4 以降は新旧どちらの入力にも対応していますが、AutoTranslator 側は旧 API 固定のままです
- そうなんだ(無知)
- BepInEx 5.4から両方扱えるShimが生えているが、AutoTranslatorは旧Input APIを直接使っている
- なのでゲーム側で旧APIが使えないならそりゃ動かない
- https://github.com/bbepis/XUnity.AutoTranslator/tree/master/libs/BepInEx%205.0 の last commitが4年前
- https://github.com/BepInEx/BepInEx/releases/tag/v5.4.20
- KeyboardShortcutで旧APIを使っていたのは5.4.20で修正されたらしい
- https://github.com/bbepis/XUnity.AutoTranslator/commit/45e87c9581a2e2b1311e629f5930dac18e9f6d1d
- AutoTranslator側はコミットメッセージ見るとBepInEx 5.3のままになってそう
- https://github.com/BepInEx/BepInEx/releases/tag/v5.4.20
- https://github.com/bbepis/XUnity.AutoTranslator/issues/329
- issueは立ってたが対応するリソースがないよという状態っぽい
対処1(失敗)
…/DolocTown_Data/globalgamemanagersを開いて PlayerSettings のactiveInputHandlerを2にセットすればいいよ- これで 旧 Input クラスが再び有効になりますよ^^
- と言われたので
UnityPyを使って書き換えるコードを書かせて実行- しかし既に
activeInputHandlerは2になっていた
- しかし既に
対処1の結果をAIに投げた
Doloc Town はビルド時に レガシー Input API を完全に除去 しており、 UnityEngine.Input.* を呼ぶと例外を投げる設定(ENABLE_LEGACY_INPUT_MANAGER 定義が false)でコンパイルされています。 そのため PlayerSettings を書き換えても旧 API は復活しません。
- そうなんだ(無知)
前回ご案内した 方法②(旧 Input 呼び出しを新 Input System に橋渡しする BepInEx プラグイン)が唯一の現実的手段になります
- とのことなので、ビルド環境のお膳立てからなにからしてもらいBepInEx pluginを書いた
- Unity本体が入ってないとダメなのかと思って環境構築めんどくせぇなと思ってたけど、dllさえありゃビルドできるんですね(無知)
- 言う通りにしたらビルドが失敗する、というのが5回くらい続いたが、ログ貼ってデバッグしてもらいつつ最終的には解決
ビルドの結果生まれたdllを読み込んだがやはりキーが効かない
- ログを貼ったらしっかりデバッグしてくれた
InputShim の Prefix が AutoTranslator のパッチより後に呼ばれてしまい, 結局 Input.GetKeyInt が実行されて例外→ホットキー無効 という状況です。 (BepInEx はプラグイン読み込み順=パッチ登録順なので,あとからロードした InputShim の Prefix が 最初 に動くと思いがちですが,Harmony は 同一 Priority の場合 ID がアルファベット順で早いものが先に実行 というルールです。 XUnity.AutoTranslator → jp.example.inputshim の順になり,InputShim の Prefix が後回しになっています)
- そうなんだ(無知)
- こういう仕組みである以上複数プラグインの動作の整合性取るのクッソ面倒そう
XUnity.AutoTranslatorにcontributeしとこうかなーと思って難易度を聞いた
「プルリク 1 本で済む」ほど簡単ではありません。 旧 UnityEngine.Input 依存は キー入力 だけではなく マウス/スクロール/タッチ でも広範に使われています。 BepInEx 6 系以降は 新旧 Input API を抽象化するヘルパー(BepInEx.Unity.Input 名前空間)が用意されていますが、AutoTranslator 本体は 5 系依存で止まっており、直接は利用していません。 やるなら 依存 DLL を最新版へ更新(libs/ を丸ごと入れ替え)。 HandleInput・GUIUtil を中心に Input ラッパー層を挟む。 Mono/IL2CPP 両ターゲット・Unity 2017〜2023 で回帰テスト。 既存ホットキー CFG 互換・マクロ録画系ツールとの併用確認。 という“中規模 refactor”規模になります。
- じゃあ・・・やめとくか・・・Unityのことなんも知らんし・・・
感想
- 何も知らない分野でも2時間かそこらで問題解決できたのでAIってすごいなと思いました(小学生並みの感想)
- GitHubで公開してブログも書くか!とかやったので結局半日溶けてしまった
Pixel 3a XLからPixel 6aに移行した
Pixel 3a XLのサポート切れてたことに気付いたので6aを買って移行した
基本的にはケーブルでつないでボタンポチーするだけだったが、アプリごとに追加の対応が必要だったものをまとめておく
移行
付属のケーブルに両方の端末を繋げて画面の指示に従えばだいたい移行完了
ホーム画面等もそのまま移行してくれる
アプリごと個別の対応
モバイル Suica
LINE
新しい端末でログインしたら画面の指示に従えばOK
過去のトークも含め全部移行できる
Authy
以下はSettings -> Accounts -> BackupsはONにしている前提
電話番号を入れてSMS認証したら移行完了
移行はされているが内容は暗号化されているので、復号化パスワードを入力しアンロック
Google Pay
iDを使っているのでそれを移行
作業前に古い方で、カードを選択し右上のメニューから「非接触型決済を無効にする」を実行しておく
カード情報は移行されていたので、再入力非接触型決済を有効にするだけ
ここでCVCやカード名義などの情報は再入力が必要だった
OMRON connect
新しい方でアプリを立ち上げると初回設定時のプロンプトが現れるのでそれに従う
個人番号を上書きしようとすると古い端末の連携を解除しとけと勧められるので、それに従って解除後新しい方で連携
続いてクラウドサービスにログインし、すべての過去データ復元
ゆうちょ認証アプリ
Fitbit
ログインしたら一通り過去データは引き継がれるが、一度古い端末で解除しないと新しい端末でペアリングできない
解除時は「アカウントから削除しますか?」という文面だが、既にfitbit accountに同期済みのデータは失われることはない
解除したら新しい端末で新規に機器を追加すれば完了
Fit to Fit
何も設定しなくてもsyncされてた
Coke ON
特にログインして使ってなかったので、メニューの「機種変更、データ移行・復元」からアプリID・PINを利用して移行
移行後は前の端末ではアプリを利用できなくなる
支払い設定は(たぶん)引き継がれないので再設定が必要
Meta Quest 2
新しい端末でログイン後、Quest 2本体の電源を入れたら勝手にペアリングされる
古い端末の方はログインセッション切れでアプリからログアウトされる
同時に使えるかどうかは知らぬ
Sony Headphones
特に引継ぎはされてないっぽい
初回セットアップっぽいのをこなしてサインインし、使用している音響機器を再度登録していく作業が必要
WF-1000XM4とWH-1000XM3を再登録。WF-1000XM3も持ってるけど充電できなくなっちゃったのでお前は捨て置く
Soundcore
特に引継ぎはされてなさそう。ログインして機器を接続し直した
HearIDの設定は残ってたけどこれはイヤホン本体に保存されてたっぽいね
WF-1000XM4は問題なかったけど、WH-1000XM3とSoundcore Space A40は接続済みのデバイス一覧で「接続エラーです。デバイスをOFFにしてからONに戻してください」という表示が出る。その通りにしても直らない
しかし問題なく接続はされていてアプリで認識もされてるし音も聞こえる。謎
Steam
ログインすると古い端末の方に認証リクエストが飛ぶので、それを認証すればOK
ただしSteam Guardの機能はそのまま古い端末側にあるので、Steam Guard -> Move Authenticatorで新しい端末の方へ移行しておく
GO タクシー
アカウント登録せずに使っていたので、古い端末でアカウント登録した上で新しい端末でログインする必要があった
GO Payの登録情報はそのまま引き継がれる様子
ニコニコ動画
新しい端末でアプリ開いたら既にログインされてた
セッションまで引継ぎしなくていいぞ
以上
終わり
昼過ぎに端末届いてこれ書いてる時間を除いて2時間半くらいはかかった
クレジットカード更新時や電話番号変わったときほどではないけど移行作業めんどくさいね、アプリ再インストールしなくていいしだいぶ楽ではあるんだろうけど
PySideで動くもの作るまでに詰まったところのメモ
PySideのインストール
> pip install PySide
おわり
QtDesignerってどこにあるの
- PySideインストールしたら一緒についてきます
C:\python33\Lib\site-packages\PySide\designer.exe
QtDesignerで作ったuiファイルをpyに変換
> pyside-uic app.ui -o app.py
すると「予期せぬエラーでたし中断するわ^^ すまんな^^;」と言われるのでランボー怒りのデバッグ出力
> pyside-uic -d app.ui -o app.py
すると
# ... 略 File "C:\python33\Lib\site-packages\pysideuic\uiparser.py", line 212, in createWidget bg_name = bg_i18n.string AttributeError: 'str' object has no attribute 'string'
ということなので
C:\python33\Lib\site-packages\pysideuic\uiparser.py
の該当行から.stringを除いてやれば動く
参考:Bug 1156 – PySide 1.1.0 compile error when assigning radio buttons into a button group
PySideのドキュメント
Qt関係は正直ググったらStack Overflowがこれでもかって程ヒットするのであまり困らなかった
- QTableViewでセルをクリックしたら一行すべて選択したい
QTableView.setSelectionBehavior(QAbstractItemView.SelectRows)
- QTableViewで複数行選択できないようにしたい
QTableView.setSelectionMode(QAbstractItemView.SingleSelection)
- QTableViewでスクロールがなんかガックガクなのですっきりなめらかにしたい
QTableView.setVerticalScrollMode(QAbstractItemView.ScrollPerPixel) QTableView.setHorizontalScrollMode(QAbstractItemView.ScrollPerPixel)
そうだexeにしよう
- cx_Freeze 4.3.2 (Python 3.3, 64 bit)をインストール
> cxfreeze app.py --target-dir dist --base-name Win32GUI --include-modules atexit
できました
できあがったexeはdllやpydを含めて31MBほど
ひえーでっかい
compile終了時にキュピーンと音を鳴らしてみる
今書いてるプロジェクトがコンパイルに1分程度かかるようになってきたので、コンパイル終了時に音で知らせてくれたら良くね?とふと思って書いた。
コンパイルの成功/失敗が音で分かるようになります。
ただし使ってると段々うっとうしくなってきます。
sbt0.13でmapRがdeprecatedになってたので修正 (2013/12/20追記)
コード
- build.sbt
// http://www.scala-sbt.org/release/docs/Detailed-Topics/Tasks.html#handling-failure //compile in Compile <<= compile in Compile mapR { compile in Compile <<= (compile in Compile result) map { case Inc(inc: Incomplete) => Sound.play("sound/NG.wav") throw inc case Value(v) => Sound.play("sound/OK.wav") v }
- project/Build.scala
import java.io.File import javax.sound.sampled._ import scala.concurrent.ops._ // 参考 // http://www.ibm.com/developerworks/jp/java/library/j-5things12/#N101D9 object Sound { def play(file: String) { spawn { val clip = AudioSystem.getClip() clip.addLineListener(new LineListener(){ def update(e: LineEvent) { if (e.getType == LineEvent.Type.STOP) { clip.synchronized { clip.notify() } } } }) val soundfile = new File(file) val stream = AudioSystem.getAudioInputStream(soundfile) clip.open(stream) clip.start() clip.synchronized { clip.wait() } clip.drain() clip.close() stream.close() } } }
waveファイルは好きなものを自前で用意して好きな所に置いてください。
上の例ではprojectのrootにsoundディレクトリを作ってそこにぶち込みました。
あまり長い音声を設定すると、前の音声が終わらないうちに次のが重なって鳴り始めてひどいことになります。
sbt0.12系はまだscala2.9なのでconcurrent.ops.spwanを使ってますが、sbt0.13からはscala2.10でfuture使えるためsbt0.13-SNAPSHOTを使ってる人はfutureで書いた方がいいです(2.10だとconcurrent.opsは丸ごとdeprecated)
scala2.10.0-RC1でscala.actorsパッケージがscala-library.jarから追い出されている
ので、2.10.0-RC1でscalaのactorを使いたい時はbuild.sbtに
scalaVersion := "2.10.0-RC1" libraryDependencies <+= scalaVersion { "org.scala-lang" % "scala-actors" % _ } //libraryDependencies <+= scalaVersion { "org.scala-lang" % "scala-reflect" % _ }
と依存を追加すればOK。
直接は使ってなかったけどscalatestがscala.actor使ってたので、test実行したら
NoClassDefFoundError: scala/actors/Actor$
って言われてびびった。
ここ見る限り2.10.0-M3からjar別になったっぽい。
上に書いたけど、scala.reflectも同じくハブられてるので注意。
環境:
scala : 2.10.0-RC1
sbt : 0.12.0
scalatest : 2.0.M4
参考:
ScalaTest IDE plug-in and 2.10 - scalatest-users
Scala2.10 以降に入るかもしれない機能まとめ - xuwei-k's blog
sbtでネイティブライブラリを使う時のメモ
2012/11/02追記
もはやうろ覚えなのですが、このエントリを書いた頃は確かsbt0.7が最新だったはずなので以下は超絶古い情報です。
もう今はsbt0.13が出そうな状況だというのに「sbt dll」とかでググると未だにこのエントリが割と上の方に出てきやがることに危機感を覚えたので追記しておきます。
This page has moved
scala - Integrating native system libraries with SBT - Stack Overflow
↑を読んだらいいと思います!!以上!!!
以下はsbt0.7の情報
ググってもほとんど情報が見つからなかったのでメモ。
プロジェクトのルートディレクトリにネイティブライブラリ(dll,so)を置いてしまうと、二度目以降のrun時に
> java.lang.UnsatisfiedLinkError: Native Library ***.dll already loaded in another classloader
と出て実行できなくなります。
sbtでは、アプリケーションは通常sbtと同じJVM上で実行される*1みたいです。sbtのソースを見るとrunする時に新しいクラスローダを作ってるので、1度目のrunでdllをロードした古いクラスローダが生き残ってて冒頭のエラーが出るのかな。ごめんなさい検証せずに適当書いてます。
これを回避するにはforkRunを使ってアプリケーションをsbtとは別のJVMで起動し、JVMオプションのjava.library.pathでdllの場所を指定してやればいいです。
ルートディレクトリ以外の適当な場所にディレクトリを作って、dllをぶちこみます。ここではlib/nativeにしておきましょう。
project/build/Project.scala のプロジェクト定義の中に以下を記述。
def nativeLibPath = System.getProperty("path.separator") + (path("lib") / "native" ) override def fork = forkRun( ("-Djava.library.path=" + System.getProperty("java.library.path") + nativeLibPath) :: Nil )
もういくらrunrunしてもUnsatisfiedLinkErrorが出ることはありません。めでたい。
これに気づかずしばらくの間、一度runしたらいちいちsbtを起動し直すというあほなことを続けてました……
参考:
Google Code Archive - Long-term storage for Google Code Project Hosting.
Scala and Sbt getting started.
SRM494 DIV2
oxx 230.12pt [rate : 690 -> 747] (+57)
Medium解けたと思ったのに思いっきり間違ってて涙目
3問ともpracticeで解いておきました。
↓以下コード
続きを読む