支持解锁 Live, 支持自定义角色, 支持换头, 支持 FOV 调整
This commit is contained in:
parent
fb65a34684
commit
acac544183
@ -16,7 +16,7 @@
|
||||
- [ ] 卡片信息、TIP等部分的文本 hook
|
||||
- [x] 自定义配置
|
||||
- [ ] 更多类型的文件替换
|
||||
- [ ] 自由视角 FOV 调整
|
||||
- [x] 自由视角 FOV 调整
|
||||
|
||||
... and more
|
||||
|
||||
|
@ -75,13 +75,14 @@ namespace GakumasLocal::HookMain {
|
||||
|
||||
DEFINE_HOOK(void, Internal_LogException, (void* ex, void* obj)) {
|
||||
Internal_LogException_Orig(ex, obj);
|
||||
// Log::LogFmt(ANDROID_LOG_VERBOSE, "UnityLog - Internal_LogException");
|
||||
static auto Exception_ToString = Il2cppUtils::GetMethod("mscorlib.dll", "System", "Exception", "ToString");
|
||||
Log::LogUnityLog(ANDROID_LOG_ERROR, "UnityLog - Internal_LogException:\n%s", Exception_ToString->Invoke<Il2cppString*>(ex)->ToString().c_str());
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, Internal_Log, (int logType, int logOption, UnityResolve::UnityType::String* content, void* context)) {
|
||||
Internal_Log_Orig(logType, logOption, content, context);
|
||||
// 2022.3.21f1
|
||||
// Log::LogFmt(ANDROID_LOG_VERBOSE, "UnityLog - Internal_Log: %s", content->ToString().c_str());
|
||||
Log::LogUnityLog(ANDROID_LOG_VERBOSE, "Internal_Log:\n%s", content->ToString().c_str());
|
||||
}
|
||||
|
||||
UnityResolve::UnityType::Camera* mainCameraCache = nullptr;
|
||||
@ -97,6 +98,39 @@ namespace GakumasLocal::HookMain {
|
||||
cameraTransformCache = mainCameraCache->GetTransform();
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, Unity_set_fieldOfView, (UnityResolve::UnityType::Camera* _this, float value)) {
|
||||
if (Config::enableFreeCamera) {
|
||||
if (_this == mainCameraCache) {
|
||||
value = GKCamera::baseCamera.fov;
|
||||
}
|
||||
}
|
||||
Unity_set_fieldOfView_Orig(_this, value);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(float, Unity_get_fieldOfView, (UnityResolve::UnityType::Camera* _this)) {
|
||||
if (Config::enableFreeCamera) {
|
||||
if (_this == mainCameraCache) {
|
||||
static auto get_orthographic = reinterpret_cast<bool (*)(void*)>(Il2cppUtils::il2cpp_resolve_icall(
|
||||
"UnityEngine.Camera::get_orthographic()"
|
||||
));
|
||||
static auto set_orthographic = reinterpret_cast<bool (*)(void*, bool)>(Il2cppUtils::il2cpp_resolve_icall(
|
||||
"UnityEngine.Camera::set_orthographic(System.Boolean)"
|
||||
));
|
||||
|
||||
for (const auto& i : UnityResolve::UnityType::Camera::GetAllCamera()) {
|
||||
// Log::DebugFmt("get_orthographic: %d", get_orthographic(i));
|
||||
// set_orthographic(i, false);
|
||||
Unity_set_fieldOfView_Orig(i, GKCamera::baseCamera.fov);
|
||||
}
|
||||
Unity_set_fieldOfView_Orig(_this, GKCamera::baseCamera.fov);
|
||||
|
||||
// Log::DebugFmt("main - get_orthographic: %d", get_orthographic(_this));
|
||||
return GKCamera::baseCamera.fov;
|
||||
}
|
||||
}
|
||||
return Unity_get_fieldOfView_Orig(_this);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, Unity_set_position_Injected, (UnityResolve::UnityType::Transform* _this, UnityResolve::UnityType::Vector3* data)) {
|
||||
if (Config::enableFreeCamera) {
|
||||
CheckAndUpdateMainCamera();
|
||||
@ -123,13 +157,21 @@ namespace GakumasLocal::HookMain {
|
||||
"UnityEngine.Transform::Internal_LookAt_Injected(UnityEngine.Vector3&,UnityEngine.Vector3&)"));
|
||||
static auto worldUp = UnityResolve::UnityType::Vector3(0, 1, 0);
|
||||
lookat_injected(_this, &origCameraLookat, &worldUp);
|
||||
// TODO 相机 FOV
|
||||
// Log::DebugFmt("fov: %f, target: %f", Unity_get_fieldOfView_Orig(mainCameraCache), GKCamera::baseCamera.fov);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return Unity_set_rotation_Injected_Orig(_this, value);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, EndCameraRendering, (void* ctx, void* camera, void* method)) {
|
||||
EndCameraRendering_Orig(ctx, camera, method);
|
||||
|
||||
if (Config::enableFreeCamera && mainCameraCache) {
|
||||
Unity_set_fieldOfView_Orig(mainCameraCache, GKCamera::baseCamera.fov);
|
||||
}
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, Unity_set_targetFrameRate, (int value)) {
|
||||
const auto configFps = Config::targetFrameRate;
|
||||
return Unity_set_targetFrameRate_Orig(configFps == 0 ? value: configFps);
|
||||
@ -321,6 +363,76 @@ namespace GakumasLocal::HookMain {
|
||||
// return UnityResolve::UnityType::String::New("[I18]" + ret->ToString());
|
||||
}
|
||||
|
||||
DEFINE_HOOK(void, PictureBookLiveThumbnailView_SetData, (void* _this, void* liveData, bool isUnlocked, bool isNew)) {
|
||||
// Log::DebugFmt("PictureBookLiveThumbnailView_SetData: isUnlocked: %d, isNew: %d", isUnlocked, isNew);
|
||||
if (Config::unlockAllLive) {
|
||||
isUnlocked = true;
|
||||
}
|
||||
PictureBookLiveThumbnailView_SetData_Orig(_this, liveData, isUnlocked, isNew);
|
||||
}
|
||||
|
||||
|
||||
DEFINE_HOOK(void*, PictureBookLiveSelectScreenPresenter_MoveLiveScene, (void* _this, void* produceLive,
|
||||
Il2cppString* characterId, Il2cppString* costumeId, Il2cppString* costumeHeadId)) {
|
||||
|
||||
Log::InfoFmt("MoveLiveScene: characterId: %s, costumeId: %s, costumeHeadId: %s,",
|
||||
characterId->ToString().c_str(), costumeId->ToString().c_str(), costumeHeadId->ToString().c_str());
|
||||
|
||||
/*
|
||||
characterId: hski, costumeId: hski-cstm-0002, costumeHeadId: costume_head_hski-cstm-0002,
|
||||
characterId: shro, costumeId: shro-cstm-0006, costumeHeadId: costume_head_shro-cstm-0006,
|
||||
*/
|
||||
|
||||
if (Config::enableLiveCustomeDress) {
|
||||
// 修改 LiveFixedData_GetCharacter 可以更改 Loading 角色和演唱者名字,而不变更实际登台人
|
||||
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(_this, produceLive, characterId,
|
||||
Config::liveCustomeCostumeId.empty() ? costumeId : Il2cppString::New(Config::liveCustomeCostumeId),
|
||||
Config::liveCustomeHeadId.empty() ? costumeHeadId : Il2cppString::New(Config::liveCustomeHeadId));
|
||||
}
|
||||
|
||||
return PictureBookLiveSelectScreenPresenter_MoveLiveScene_Orig(_this, produceLive, characterId, costumeId, costumeHeadId);
|
||||
}
|
||||
|
||||
// std::string lastMusicId;
|
||||
DEFINE_HOOK(void, PictureBookLiveSelectScreenPresenter_OnSelectMusic, (void* _this, void* itemModel, bool isFirst, void* mtd)) {
|
||||
/* // 修改角色后,Live 结束返回时, itemModel 为 null
|
||||
Log::DebugFmt("OnSelectMusic itemModel at %p", itemModel);
|
||||
|
||||
static auto GetMusic = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.OutGame",
|
||||
"PlaylistMusicContext", "GetMusic");
|
||||
static auto GetCurrMusic = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
|
||||
"PictureBookLiveSelectMusicListItemModel", "get_Music");
|
||||
static auto GetMusicId = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.Common.Proto.Client.Master",
|
||||
"Music", "get_Id");
|
||||
|
||||
static auto PictureBookLiveSelectMusicListItemModel_klass = Il2cppUtils::GetClass("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
|
||||
"PictureBookLiveSelectMusicListItemModel");
|
||||
static auto PictureBookLiveSelectMusicListItemModel_ctor = Il2cppUtils::GetMethod("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
|
||||
"PictureBookLiveSelectMusicListItemModel", ".ctor", {"*", "*"});
|
||||
|
||||
if (!itemModel) {
|
||||
Log::DebugFmt("OnSelectMusic block", itemModel);
|
||||
auto music = GetMusic->Invoke<void*>(lastMusicId);
|
||||
auto newItemModel = PictureBookLiveSelectMusicListItemModel_klass->New<void*>();
|
||||
PictureBookLiveSelectMusicListItemModel_ctor->Invoke<void>(newItemModel, music, false);
|
||||
|
||||
return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(_this, newItemModel, isFirst, mtd);
|
||||
}
|
||||
|
||||
if (itemModel) {
|
||||
auto currMusic = GetCurrMusic->Invoke<void*>(itemModel);
|
||||
auto musicId = GetMusicId->Invoke<Il2cppString*>(currMusic);
|
||||
lastMusicId = musicId->ToString();
|
||||
}*/
|
||||
if (!itemModel) return;
|
||||
return PictureBookLiveSelectScreenPresenter_OnSelectMusic_Orig(_this, itemModel, isFirst, mtd);
|
||||
}
|
||||
|
||||
DEFINE_HOOK(bool, VLDOF_IsActive, (void* _this)) {
|
||||
if (Config::enableFreeCamera) return false;
|
||||
return VLDOF_IsActive_Orig(_this);
|
||||
}
|
||||
|
||||
void StartInjectFunctions() {
|
||||
const auto hookInstaller = Plugin::GetInstance().GetHookInstaller();
|
||||
UnityResolve::Init(xdl_open(hookInstaller->m_il2cppLibraryPath.c_str(), RTLD_NOW), UnityResolve::Mode::Il2Cpp);
|
||||
@ -361,6 +473,21 @@ namespace GakumasLocal::HookMain {
|
||||
Il2cppUtils::GetMethodPointer("Octo.dll", "Octo",
|
||||
"OnDownloadProgress", "Invoke"));
|
||||
|
||||
ADD_HOOK(PictureBookLiveThumbnailView_SetData,
|
||||
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame.PictureBook",
|
||||
"PictureBookLiveThumbnailView", "SetData"));
|
||||
|
||||
ADD_HOOK(PictureBookLiveSelectScreenPresenter_MoveLiveScene,
|
||||
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
|
||||
"PictureBookLiveSelectScreenPresenter", "MoveLiveScene"));
|
||||
ADD_HOOK(PictureBookLiveSelectScreenPresenter_OnSelectMusic,
|
||||
Il2cppUtils::GetMethodPointer("Assembly-CSharp.dll", "Campus.OutGame",
|
||||
"PictureBookLiveSelectScreenPresenter", "OnSelectMusic"));
|
||||
|
||||
ADD_HOOK(VLDOF_IsActive,
|
||||
Il2cppUtils::GetMethodPointer("Unity.RenderPipelines.Universal.Runtime.dll", "VL.Rendering",
|
||||
"VLDOF", "IsActive"));
|
||||
|
||||
ADD_HOOK(Internal_LogException, Il2cppUtils::il2cpp_resolve_icall(
|
||||
"UnityEngine.DebugLogHandler::Internal_LogException(System.Exception,UnityEngine.Object)"));
|
||||
ADD_HOOK(Internal_Log, Il2cppUtils::il2cpp_resolve_icall(
|
||||
@ -370,8 +497,14 @@ namespace GakumasLocal::HookMain {
|
||||
"UnityEngine.Transform::set_position_Injected(UnityEngine.Vector3&)"));
|
||||
ADD_HOOK(Unity_set_rotation_Injected, Il2cppUtils::il2cpp_resolve_icall(
|
||||
"UnityEngine.Transform::set_rotation_Injected(UnityEngine.Quaternion&)"));
|
||||
ADD_HOOK(Unity_get_fieldOfView, Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine",
|
||||
"Camera", "get_fieldOfView"));
|
||||
ADD_HOOK(Unity_set_fieldOfView, Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine",
|
||||
"Camera", "set_fieldOfView"));
|
||||
ADD_HOOK(Unity_set_targetFrameRate, Il2cppUtils::il2cpp_resolve_icall(
|
||||
"UnityEngine.Application::set_targetFrameRate(System.Int32)"));
|
||||
ADD_HOOK(EndCameraRendering, Il2cppUtils::GetMethodPointer("UnityEngine.CoreModule.dll", "UnityEngine.Rendering",
|
||||
"RenderPipeline", "EndCameraRendering"));
|
||||
}
|
||||
// 77 2640 5000
|
||||
|
||||
|
@ -139,4 +139,28 @@ namespace GakumasLocal::Log {
|
||||
|
||||
Debug(result.c_str());
|
||||
}
|
||||
|
||||
void LogUnityLog(int prio, const char* fmt, ...) {
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
va_list args_copy;
|
||||
va_copy(args_copy, args);
|
||||
|
||||
// 计算格式化后的字符串长度
|
||||
int size = vsnprintf(nullptr, 0, fmt, args_copy) + 1; // 加上额外的终止符空间
|
||||
va_end(args_copy);
|
||||
|
||||
// 动态分配缓冲区
|
||||
char* buffer = new char[size];
|
||||
|
||||
// 格式化字符串
|
||||
vsnprintf(buffer, size, fmt, args);
|
||||
|
||||
va_end(args);
|
||||
|
||||
std::string result(buffer);
|
||||
delete[] buffer; // 释放缓冲区
|
||||
|
||||
__android_log_write(prio, "GakumasLog", result.c_str());
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
#define GAKUMAS_LOCALIFY_LOG_H
|
||||
|
||||
namespace GakumasLocal::Log {
|
||||
void LogUnityLog(int prio, const char* fmt, ...);
|
||||
void LogFmt(int prio, const char* fmt, ...);
|
||||
void Info(const char* msg);
|
||||
void InfoFmt(const char* fmt, ...);
|
||||
|
@ -8,14 +8,25 @@ namespace GakumasLocal::Config {
|
||||
bool enabled = true;
|
||||
bool enableFreeCamera = false;
|
||||
int targetFrameRate = 0;
|
||||
bool unlockAllLive = false;
|
||||
|
||||
bool enableLiveCustomeDress = false;
|
||||
std::string liveCustomeHeadId = "";
|
||||
std::string liveCustomeCostumeId = "";
|
||||
|
||||
void LoadConfig(const std::string& configStr) {
|
||||
try {
|
||||
const auto config = nlohmann::json::parse(configStr);
|
||||
|
||||
enabled = config["enabled"];
|
||||
targetFrameRate = config["targetFrameRate"];
|
||||
enableFreeCamera = config["enableFreeCamera"];
|
||||
#define GetConfigItem(name) if (config.contains(#name)) name = config[#name]
|
||||
|
||||
GetConfigItem(enabled);
|
||||
GetConfigItem(targetFrameRate);
|
||||
GetConfigItem(enableFreeCamera);
|
||||
GetConfigItem(unlockAllLive);
|
||||
GetConfigItem(enableLiveCustomeDress);
|
||||
GetConfigItem(liveCustomeHeadId);
|
||||
GetConfigItem(liveCustomeCostumeId);
|
||||
|
||||
}
|
||||
catch (std::exception& e) {
|
||||
|
@ -8,6 +8,11 @@ namespace GakumasLocal::Config {
|
||||
extern bool enabled;
|
||||
extern bool enableFreeCamera;
|
||||
extern int targetFrameRate;
|
||||
extern bool unlockAllLive;
|
||||
|
||||
extern bool enableLiveCustomeDress;
|
||||
extern std::string liveCustomeHeadId;
|
||||
extern std::string liveCustomeCostumeId;
|
||||
|
||||
void LoadConfig(const std::string& configStr);
|
||||
}
|
||||
|
@ -19,6 +19,10 @@ interface ConfigListener {
|
||||
fun onEnabledChanged(value: Boolean)
|
||||
fun onEnableFreeCameraChanged(value: Boolean)
|
||||
fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
||||
fun onUnlockAllLiveChanged(value: Boolean)
|
||||
fun onLiveCustomeDressChanged(value: Boolean)
|
||||
fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
||||
fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int)
|
||||
}
|
||||
|
||||
class MainActivity : AppCompatActivity(), ConfigListener {
|
||||
@ -85,6 +89,11 @@ class MainActivity : AppCompatActivity(), ConfigListener {
|
||||
saveConfig()
|
||||
}
|
||||
|
||||
override fun onUnlockAllLiveChanged(value: Boolean) {
|
||||
config.unlockAllLive = value
|
||||
saveConfig()
|
||||
}
|
||||
|
||||
override fun onTargetFpsChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
try {
|
||||
val valueStr = s.toString()
|
||||
@ -102,6 +111,21 @@ class MainActivity : AppCompatActivity(), ConfigListener {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLiveCustomeDressChanged(value: Boolean) {
|
||||
config.enableLiveCustomeDress = value
|
||||
saveConfig()
|
||||
}
|
||||
|
||||
override fun onLiveCustomeCostumeIdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
config.liveCustomeCostumeId = s.toString()
|
||||
saveConfig()
|
||||
}
|
||||
|
||||
override fun onLiveCustomeHeadIdChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
config.liveCustomeHeadId = s.toString()
|
||||
saveConfig()
|
||||
}
|
||||
|
||||
override fun onClickStartGame() {
|
||||
val intent = Intent().apply {
|
||||
setClassName("com.bandainamcoent.idolmaster_gakuen", "com.google.firebase.MessagingUnityPlayerActivity")
|
||||
|
@ -3,6 +3,10 @@ package io.github.chinosk.gakumas.localify.models
|
||||
data class GakumasConfig(
|
||||
var enabled: Boolean = true,
|
||||
var enableFreeCamera: Boolean = false,
|
||||
var targetFrameRate: Int = 0
|
||||
var targetFrameRate: Int = 0,
|
||||
var unlockAllLive: Boolean = false,
|
||||
var enableLiveCustomeDress: Boolean = false,
|
||||
var liveCustomeHeadId: String = "",
|
||||
var liveCustomeCostumeId: String = ""
|
||||
)
|
||||
|
||||
|
@ -96,6 +96,81 @@
|
||||
android:text="@string/enable_free_camera" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/SwitchUnlockAllLive"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:checked="@={config.unlockAllLive}"
|
||||
android:onCheckedChanged="@{(view, value) -> listener.onUnlockAllLiveChanged(value)}"
|
||||
android:text="@string/unlockAllLive" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/SwitchLiveUseCustomeDress"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
android:checked="@={config.enableLiveCustomeDress}"
|
||||
android:onCheckedChanged="@{(view, value) -> listener.onLiveCustomeDressChanged(value)}"
|
||||
android:text="@string/liveUseCustomeDress" />
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
app:boxBackgroundColor="@android:color/transparent"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="@string/live_costume_head_id" >
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editTextLiveCustomeCharaId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:ems="10"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:text="@={config.liveCustomeHeadId}"
|
||||
android:onTextChanged="@{(s, st, b, a) -> listener.onLiveCustomeHeadIdChanged(s, st, b, a)}"/>
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</TableRow>
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="48dp"
|
||||
app:boxBackgroundColor="@android:color/transparent"
|
||||
android:background="@android:color/transparent"
|
||||
android:hint="@string/live_custome_dress_id" >
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/editTextLiveCustomeCostumeId"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:ems="10"
|
||||
android:paddingStart="0dp"
|
||||
android:paddingEnd="0dp"
|
||||
android:paddingBottom="0dp"
|
||||
android:text="@={config.liveCustomeCostumeId}"
|
||||
android:onTextChanged="@{(s, st, b, a) -> listener.onLiveCustomeCostumeIdChanged(s, st, b, a)}"/>
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</TableRow>
|
||||
|
||||
|
||||
<TableRow
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
@ -5,4 +5,8 @@
|
||||
<string name="enable_free_camera">启用自由视角(可热重载; 需使用实体键盘)</string>
|
||||
<string name="start_game">以上述配置启动游戏/重载配置</string>
|
||||
<string name="setFpsTitle">最大 FPS (0 为保持游戏原设置)</string>
|
||||
<string name="unlockAllLive">解锁所有 Live</string>
|
||||
<string name="liveUseCustomeDress">Live 使用自定义角色</string>
|
||||
<string name="live_costume_head_id">Live 自定义头部 ID (例: costume_head_hski-cstm-0002)</string>
|
||||
<string name="live_custome_dress_id">Live 自定义服装 ID (例: hski-cstm-0002)</string>
|
||||
</resources>
|
Loading…
x
Reference in New Issue
Block a user