COM3D2のキャラデータをカスタムキャストで使う方法
投稿日:
カスタムキャスト側でCOM3D2のプリセットを再現しようとすると、色周りの設定が数値指定できない関係で難しすぎたので、何とかしようとして見つけた方法。
一撃で同じキャラを持ってこれるのでだいぶ楽。
必要なもの
- スマホとPCを繋ぐためのUSBケーブル
- バイナリエディタ
- COM3D2
- カスタムキャスト
確認環境
| Env | Ver |
|---|---|
| Android | 16 |
| COM3D2 | 2.29.4 |
| カスタムキャスト | 1.04.01 |
やり方
- スマホをUSBケーブルでPCに繋ぐ
- スマホ側の接続形態を充電からファイル転送モードにする
- カスタムキャストを開いて適当に服・体プリセットをセーブする
内部共有ストレージ/Android/data/jp.customcast.cc2/files/Presetを開き、保存したプリセットを取り出す- COM3D2を開き、カスタムキャストで使いたいキャラのエディット画面を開き、服・体プリセットを保存する
- バイナリエディタでCOM3D2のプリセットファイルを開き、
454E44AE426082を検索し、そこより先にあるバイナリコードをすべてコピーする

- カスタムキャストのプリセットを開き、
454E44AE426082を検索し、そこより先にあるバイナリコードを、先ほどコピーしたCOM3D2のバイナリコードですべて上書きし、保存する - 保存したカスタムキャストのプリセットファイルを
内部共有ストレージ/Android/data/jp.customcast.cc2/files/Presetに戻す - カスタムキャストを開き、キャラエディットからプリセットをロードする
備考
恐らくファイルヘッダ以外ほぼ互換だと思われるが、ヘッダのリプレースが面倒なのでボディを書き換えることで達成した。
当たり前だがMODとかは反映されない。
おまけ
データを個別に取得して弄れるようにしようとツールを書こうとしていたが、パラメーターが多いのと、フォーマットが異なるものがあり、解析に飽きたので調べた残骸として残しておく。
using System.Diagnostics;
using System.Text;
class Program {
static void Main(string[] args) {
string filePath = @"C:\path\to\pre_Dummymaid_20260222134142.preset";
if (!File.Exists(filePath)) {
Debug.WriteLine($"ファイルが見つかりません: {filePath}");
return;
}
byte[] fileData = File.ReadAllBytes(filePath);
Debug.WriteLine($"ファイル読み込み完了: {fileData.Length} bytes");
var obj = new ParamFinder(fileData);
var MuneL = obj.FindBodyValue("MuneL");
var MuneS = obj.FindBodyValue("MuneS");
var MuneTare = obj.FindBodyValue("MuneTare");
var RegFat = obj.FindBodyValue("RegFat");
var Hara = obj.FindBodyValue("Hara");
var RegMeet = obj.FindBodyValue("RegMeet");
var KubiScl = obj.FindBodyValue("KubiScl");
var UdeScl = obj.FindBodyValue("UdeScl");
var EyeSclX = obj.FindBodyValue("EyeSclX");
var EyeSclY = obj.FindBodyValue("EyeSclY");
var EyePosX = obj.FindBodyValue("EyePosX");
var EyePosY = obj.FindBodyValue("EyePosY");
var EyeClose = obj.FindBodyValue("EyeClose");
var EyeBallPosX = obj.FindBodyValue("EyeBallPosX");
var EyeBallPosY = obj.FindBodyValue("EyeBallPosY");
var EyeBallSclX = obj.FindBodyValue("EyeBallSclX");
var EyeBallSclY = obj.FindBodyValue("EyeBallSclY");
var FaceShape = obj.FindBodyValue("FaceShape");
var MayuY = obj.FindBodyValue("MayuY");
var HeadX = obj.FindBodyValue("HeadX");
var HeadY = obj.FindBodyValue("HeadY");
var DouPer = obj.FindBodyValue("DouPer");
var Sintyou = obj.FindBodyValue("sintyou");
var Koshi = obj.FindBodyValue("koshi");
var Kata = obj.FindBodyValue("kata");
var West = obj.FindBodyValue("west");
var MuneUpDown = obj.FindBodyValue("MuneUpDown");
var MuneYori = obj.FindBodyValue("MuneYori");
Debug.WriteLine($"MuneL: {MuneL}, MuneS: {MuneS}, MuneTare: {MuneTare}, RegFat: {RegFat}, Hara: {Hara}, RegMeet: {RegMeet}, KubiScl: {KubiScl}, UdeScl: {UdeScl}, EyeSclX: {EyeSclX}, EyeSclY: {EyeSclY}, EyePosX: {EyePosX}, EyePosY: {EyePosY}, EyeClose: {EyeClose}, EyeBallPosX: {EyeBallPosX}, EyeBallPosY: {EyeBallPosY}, EyeBallSclX: {EyeBallSclX}, EyeBallSclY: {EyeBallSclY}, FaceShape: {FaceShape}, MayuY: {MayuY}, HeadX: {HeadX}, HeadY: {HeadY}, DouPer: {DouPer}, Sintyou: {Sintyou}, Koshi: {Koshi}, Kata: {Kata}, West: {West}, MuneUpDown: {MuneUpDown}, MuneYori: {MuneYori}");
}
}
class ParamFinder {
private Byte[] FileData;
static byte[] CreateBinaryStringBytes(string text) {
using var ms = new MemoryStream();
using var writer = new BinaryWriter(ms, Encoding.UTF8);
writer.Write(text);
writer.Flush();
return ms.ToArray();
}
/// <summary>
/// バイト配列内から指定パターンの最初の出現位置を返す (見つからなければ -1)
/// </summary>
static int FindPattern(byte[] data, byte[] pattern) {
int limit = data.Length - pattern.Length;
for (int i = 0; i <= limit; i++) {
bool match = true;
for (int j = 0; j < pattern.Length; j++) {
if (data[i + j] != pattern[j]) {
match = false;
break;
}
}
if (match)
return i;
}
return -1;
}
public ParamFinder(Byte[] fileData) {
this.FileData = fileData;
}
public int FindBodyValue(string subject) {
byte[] signature = CreateBinaryStringBytes(subject);
int sigOffset = FindPattern(this.FileData, signature);
if (sigOffset < 0) {
throw new Exception($"エラー: データセクションのシグネチャ '${subject}' が見つかりませんでした。");
}
using var ms = new MemoryStream(this.FileData, sigOffset, this.FileData.Length - sigOffset);
using var reader = new BinaryReader(ms, Encoding.UTF8);
reader.ReadString();
reader.ReadString();
reader.ReadInt32();
reader.ReadInt32();
reader.ReadString();
reader.ReadInt32();
reader.ReadInt32();
return reader.ReadInt32();
}
}
