Add support for exporting prop, compatible with the new version, add the EnableRootMotion option (#143)
* update BillboardBuilder * add a new enum value * Add the EnableRootMotion option to support scenarios where the character is not within the camera view * Add support for exporting prop * update * compatible with the new version 2 * update --------- Co-authored-by: 左右看 <1918891542@qq.com>
This commit is contained in:
Binary file not shown.
@@ -1,5 +1,5 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 654feed001747154f9fc3ac6f1ba11a7
|
||||
guid: 26e1318ece7f74447bfe09802527f226
|
||||
PluginImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
@@ -14,20 +14,14 @@ PluginImporter:
|
||||
- first:
|
||||
Any:
|
||||
second:
|
||||
enabled: 0
|
||||
enabled: 1
|
||||
settings: {}
|
||||
- first:
|
||||
Editor: Editor
|
||||
second:
|
||||
enabled: 1
|
||||
enabled: 0
|
||||
settings:
|
||||
DefaultValueInitialized: true
|
||||
- first:
|
||||
Standalone: OSXUniversal
|
||||
second:
|
||||
enabled: 1
|
||||
settings:
|
||||
CPU: AnyCPU
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -29,7 +29,7 @@ AnimatorState:
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_Name: motion_1
|
||||
m_Name: motion_p
|
||||
m_Speed: 1
|
||||
m_CycleOffset: 0
|
||||
m_Transitions:
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,114 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0f7f1a85919ef4c419dfcd825b113de6
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 13
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
@@ -0,0 +1,114 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 64ce9962a1619a948a98ccddacd0416c
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 12
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
flipGreenChannel: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
ignoreMipmapLimit: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: 1
|
||||
aniso: 1
|
||||
mipBias: 0
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 0
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 23, y: 27, z: 23, w: 21}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
swizzle: 50462976
|
||||
cookieLightType: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
- serializedVersion: 3
|
||||
buildTarget: Standalone
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
ignorePlatformSupport: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 1537655665
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
nameFileIdTable: {}
|
||||
mipmapLimitGroupName:
|
||||
pSDRemoveMatte: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
+3601
-166
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,27 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
public static class AssetBundleDecryptor
|
||||
{
|
||||
public static byte[] DecryptFileToBytes(string inputFilePath, byte[] Keys)
|
||||
{
|
||||
if (string.IsNullOrEmpty(inputFilePath))
|
||||
throw new ArgumentNullException(nameof(inputFilePath));
|
||||
if (!File.Exists(inputFilePath))
|
||||
throw new FileNotFoundException("Input file not found", inputFilePath);
|
||||
if (Keys == null)
|
||||
throw new ArgumentException("Keys must not be null or empty", nameof(Keys));
|
||||
|
||||
byte[] data = File.ReadAllBytes(inputFilePath);
|
||||
|
||||
if (data.Length <= 256)
|
||||
return data;
|
||||
|
||||
for (int i = 256; i < data.Length; ++i)
|
||||
{
|
||||
data[i] ^= Keys[i % Keys.Length];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c9ca478441998cb449da2a875dc99b51
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
public class Config
|
||||
@@ -9,6 +11,15 @@ public class Config
|
||||
public static Config Instance;
|
||||
public string Version = "";
|
||||
|
||||
public string DBBaseKeyTip = "Base key to read game database files";
|
||||
public string DBBaseKeyText;
|
||||
|
||||
public string DBKeyTip = "Key to read game database files";
|
||||
public string DBKeyText;
|
||||
|
||||
public string ABKeyTip = "Key to read asset bundles";
|
||||
public string ABKeyText;
|
||||
|
||||
public string LanguageTip = "Affects Uma names on the list. Language options: 0 - En, 1 - Jp";
|
||||
public Language Language = Language.En;
|
||||
|
||||
@@ -94,6 +105,29 @@ public class Config
|
||||
new MorphConvertConfig("口横狭い", new string[] { "Mouth_55_0" })
|
||||
};
|
||||
|
||||
[NonSerialized]
|
||||
public byte[] DBBaseKey = new byte[]
|
||||
{
|
||||
0xF1, 0x70, 0xCE, 0xA4, 0xDF, 0xCE, 0xA3, 0xE1,
|
||||
0xA5, 0xD8, 0xC7, 0x0B, 0xD1, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
[NonSerialized]
|
||||
public byte[] DBKey = new byte[]
|
||||
{
|
||||
0x6D, 0x5B, 0x65, 0x33, 0x63, 0x36,
|
||||
0x63, 0x25, 0x54, 0x71, 0x2D, 0x73,
|
||||
0x50, 0x53, 0x63, 0x38, 0x6D, 0x34,
|
||||
0x37, 0x7B, 0x35, 0x63, 0x70, 0x23,
|
||||
0x37, 0x34, 0x53, 0x29, 0x73, 0x43,
|
||||
0x36, 0x33
|
||||
};
|
||||
[NonSerialized]
|
||||
public byte[] ABKey = new byte[]
|
||||
{
|
||||
0x53, 0x2B, 0x46, 0x31, 0xE4, 0xA7, 0xB9, 0x47, 0x3E, 0x7C, 0xFB
|
||||
};
|
||||
|
||||
|
||||
public Config()
|
||||
{
|
||||
@@ -111,6 +145,9 @@ public class Config
|
||||
|
||||
if (!File.Exists(configPath))
|
||||
{
|
||||
DBBaseKeyText = ByteArrayToHex(DBBaseKey);
|
||||
DBKeyText = ByteArrayToHex(DBKey);
|
||||
ABKeyText = ByteArrayToHex(ABKey);
|
||||
File.WriteAllText(configPath, JsonUtility.ToJson(this, true));
|
||||
}
|
||||
else
|
||||
@@ -118,6 +155,31 @@ public class Config
|
||||
try
|
||||
{
|
||||
JsonUtility.FromJsonOverwrite(File.ReadAllText(configPath), this);
|
||||
|
||||
if (string.IsNullOrEmpty(DBBaseKeyText))
|
||||
{
|
||||
DBBaseKeyText = ByteArrayToHex(DBBaseKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
DBBaseKey = HexToByteArray(DBBaseKeyText);
|
||||
}
|
||||
if (string.IsNullOrEmpty(DBKeyText))
|
||||
{
|
||||
DBKeyText = ByteArrayToHex(DBKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
DBKey = HexToByteArray(DBKeyText);
|
||||
}
|
||||
if (string.IsNullOrEmpty(ABKeyText))
|
||||
{
|
||||
ABKeyText = ByteArrayToHex(ABKey);
|
||||
}
|
||||
else
|
||||
{
|
||||
ABKey = HexToByteArray(ABKeyText);
|
||||
}
|
||||
File.WriteAllText(configPath, JsonUtility.ToJson(this, true));
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -126,7 +188,6 @@ public class Config
|
||||
MainPath = $@"{Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "Low"}\Cygames\umamusume";
|
||||
}
|
||||
}
|
||||
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
@@ -138,6 +199,32 @@ public class Config
|
||||
UmaViewerUI.Instance.ShowMessage("The configuration has changed. Please restart the application.", UIMessageType.Default);
|
||||
}
|
||||
}
|
||||
|
||||
private string ByteArrayToHex(byte[] byteArray)
|
||||
{
|
||||
if (byteArray == null) return string.Empty;
|
||||
|
||||
StringBuilder sb = new StringBuilder(byteArray.Length * 2);
|
||||
foreach (byte b in byteArray)
|
||||
{
|
||||
sb.AppendFormat("{0:X2}", b);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private byte[] HexToByteArray(string hex)
|
||||
{
|
||||
if (string.IsNullOrEmpty(hex) || hex.Length % 2 != 0)
|
||||
return new byte[0];
|
||||
|
||||
byte[] result = new byte[hex.Length / 2];
|
||||
for (int i = 0; i < result.Length; i++)
|
||||
{
|
||||
string byteString = hex.Substring(i * 2, 2);
|
||||
result[i] = byte.Parse(byteString, System.Globalization.NumberStyles.HexNumber);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Language
|
||||
|
||||
@@ -4,59 +4,85 @@ using UnityEngine;
|
||||
|
||||
public class BillboardBuilder : MonoBehaviour
|
||||
{
|
||||
public static Mesh CreateSphereMesh(float radius, float uvRadius, Vector2 uvCenter, int longitude = 6, int latitude = 6)
|
||||
|
||||
|
||||
|
||||
//修改(Billboard的导出相关)
|
||||
public static Mesh CreateSphereMesh(float radius, int subdiv, float uvRadius, Vector2 uvCenter)
|
||||
{
|
||||
Mesh mesh = new Mesh();
|
||||
Vector3[] vertices = new Vector3[(longitude + 1) * (latitude + 1)];
|
||||
Vector2[] uvs = new Vector2[vertices.Length];
|
||||
int[] triangles = new int[longitude * latitude * 6];
|
||||
|
||||
int vert = 0, tri = 0;
|
||||
for (int lat = 0; lat <= latitude; lat++)
|
||||
var t = (1f + Mathf.Sqrt(5f)) / 2f;
|
||||
var baseVerts = new List<Vector3>
|
||||
{
|
||||
float a1 = Mathf.PI * lat / latitude;
|
||||
float y = Mathf.Cos(a1);
|
||||
float sin1 = Mathf.Sin(a1);
|
||||
|
||||
for (int lon = 0; lon <= longitude; lon++)
|
||||
{
|
||||
float a2 = 2 * Mathf.PI * lon / longitude;
|
||||
float x = sin1 * Mathf.Cos(a2);
|
||||
float z = sin1 * Mathf.Sin(a2);
|
||||
|
||||
vertices[vert] = new Vector3(x, y, z) * radius;
|
||||
//uvs[vert] = new Vector2((float)lon / longitude, (float)lat / latitude);
|
||||
|
||||
Vector3 p = vertices[vert].normalized;
|
||||
uvs[vert] = new Vector2(p.x, p.z) * uvRadius + uvCenter;
|
||||
|
||||
//uvs[vert] = new Vector2(p.x, p.z) * 0.5f + new Vector2(0.5f, 0.5f);
|
||||
|
||||
if (lat < latitude && lon < longitude)
|
||||
{
|
||||
int current = vert;
|
||||
int next = vert + longitude + 1;
|
||||
|
||||
triangles[tri++] = current;
|
||||
triangles[tri++] = current + 1;
|
||||
triangles[tri++] = next;
|
||||
|
||||
triangles[tri++] = current + 1;
|
||||
triangles[tri++] = next + 1;
|
||||
triangles[tri++] = next;
|
||||
}
|
||||
|
||||
vert++;
|
||||
}
|
||||
new Vector3(-1, t, 0), new Vector3( 1, t, 0), new Vector3(-1, -t, 0), new Vector3( 1, -t, 0),
|
||||
new Vector3( 0, -1, t), new Vector3( 0, 1, t), new Vector3( 0, -1, -t), new Vector3( 0, 1, -t),
|
||||
new Vector3( t, 0, -1), new Vector3( t, 0, 1), new Vector3(-t, 0, -1), new Vector3(-t, 0, 1),
|
||||
};
|
||||
var faces = new List<int[]>
|
||||
{
|
||||
new[]{0,11,5}, new[]{0,5,1}, new[]{0,1,7}, new[]{0,7,10}, new[]{0,10,11},
|
||||
new[]{1,5,9}, new[]{5,11,4}, new[]{11,10,2}, new[]{10,7,6}, new[]{7,1,8},
|
||||
new[]{3,9,4}, new[]{3,4,2}, new[]{3,2,6}, new[]{3,6,8}, new[]{3,8,9},
|
||||
new[]{4,9,5}, new[]{2,4,11}, new[]{6,2,10}, new[]{8,6,7}, new[]{9,8,1}
|
||||
};
|
||||
|
||||
for (int i = 0; i < baseVerts.Count; i++) baseVerts[i] = baseVerts[i].normalized;
|
||||
|
||||
var vertList = new List<Vector3>(baseVerts);
|
||||
var triList = new List<int>();
|
||||
foreach (var f in faces) { triList.Add(f[0]); triList.Add(f[1]); triList.Add(f[2]); }
|
||||
|
||||
var midpointCache = new Dictionary<long, int>();
|
||||
int GetMidpoint(int a, int b)
|
||||
{
|
||||
long key = a < b ? ((long)a << 32) + b : ((long)b << 32) + a;
|
||||
if (midpointCache.TryGetValue(key, out var idx)) return idx;
|
||||
var m = (vertList[a] + vertList[b]).normalized;
|
||||
idx = vertList.Count;
|
||||
vertList.Add(m);
|
||||
midpointCache[key] = idx;
|
||||
return idx;
|
||||
}
|
||||
|
||||
mesh.vertices = vertices;
|
||||
mesh.uv = uvs;
|
||||
mesh.triangles = triangles;
|
||||
mesh.RecalculateNormals();
|
||||
|
||||
for (int s = 0; s < subdiv; s++)
|
||||
{
|
||||
var newTris = new List<int>(triList.Count * 4);
|
||||
for (int i = 0; i < triList.Count; i += 3)
|
||||
{
|
||||
int v0 = triList[i], v1 = triList[i+1], v2 = triList[i+2];
|
||||
int a = GetMidpoint(v0, v1);
|
||||
int b = GetMidpoint(v1, v2);
|
||||
int c = GetMidpoint(v2, v0);
|
||||
newTris.AddRange(new[]{ v0, a, c, v1, b, a, v2, c, b, a, b, c });
|
||||
}
|
||||
triList = newTris;
|
||||
}
|
||||
|
||||
Vector3[] vertices = new Vector3[vertList.Count];
|
||||
Vector3[] normals = new Vector3[vertList.Count];
|
||||
Vector2[] uvs = new Vector2[vertList.Count];
|
||||
for (int i = 0; i < vertList.Count; i++)
|
||||
{
|
||||
Vector3 p = vertList[i];
|
||||
vertices[i] = p * radius;
|
||||
normals[i] = p;
|
||||
uvs[i] = new Vector2(p.x, p.z) * uvRadius + uvCenter;
|
||||
}
|
||||
|
||||
var mesh = new Mesh();
|
||||
if (vertices.Length > 65535) mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
|
||||
mesh.vertices = vertices;
|
||||
mesh.normals = normals;
|
||||
mesh.uv = uvs;
|
||||
mesh.triangles = triList.ToArray();
|
||||
mesh.RecalculateBounds();
|
||||
|
||||
return mesh;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static void BuildBillboard(UmaContainerCharacter container)
|
||||
{
|
||||
int groupSize = 4;
|
||||
@@ -119,16 +145,27 @@ public class BillboardBuilder : MonoBehaviour
|
||||
// Create
|
||||
Mesh sphereMesh;
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//修改(Billboard的导出相关)
|
||||
if (radius < 0.003)
|
||||
{
|
||||
sphereMesh = CreateSphereMesh(radius, uv_radius, uv_center);
|
||||
sphereMesh = CreateSphereMesh(radius, 1, uv_radius, uv_center);
|
||||
}
|
||||
else
|
||||
{
|
||||
sphereMesh = CreateSphereMesh(radius, uv_radius, uv_center, 12, 9);
|
||||
sphereMesh = CreateSphereMesh(radius, 2, uv_radius, uv_center);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
int vertexOffset = finalVertices.Count;
|
||||
|
||||
foreach (Vector3 v in sphereMesh.vertices)
|
||||
@@ -165,6 +202,14 @@ public class BillboardBuilder : MonoBehaviour
|
||||
newSMR.material = s.sharedMaterial;
|
||||
|
||||
Debug.Log($"Generated {sphereCount} small spheres and combined them into a single mesh.");
|
||||
|
||||
|
||||
|
||||
//修改(Billboard的导出相关)
|
||||
//ApplyBillboardUVOffset(s);
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -179,4 +224,36 @@ public class BillboardBuilder : MonoBehaviour
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//修改(Billboard的导出相关)
|
||||
private static void ApplyBillboardUVOffset(SkinnedMeshRenderer smr)
|
||||
{
|
||||
var mesh = smr.sharedMesh;
|
||||
if (mesh == null || mesh.uv2 == null || mesh.uv2.Length == 0) return;
|
||||
|
||||
var newMesh = UnityEngine.Object.Instantiate(mesh);
|
||||
var vertices = newMesh.vertices;
|
||||
var uv2 = newMesh.uv2;
|
||||
|
||||
Matrix4x4 worldToLocal = smr.transform.worldToLocalMatrix;
|
||||
Vector3 localXAxis = worldToLocal.MultiplyVector(Vector3.right).normalized;
|
||||
Vector3 localYAxis = worldToLocal.MultiplyVector(Vector3.up).normalized;
|
||||
|
||||
for (int i = 0; i < vertices.Length; i++)
|
||||
{
|
||||
Vector2 offset = uv2[i];
|
||||
vertices[i] += localXAxis * offset.x + localYAxis * offset.y;
|
||||
}
|
||||
|
||||
newMesh.vertices = vertices;
|
||||
newMesh.RecalculateNormals();
|
||||
newMesh.RecalculateBounds();
|
||||
|
||||
smr.sharedMesh = newMesh;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class ModelExporter
|
||||
BuildBillboard(container);
|
||||
|
||||
var textures = TextureExporter.ExportAllTexture(Path.GetDirectoryName(path), container.gameObject);
|
||||
var model = ReadPMXModel(container.CharaEntry, container, textures);
|
||||
var model = ReadPMXModel(container, textures, container.CharaEntry);
|
||||
|
||||
FileStream fileStream = new FileStream(path, FileMode.Create);
|
||||
BinaryWriter writer = new BinaryWriter(fileStream);
|
||||
@@ -46,15 +46,45 @@ public class ModelExporter
|
||||
UmaViewerUI.Instance.ShowMessage($"PMX Save at {path}", UIMessageType.Success);
|
||||
}
|
||||
|
||||
public static RawMMDModel ReadPMXModel(CharaEntry entry, UmaContainer container, string[] textures)
|
||||
public static void ExportModel(UmaContainer container, string path)
|
||||
{
|
||||
|
||||
var textures = TextureExporter.ExportAllTexture(Path.GetDirectoryName(path), container.gameObject);
|
||||
var model = ReadPMXModel(container, textures);
|
||||
|
||||
FileStream fileStream = new FileStream(path, FileMode.Create);
|
||||
BinaryWriter writer = new BinaryWriter(fileStream);
|
||||
var config = new ModelConfig() { GlobalToonPath = "Toon" };
|
||||
PMXWriter.Write(writer, model, config);
|
||||
|
||||
writer.Close();
|
||||
fileStream.Close();
|
||||
|
||||
UmaViewerUI.Instance.ShowMessage($"PMX Save at {path}", UIMessageType.Success);
|
||||
}
|
||||
|
||||
public static RawMMDModel ReadPMXModel(UmaContainer container, string[] textures, CharaEntry entry = null)
|
||||
{
|
||||
RawMMDModel model = new RawMMDModel();
|
||||
model.Name = entry.Name;
|
||||
model.NameEn = entry.GetName();
|
||||
model.Description = model.DescriptionEn = $"{entry.GetName()}\n{modelInfo}";
|
||||
if (entry != null)
|
||||
{
|
||||
model.Name = entry.Name;
|
||||
model.NameEn = entry.GetName();
|
||||
model.Description = model.DescriptionEn = $"{entry.GetName()}\n{modelInfo}";
|
||||
}
|
||||
else
|
||||
{
|
||||
model.Name = container.gameObject.name;
|
||||
model.NameEn = container.gameObject.name;
|
||||
model.Description = model.DescriptionEn = $"{container.gameObject.name}\n{modelInfo}";
|
||||
}
|
||||
|
||||
//Rean Bones
|
||||
var rootBone = container.transform.Find("Position");
|
||||
if (rootBone == null)
|
||||
{
|
||||
rootBone = container.transform;
|
||||
}
|
||||
List<Transform> bones = new List<Transform>(rootBone.GetComponentsInChildren<Transform>());
|
||||
bones.RemoveAll(o => o.name.Contains("Col_"));
|
||||
|
||||
@@ -275,7 +305,10 @@ public class ModelExporter
|
||||
{
|
||||
mesh = ((SkinnedMeshRenderer)renderer).sharedMesh;
|
||||
}
|
||||
|
||||
if (!mesh.isReadable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var materials = new List<Material>(renderer.sharedMaterials);
|
||||
for (int i = 0; i < mesh.subMeshCount; i++)
|
||||
{
|
||||
@@ -293,8 +326,15 @@ public class ModelExporter
|
||||
mat.DrawSelfShadow = true;
|
||||
mat.EdgeColor = Color.black;
|
||||
mat.EdgeSize = 0.4f;
|
||||
var tex = model.TextureList.Find(t => t.TexturePath.Contains($"/{material.mainTexture.name}.png"));
|
||||
mat.Texture = (tex ?? model.TextureList[0]);
|
||||
if (material.HasProperty("_MainTex"))
|
||||
{
|
||||
var tex = model.TextureList.Find(t => t.TexturePath.Contains($"/{material.mainTexture.name}.png"));
|
||||
mat.Texture = (tex ?? model.TextureList[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
mat.Texture = model.TextureList[0];
|
||||
}
|
||||
mat.MetaInfo = "";
|
||||
part.BaseShift = baseShift + mesh.GetSubMesh(i).indexStart;
|
||||
part.TriangleIndexNum = mesh.GetSubMesh(i).indexCount;
|
||||
@@ -317,6 +357,11 @@ public class ModelExporter
|
||||
var meshfilter = mr.GetComponent<MeshFilter>();
|
||||
//Cache this data to avoid copying arrays.
|
||||
var mesh = meshfilter.mesh;
|
||||
if (!mesh.isReadable)
|
||||
{
|
||||
UmaViewerUI.Instance.ShowMessage($"Export {mesh.name} Failed : Mesh is not readable", UIMessageType.Error);
|
||||
continue;
|
||||
}
|
||||
var vertices = mesh.vertices;
|
||||
var normals = mesh.normals;
|
||||
var uv = mesh.uv;
|
||||
@@ -355,6 +400,11 @@ public class ModelExporter
|
||||
else if (renderer is SkinnedMeshRenderer smr)
|
||||
{
|
||||
var mesh = smr.sharedMesh;
|
||||
if (!mesh.isReadable)
|
||||
{
|
||||
UmaViewerUI.Instance.ShowMessage($"Export {mesh.name} Failed : Mesh is not readable", UIMessageType.Error);
|
||||
continue;
|
||||
}
|
||||
var vertices = mesh.vertices;
|
||||
var normals = mesh.normals;
|
||||
var uv = mesh.uv;
|
||||
|
||||
@@ -118,6 +118,13 @@ public class UISettingsAnimation : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
public void EnableRootMotion(bool enable)
|
||||
{
|
||||
var container = Builder.CurrentUMAContainer;
|
||||
if (!container || !container.UmaAnimator) return;
|
||||
container.UmaAnimator.SetLayerWeight(2, enable ? 1 : 0);
|
||||
}
|
||||
|
||||
public static string ToFrameFormat(float time, float frameRate)
|
||||
{
|
||||
int frames = Mathf.FloorToInt(time % 1 * frameRate);
|
||||
|
||||
@@ -66,7 +66,11 @@ public class UISettingsAssets : MonoBehaviour
|
||||
if (File.Exists(asset.FilePath) && !File.Exists(outName))
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outName));
|
||||
File.Copy(asset.FilePath, outName);
|
||||
|
||||
//修改(导出解密ab包)
|
||||
var decryptedData = AssetBundleDecryptor.DecryptFileToBytes(asset.FilePath, asset.FKey);
|
||||
File.WriteAllBytes(outName, decryptedData);
|
||||
|
||||
}
|
||||
}
|
||||
UmaViewerUI.Instance.ShowMessage($"{LoadedAssetEntries.Count} files copied", UIMessageType.Success);
|
||||
@@ -96,7 +100,11 @@ public class UISettingsAssets : MonoBehaviour
|
||||
if (File.Exists(asset.FilePath) && !File.Exists(outName))
|
||||
{
|
||||
Directory.CreateDirectory(Path.GetDirectoryName(outName));
|
||||
File.Copy(asset.FilePath, outName);
|
||||
|
||||
//修改(导出解密ab包)
|
||||
var decryptedData = AssetBundleDecryptor.DecryptFileToBytes(asset.FilePath, asset.FKey);
|
||||
File.WriteAllBytes(outName, decryptedData);
|
||||
|
||||
}
|
||||
}
|
||||
UmaViewerUI.Instance.ShowMessage($"{assets.Count} files copied", UIMessageType.Success);
|
||||
|
||||
@@ -110,6 +110,17 @@ public class UISettingsModel : MonoBehaviour
|
||||
ModelExporter.ExportModel(container, path);
|
||||
}
|
||||
}
|
||||
|
||||
var prop_container = Builder.CurrentOtherContainer;
|
||||
if (prop_container)
|
||||
{
|
||||
var path = StandaloneFileBrowser.SaveFilePanel("Save PMX File", Config.Instance.MainPath, $"{prop_container}", "pmx");
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
ModelExporter.ExportModel(prop_container, path);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
UmaViewerUI.Instance.ShowMessage("Not supported on this platform", UIMessageType.Warning);
|
||||
#endif
|
||||
|
||||
@@ -132,7 +132,16 @@ public class UmaAssetManager : MonoBehaviour
|
||||
}
|
||||
else
|
||||
{
|
||||
AssetBundle bundle = AssetBundle.LoadFromFile(filePath);
|
||||
AssetBundle bundle;
|
||||
if (string.IsNullOrEmpty(entry.Key) || long.Parse(entry.Key) == 0)
|
||||
{
|
||||
bundle = AssetBundle.LoadFromFile(filePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
var data = AssetBundleDecryptor.DecryptFileToBytes(filePath, entry.FKey);
|
||||
bundle = AssetBundle.LoadFromMemory(data);
|
||||
}
|
||||
if (!bundle)
|
||||
{
|
||||
Debug.Log(filePath + " exists and doesn't work");
|
||||
|
||||
@@ -608,6 +608,9 @@ public class UmaContainerCharacter : UmaContainer
|
||||
}
|
||||
|
||||
//BodyAlapha's shader need to change manually.
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)
|
||||
if (m.name.Contains("bdy"))
|
||||
{
|
||||
if (m.name.Contains("Alpha"))
|
||||
@@ -616,17 +619,33 @@ public class UmaContainerCharacter : UmaContainer
|
||||
}
|
||||
else
|
||||
{
|
||||
//some costume use area texture
|
||||
var areaTex = UmaViewerMain.Instance.AbChara.FirstOrDefault(a => a.Name.StartsWith(UmaDatabaseController.BodyPath + $"bdy{costumeIdShort}/textures") && a.Name.EndsWith("area"));
|
||||
if (areaTex != null)
|
||||
|
||||
var areaTexCandidates = UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith(UmaDatabaseController.BodyPath + $"bdy{costumeIdShort}/textures") && a.Name.EndsWith("area")).ToList();
|
||||
if (areaTexCandidates.Count > 0)
|
||||
{
|
||||
LoadTextures(areaTex);
|
||||
m.SetTexture("_MaskColorTex", textures.Load(t => t.name.Contains(costumeIdShort) && t.name.EndsWith("area")));
|
||||
SetMaskColor(m, IsMob ? MobDressColor : CharaData, IsMob);
|
||||
foreach (var areaEntry in areaTexCandidates)
|
||||
{
|
||||
LoadTextures(areaEntry);
|
||||
}
|
||||
var defaultAreaTex = GenericBodyTextures.Load(t => t.name.Contains(costumeIdShort) && t.name.EndsWith("0_area"));
|
||||
if (defaultAreaTex != null)
|
||||
{
|
||||
m.SetTexture("_MaskColorTex", defaultAreaTex);
|
||||
}
|
||||
if (IsMob)
|
||||
{
|
||||
SetMaskColor(m, MobDressColor, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyDressColorAndArea(m);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
switch (costumeIdShort.Split('_')[0]) //costume ID
|
||||
{
|
||||
case "0001":
|
||||
@@ -922,6 +941,17 @@ public class UmaContainerCharacter : UmaContainer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//修改(载入专用尾巴相关)
|
||||
public void LoadExclusiveTail(UmaDatabaseEntry entry)
|
||||
{
|
||||
GameObject go = entry.Get<GameObject>();
|
||||
Tail = Instantiate(go, transform);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void LoadTail(UmaDatabaseEntry entry)
|
||||
{
|
||||
Tail = InstantiateEntry(entry, transform);
|
||||
@@ -956,6 +986,92 @@ public class UmaContainerCharacter : UmaContainer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)
|
||||
public void RefreshDressColor()
|
||||
{
|
||||
foreach (var materialHelper in Materials)
|
||||
{
|
||||
var mat = materialHelper.Mat;
|
||||
if (mat == null) continue;
|
||||
|
||||
if (mat.name.Contains("bdy") && !mat.name.Contains("Alpha"))
|
||||
{
|
||||
if (mat.HasProperty("_MaskColorTex"))
|
||||
{
|
||||
if (IsMob)
|
||||
{
|
||||
SetMaskColor(mat, MobDressColor, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
ApplyDressColorAndArea(mat);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)
|
||||
private void ApplyDressColorAndArea(Material mat)
|
||||
{
|
||||
var uma = Builder.CurrentUMAContainer;
|
||||
if (uma == null || string.IsNullOrEmpty(uma.VarCostumeIdShort)) return;
|
||||
|
||||
string selectedId = UmaViewerUI.CurrentSelectedColorSetId;
|
||||
var parts = uma.VarCostumeIdShort.Split('_');
|
||||
if (parts.Length < 2) return;
|
||||
|
||||
if (!int.TryParse(parts[0], out int bodyType)) return;
|
||||
if (!int.TryParse(parts[1], out int bodyTypeSub)) return;
|
||||
|
||||
DataRow charaColor = null;
|
||||
var dressRow = UmaDatabaseController.Instance.DressData.FirstOrDefault(row =>
|
||||
int.Parse(row["body_type"].ToString()) == bodyType &&
|
||||
int.Parse(row["body_type_sub"].ToString()) == bodyTypeSub);
|
||||
|
||||
if (dressRow != null && dressRow["body_setting"].ToString() == "4")
|
||||
{
|
||||
string dressId = dressRow["id"].ToString();
|
||||
var colorSetRows = UmaDatabaseController.Instance.CharaDressColor
|
||||
.Where(row => row["dress_id"].ToString() == dressId).ToList();
|
||||
|
||||
charaColor = !string.IsNullOrEmpty(selectedId)
|
||||
? colorSetRows.FirstOrDefault(row => row["id"].ToString() == selectedId)
|
||||
: null;
|
||||
if (charaColor == null && colorSetRows.Count > 0) charaColor = colorSetRows.First();
|
||||
}
|
||||
else
|
||||
{
|
||||
charaColor = !string.IsNullOrEmpty(selectedId)
|
||||
? UmaDatabaseController.Instance.CharaDressColor.FirstOrDefault(row => row["id"].ToString() == selectedId)
|
||||
: null;
|
||||
if (charaColor == null) charaColor = UmaDatabaseController.Instance.CharaDressColor.FirstOrDefault();
|
||||
}
|
||||
|
||||
if (charaColor == null) return;
|
||||
|
||||
Texture2D areaTex = null;
|
||||
try
|
||||
{
|
||||
if (charaColor.Table.Columns.Contains("area_map_id"))
|
||||
{
|
||||
int areaMapId = Convert.ToInt32(charaColor["area_map_id"]);
|
||||
string suffix = areaMapId.ToString("D2") + "_area";
|
||||
areaTex = GenericBodyTextures.Load(t => t.name.EndsWith(suffix));
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (areaTex != null) mat.SetTexture("_MaskColorTex", areaTex);
|
||||
SetMaskColor(mat, charaColor, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private void SetMaskColor(Material mat, DataRow colordata, bool IsMob)
|
||||
{
|
||||
mat.EnableKeyword("USE_MASK_COLOR");
|
||||
@@ -977,19 +1093,23 @@ public class UmaContainerCharacter : UmaContainer
|
||||
}
|
||||
else
|
||||
{
|
||||
ColorUtility.TryParseHtmlString("#" + colordata["image_color_main"].ToString(), out c1);
|
||||
ColorUtility.TryParseHtmlString("#" + colordata["image_color_sub"].ToString(), out c2);
|
||||
ColorUtility.TryParseHtmlString("#" + colordata["ui_color_sub"].ToString(), out c3);
|
||||
ColorUtility.TryParseHtmlString("#" + colordata["ui_color_sub"].ToString(), out c4);
|
||||
ColorUtility.TryParseHtmlString("#" + colordata["ui_training_color_1"].ToString(), out c5);
|
||||
ColorUtility.TryParseHtmlString("#" + colordata["ui_training_color_2"].ToString(), out c6);
|
||||
float toonstrength = 0.8f;
|
||||
t1 = c1 * toonstrength;
|
||||
t2 = c2 * toonstrength;
|
||||
t3 = c3 * toonstrength;
|
||||
t4 = c4 * toonstrength;
|
||||
t5 = c5 * toonstrength;
|
||||
t6 = c6 * toonstrength;
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)
|
||||
ColorUtility.TryParseHtmlString(colordata["color_r1"].ToString(), out c1);
|
||||
ColorUtility.TryParseHtmlString(colordata["color_r2"].ToString(), out c2);
|
||||
ColorUtility.TryParseHtmlString(colordata["color_g1"].ToString(), out c3);
|
||||
ColorUtility.TryParseHtmlString(colordata["color_g2"].ToString(), out c4);
|
||||
ColorUtility.TryParseHtmlString(colordata["color_b1"].ToString(), out c5);
|
||||
ColorUtility.TryParseHtmlString(colordata["color_b2"].ToString(), out c6);
|
||||
ColorUtility.TryParseHtmlString(colordata["toon_color_r1"].ToString(), out t1);
|
||||
ColorUtility.TryParseHtmlString(colordata["toon_color_r2"].ToString(), out t2);
|
||||
ColorUtility.TryParseHtmlString(colordata["toon_color_g1"].ToString(), out t3);
|
||||
ColorUtility.TryParseHtmlString(colordata["toon_color_g2"].ToString(), out t4);
|
||||
ColorUtility.TryParseHtmlString(colordata["toon_color_b1"].ToString(), out t5);
|
||||
ColorUtility.TryParseHtmlString(colordata["toon_color_b2"].ToString(), out t6);
|
||||
|
||||
|
||||
}
|
||||
|
||||
mat.SetColor("_MaskColorR1", c1);
|
||||
@@ -1103,7 +1223,7 @@ public class UmaContainerCharacter : UmaContainer
|
||||
{
|
||||
if (IsMini) return;
|
||||
OverrideController["clip_p"] = clip;
|
||||
UmaAnimator.Play("motion_1", 2, 0);
|
||||
UmaAnimator.Play("motion_p", 2, 0);
|
||||
}
|
||||
else if (clip.name.EndsWith("_cam"))
|
||||
{
|
||||
@@ -1148,7 +1268,7 @@ public class UmaContainerCharacter : UmaContainer
|
||||
Builder.SetPreviewCamera(null);
|
||||
OverrideController["clip_1"] = OverrideController["clip_2"];
|
||||
OverrideController["clip_2"] = clip;
|
||||
UmaAnimator.Play("motion_1", -1, 0);
|
||||
UmaAnimator.Play("motion_1", 0, 0);
|
||||
UmaAnimator.SetTrigger((motion_s != null && motion_e != null) ? "next_e" : ((motion_s != null) ? "next_s" : "next"));
|
||||
}
|
||||
else
|
||||
@@ -1159,7 +1279,9 @@ public class UmaContainerCharacter : UmaContainer
|
||||
isAnimatorControl = false;
|
||||
}
|
||||
UpBodyReset();
|
||||
var pos_weight = UmaAnimator.GetLayerWeight(2);
|
||||
UmaAnimator.Rebind();
|
||||
UmaAnimator.SetLayerWeight(2, pos_weight); //keep position layer weight
|
||||
OverrideController["clip_2"] = clip;
|
||||
// If Cut-in, play immediately without state interpolation
|
||||
|
||||
|
||||
@@ -0,0 +1,364 @@
|
||||
// Sqlite3MC.cs
|
||||
// Unity C# wrapper for sqlite3mc_x64.dll (Windows x64).
|
||||
// Put sqlite3mc_x64.dll into Assets/Plugins (set platform to Windows x86_64).
|
||||
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
public static class Sqlite3MC
|
||||
{
|
||||
// ---- DLL name: change if you use different file name / arch ----
|
||||
private const string DLL = "sqlite3mc_x64"; // ensure sqlite3mc_x64.dll is available for x64 build
|
||||
|
||||
// ---- Common SQLite constants ----
|
||||
public const int SQLITE_OK = 0;
|
||||
public const int SQLITE_ROW = 100;
|
||||
public const int SQLITE_DONE = 101;
|
||||
public const int SQLITE_OPEN_READWRITE = 0x00000002;
|
||||
public const int SQLITE_OPEN_CREATE = 0x00000004;
|
||||
|
||||
// ---- Native Imports ----
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_open_v2")]
|
||||
private static extern int sqlite3_open_v2([MarshalAs(UnmanagedType.LPStr)] string filename, out IntPtr ppDb, int flags, IntPtr zVfs);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_close")]
|
||||
private static extern int sqlite3_close(IntPtr db);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_errmsg")]
|
||||
private static extern IntPtr sqlite3_errmsg_ptr(IntPtr db);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_exec")]
|
||||
private static extern int sqlite3_exec(IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string sql, IntPtr callback, IntPtr arg, out IntPtr errMsg);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_free")]
|
||||
private static extern void sqlite3_free(IntPtr ptr);
|
||||
|
||||
// sqlite3mc plugin-specific
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3mc_config")]
|
||||
private static extern int sqlite3mc_config(IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string paramName, int newValue);
|
||||
|
||||
// Key: provide both byte[] and IntPtr signatures (use whichever convenient)
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_key")]
|
||||
private static extern int sqlite3_key_bytes(IntPtr db, byte[] pKey, int nKey);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_key")]
|
||||
private static extern int sqlite3_key_ptr(IntPtr db, IntPtr pKey, int nKey);
|
||||
|
||||
// Prepare/step/finalize
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_prepare_v2")]
|
||||
private static extern int sqlite3_prepare_v2(IntPtr db, [MarshalAs(UnmanagedType.LPStr)] string zSql, int nByte, out IntPtr ppStmt, IntPtr pzTail);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_step")]
|
||||
private static extern int sqlite3_step(IntPtr stmt);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_finalize")]
|
||||
private static extern int sqlite3_finalize(IntPtr stmt);
|
||||
|
||||
// Column accessors
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_column_text")]
|
||||
private static extern IntPtr sqlite3_column_text(IntPtr stmt, int iCol);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_column_blob")]
|
||||
private static extern IntPtr sqlite3_column_blob(IntPtr stmt, int iCol);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_column_bytes")]
|
||||
private static extern int sqlite3_column_bytes(IntPtr stmt, int iCol);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_column_int")]
|
||||
private static extern int sqlite3_column_int(IntPtr stmt, int iCol);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_column_int64")]
|
||||
private static extern long sqlite3_column_int64(IntPtr stmt, int iCol);
|
||||
|
||||
// Binders
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_bind_text")]
|
||||
private static extern int sqlite3_bind_text(IntPtr stmt, int idx, byte[] text, int n, IntPtr destructor);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_bind_blob")]
|
||||
private static extern int sqlite3_bind_blob(IntPtr stmt, int idx, byte[] blob, int n, IntPtr destructor);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_bind_int")]
|
||||
private static extern int sqlite3_bind_int(IntPtr stmt, int idx, int val);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_bind_int64")]
|
||||
private static extern int sqlite3_bind_int64(IntPtr stmt, int idx, long val);
|
||||
|
||||
// Backup API
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_backup_init")]
|
||||
private static extern IntPtr sqlite3_backup_init(IntPtr pDest, [MarshalAs(UnmanagedType.LPStr)] string zDestName, IntPtr pSource, [MarshalAs(UnmanagedType.LPStr)] string zSourceName);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_backup_step")]
|
||||
private static extern int sqlite3_backup_step(IntPtr backup, int nPage);
|
||||
|
||||
[DllImport(DLL, CallingConvention = CallingConvention.Cdecl, EntryPoint = "sqlite3_backup_finish")]
|
||||
private static extern int sqlite3_backup_finish(IntPtr backup);
|
||||
|
||||
// ---- Helper: convert native C string (UTF-8) to C# string ----
|
||||
private static string PtrToStringUTF8(IntPtr ptr)
|
||||
{
|
||||
if (ptr == IntPtr.Zero) return null;
|
||||
// find length
|
||||
int len = 0;
|
||||
while (Marshal.ReadByte(ptr, len) != 0) len++;
|
||||
if (len == 0) return string.Empty;
|
||||
byte[] buffer = new byte[len];
|
||||
Marshal.Copy(ptr, buffer, 0, len);
|
||||
return Encoding.UTF8.GetString(buffer);
|
||||
}
|
||||
|
||||
// Get error message for DB handle
|
||||
public static string GetErrMsg(IntPtr db)
|
||||
{
|
||||
IntPtr p = sqlite3_errmsg_ptr(db);
|
||||
return PtrToStringUTF8(p) ?? $"(null errmsg, db={db})";
|
||||
}
|
||||
|
||||
// ---- High-level wrappers ----
|
||||
|
||||
/// <summary>Open a database. Throws Exception on failure. Flags default to read-write | create.</summary>
|
||||
public static IntPtr Open(string path, int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE)
|
||||
{
|
||||
int rc = sqlite3_open_v2(path, out IntPtr db, flags, IntPtr.Zero);
|
||||
if (rc != SQLITE_OK || db == IntPtr.Zero)
|
||||
{
|
||||
string em = db != IntPtr.Zero ? GetErrMsg(db) : "(no db handle)";
|
||||
throw new InvalidOperationException($"sqlite3_open_v2('{path}') failed rc={rc} errmsg={em}");
|
||||
}
|
||||
return db;
|
||||
}
|
||||
|
||||
/// <summary>Close DB. Returns rc.</summary>
|
||||
public static int Close(IntPtr db)
|
||||
{
|
||||
if (db == IntPtr.Zero) return SQLITE_OK;
|
||||
return sqlite3_close(db);
|
||||
}
|
||||
|
||||
/// <summary>Execute SQL (no binder). Return code; if errorMsg != null it contains message (freed automatically).</summary>
|
||||
public static int Exec(IntPtr db, string sql, out string errorMsg)
|
||||
{
|
||||
errorMsg = null;
|
||||
int rc = sqlite3_exec(db, sql, IntPtr.Zero, IntPtr.Zero, out IntPtr errPtr);
|
||||
if (errPtr != IntPtr.Zero)
|
||||
{
|
||||
errorMsg = PtrToStringUTF8(errPtr);
|
||||
// free the error string allocated by sqlite3_exec
|
||||
sqlite3_free(errPtr);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/// <summary>Set sqlite3mc config parameter (plugin). e.g. sqlite3mc_config(db, "cipher", index).</summary>
|
||||
public static int MC_Config(IntPtr db, string paramName, int newValue)
|
||||
{
|
||||
return sqlite3mc_config(db, paramName, newValue);
|
||||
}
|
||||
|
||||
/// <summary>Set key from raw bytes.</summary>
|
||||
public static int Key_SetBytes(IntPtr db, byte[] keyBytes)
|
||||
{
|
||||
if (keyBytes == null) return sqlite3_key_bytes(db, null, 0);
|
||||
return sqlite3_key_bytes(db, keyBytes, keyBytes.Length);
|
||||
}
|
||||
|
||||
/// <summary>Set key from pointer (unsafe). Rarely needed.</summary>
|
||||
public static int Key_SetPtr(IntPtr db, IntPtr pKey, int len)
|
||||
{
|
||||
return sqlite3_key_ptr(db, pKey, len);
|
||||
}
|
||||
|
||||
/// <summary>Validate whether DB can be read (try SELECT on sqlite_master).</summary>
|
||||
public static bool ValidateReadable(IntPtr db, out string errmsg)
|
||||
{
|
||||
int rc = Exec(db, "SELECT name FROM sqlite_master LIMIT 1;", out errmsg);
|
||||
return rc == SQLITE_OK;
|
||||
}
|
||||
|
||||
// Prepare / Step utility: simple single-row single-column string fetch
|
||||
public static string QuerySingleString(IntPtr db, string sql)
|
||||
{
|
||||
int rc = sqlite3_prepare_v2(db, sql, -1, out IntPtr stmt, IntPtr.Zero);
|
||||
if (rc != SQLITE_OK) throw new InvalidOperationException($"prepare failed rc={rc} errmsg={GetErrMsg(db)} sql={sql}");
|
||||
try
|
||||
{
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc == SQLITE_ROW)
|
||||
{
|
||||
IntPtr txt = sqlite3_column_text(stmt, 0);
|
||||
return PtrToStringUTF8(txt);
|
||||
}
|
||||
else if (rc == SQLITE_DONE)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"step failed rc={rc} errmsg={GetErrMsg(db)}");
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper: prepare/step loop with callback
|
||||
public delegate void RowCallback(IntPtr stmt);
|
||||
public static void ForEachRow(string sql, IntPtr db, RowCallback rowCallback)
|
||||
{
|
||||
int rc = sqlite3_prepare_v2(db, sql, -1, out IntPtr stmt, IntPtr.Zero);
|
||||
if (rc != SQLITE_OK)
|
||||
throw new InvalidOperationException($"prepare failed rc={rc} errmsg={GetErrMsg(db)} sql={sql}");
|
||||
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
rc = sqlite3_step(stmt);
|
||||
if (rc == SQLITE_ROW)
|
||||
{
|
||||
rowCallback(stmt);
|
||||
}
|
||||
else if (rc == SQLITE_DONE)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException($"step failed rc={rc} errmsg={GetErrMsg(db)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
}
|
||||
}
|
||||
|
||||
// Column getters (UTF-8 text or blob)
|
||||
public static string ColumnText(IntPtr stmt, int col)
|
||||
{
|
||||
IntPtr p = sqlite3_column_text(stmt, col);
|
||||
return PtrToStringUTF8(p);
|
||||
}
|
||||
|
||||
public static byte[] ColumnBlob(IntPtr stmt, int col)
|
||||
{
|
||||
IntPtr p = sqlite3_column_blob(stmt, col);
|
||||
if (p == IntPtr.Zero) return null;
|
||||
int len = sqlite3_column_bytes(stmt, col);
|
||||
if (len <= 0) return new byte[0];
|
||||
byte[] buf = new byte[len];
|
||||
Marshal.Copy(p, buf, 0, len);
|
||||
return buf;
|
||||
}
|
||||
|
||||
public static int ColumnInt(IntPtr stmt, int col) => sqlite3_column_int(stmt, col);
|
||||
public static long ColumnInt64(IntPtr stmt, int col) => sqlite3_column_int64(stmt, col);
|
||||
|
||||
// Bind helpers (use SQLITE_TRANSIENT to let sqlite copy data)
|
||||
private static readonly IntPtr SQLITE_TRANSIENT = new IntPtr(-1);
|
||||
|
||||
public static int BindText(IntPtr stmt, int idx, string text)
|
||||
{
|
||||
if (text == null) return sqlite3_bind_text(stmt, idx, null, 0, IntPtr.Zero);
|
||||
byte[] utf8 = Encoding.UTF8.GetBytes(text);
|
||||
return sqlite3_bind_text(stmt, idx, utf8, utf8.Length, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
public static int BindBlob(IntPtr stmt, int idx, byte[] blob)
|
||||
{
|
||||
if (blob == null) return sqlite3_bind_blob(stmt, idx, null, 0, IntPtr.Zero);
|
||||
return sqlite3_bind_blob(stmt, idx, blob, blob.Length, SQLITE_TRANSIENT);
|
||||
}
|
||||
|
||||
public static int BindInt(IntPtr stmt, int idx, int v) => sqlite3_bind_int(stmt, idx, v);
|
||||
public static int BindInt64(IntPtr stmt, int idx, long v) => sqlite3_bind_int64(stmt, idx, v);
|
||||
|
||||
// Backup: copy entire main DB from source to destination path (destination will be created/opened by function)
|
||||
// Returns SQLITE_OK on success. Throws on fatal open errors.
|
||||
public static int BackupToFile(IntPtr srcDb, string dstPath, int pagesPerStep = 5)
|
||||
{
|
||||
// Open destination DB (readwrite | create)
|
||||
int rcOpen = sqlite3_open_v2(dstPath, out IntPtr dstDb, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, IntPtr.Zero);
|
||||
if (rcOpen != SQLITE_OK)
|
||||
{
|
||||
throw new InvalidOperationException($"sqlite3_open_v2(dst) failed rc={rcOpen} errmsg={GetErrMsg(dstDb)}");
|
||||
}
|
||||
|
||||
IntPtr backup = sqlite3_backup_init(dstDb, "main", srcDb, "main");
|
||||
if (backup == IntPtr.Zero)
|
||||
{
|
||||
int rcClose = sqlite3_close(dstDb);
|
||||
throw new InvalidOperationException($"sqlite3_backup_init returned NULL; errmsg(dst)={GetErrMsg(dstDb)} rcClose={rcClose}");
|
||||
}
|
||||
|
||||
int rc = SQLITE_OK;
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
rc = sqlite3_backup_step(backup, pagesPerStep);
|
||||
if (rc == SQLITE_DONE)
|
||||
{
|
||||
rc = sqlite3_backup_finish(backup);
|
||||
break;
|
||||
}
|
||||
else if (rc == SQLITE_OK)
|
||||
{
|
||||
// continue
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
// error
|
||||
rc = sqlite3_backup_finish(backup);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
// ensure destination db closed
|
||||
sqlite3_close(dstDb);
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
// Simple convenience: try to open source with key (raw bytes) and then backup to dstPath (unencrypted).
|
||||
// Throws exceptions on fatal errors; returns final sqlite rc from backup.
|
||||
public static int DecryptDbToPlainFile(string srcPath, string dstPath, byte[] keyBytes, int cipherIndex = -1)
|
||||
{
|
||||
IntPtr srcDb = IntPtr.Zero;
|
||||
try
|
||||
{
|
||||
srcDb = Open(srcPath, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
|
||||
|
||||
if (cipherIndex >= 0)
|
||||
{
|
||||
int cfg = MC_Config(srcDb, "cipher", cipherIndex);
|
||||
// cfg may return various codes depending implementation; we don't treat non-zero as fatal here
|
||||
}
|
||||
|
||||
int rcKey = Key_SetBytes(srcDb, keyBytes);
|
||||
if (rcKey != SQLITE_OK)
|
||||
{
|
||||
throw new InvalidOperationException($"sqlite3_key returned rc={rcKey} errmsg={GetErrMsg(srcDb)}");
|
||||
}
|
||||
|
||||
if (!ValidateReadable(srcDb, out string vErr))
|
||||
{
|
||||
throw new InvalidOperationException($"DB did not validate after key. errmsg={vErr}");
|
||||
}
|
||||
|
||||
int rcBackup = BackupToFile(srcDb, dstPath);
|
||||
return rcBackup;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (srcDb != IntPtr.Zero) sqlite3_close(srcDb);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 15e1a8619eefb3944b564456cbd5f403
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -181,7 +181,8 @@ public enum UmaFileType
|
||||
challengematch,
|
||||
collectevent,
|
||||
ratingrace,
|
||||
jobs
|
||||
jobs,
|
||||
manualdownloadatlas,
|
||||
}
|
||||
|
||||
public enum TranslationTables
|
||||
|
||||
@@ -45,6 +45,11 @@ public class UmaDatabaseController
|
||||
public List<DataRow> LiveData;
|
||||
public List<DataRow> DressData;
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)
|
||||
public List<DataRow> CharaDressColor;
|
||||
|
||||
|
||||
/// <summary> Meta Database Connection </summary>
|
||||
public SqliteConnection metaDb;
|
||||
/// <summary> Master Database Connection </summary>
|
||||
@@ -67,8 +72,25 @@ public class UmaDatabaseController
|
||||
masterDb = new SqliteConnection($@"Data Source={Config.Instance.MainPath}/master/master.mdb;");
|
||||
}
|
||||
|
||||
metaDb.Open();
|
||||
MetaEntries = ReadMeta(metaDb);
|
||||
try
|
||||
{
|
||||
metaDb.Open();
|
||||
MetaEntries = ReadMeta(metaDb);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dbPath = $@"{Config.Instance.MainPath}/meta";
|
||||
var key = GenFinalKey(Config.Instance.DBKey);
|
||||
MetaEntries = ReadMetaFromEncryptedDb(dbPath, key, 3);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
masterDb.Open();
|
||||
CharaData = ReadCharaMaster(masterDb);
|
||||
@@ -76,6 +98,12 @@ public class UmaDatabaseController
|
||||
FaceTypeData = ReadFaceTypeData(masterDb);
|
||||
LiveData = ReadAllLiveData(masterDb);
|
||||
DressData = ReadAllDressData(masterDb);
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)
|
||||
CharaDressColor = ReadAllCharaDressColor(masterDb);
|
||||
|
||||
|
||||
#if UNITY_STANDALONE_WIN
|
||||
if (!Config.Instance.RegionDetectionPassed)
|
||||
{
|
||||
@@ -100,16 +128,26 @@ public class UmaDatabaseController
|
||||
}
|
||||
else
|
||||
{
|
||||
msg += "\nPlease install the dmm game client";
|
||||
msg += "\nPlease install the dmm game client or check the key settings.";
|
||||
}
|
||||
UmaViewerUI.Instance.ShowMessage(msg, UIMessageType.Error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)
|
||||
static List<DataRow> ReadAllCharaDressColor(SqliteConnection conn)
|
||||
{
|
||||
return ReadMaster(conn, "SELECT * FROM chara_dress_color_set");
|
||||
}
|
||||
|
||||
|
||||
|
||||
static Dictionary<string,UmaDatabaseEntry> ReadMeta(SqliteConnection conn)
|
||||
{
|
||||
SqliteCommand sqlite_cmd = conn.CreateCommand();
|
||||
sqlite_cmd.CommandText = "SELECT m,n,h,d FROM a WHERE d IS NOT NULL";
|
||||
sqlite_cmd.CommandText = "SELECT m,n,h,d FROM a WHERE d IS NOT NULL"; //filter out manifest entries, whitch have null prerequisites
|
||||
SqliteDataReader sqlite_datareader = sqlite_cmd.ExecuteReader();
|
||||
Dictionary<string, UmaDatabaseEntry> meta = new Dictionary<string, UmaDatabaseEntry>();
|
||||
while (sqlite_datareader.Read())
|
||||
@@ -143,8 +181,8 @@ public class UmaDatabaseController
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Debug.LogError($"Error caught while reading Entry : \n {e}");
|
||||
{
|
||||
Debug.LogError($"Error caught while reading Entry : \n {e}");
|
||||
}
|
||||
|
||||
if (entry != null && !meta.ContainsKey(entry.Name)) {
|
||||
@@ -155,6 +193,108 @@ public class UmaDatabaseController
|
||||
return meta;
|
||||
}
|
||||
|
||||
public static Dictionary<string, UmaDatabaseEntry> ReadMetaFromEncryptedDb(string dbPath, byte[] keyBytes, int cipherIndex = -1)
|
||||
{
|
||||
var meta = new Dictionary<string, UmaDatabaseEntry>(StringComparer.Ordinal);
|
||||
IntPtr db = IntPtr.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
db = Sqlite3MC.Open(dbPath);
|
||||
|
||||
if (cipherIndex >= 0)
|
||||
{
|
||||
try
|
||||
{
|
||||
int cfgRc = Sqlite3MC.MC_Config(db, "cipher", cipherIndex);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogError($"MC_Config thrown: {e}");
|
||||
}
|
||||
}
|
||||
|
||||
int rcKey = Sqlite3MC.Key_SetBytes(db, keyBytes);
|
||||
if (rcKey != Sqlite3MC.SQLITE_OK)
|
||||
{
|
||||
string em = Sqlite3MC.GetErrMsg(db);
|
||||
throw new InvalidOperationException($"sqlite3_key returned rc={rcKey}, errmsg={em}");
|
||||
}
|
||||
|
||||
if (!Sqlite3MC.ValidateReadable(db, out string validateErr))
|
||||
{
|
||||
Debug.LogError($"DB validation after key failed: {validateErr}");
|
||||
throw new InvalidOperationException("DB validation after key failed: " + validateErr);
|
||||
}
|
||||
|
||||
string sql = "SELECT m,n,h,c,d,e FROM a";
|
||||
Sqlite3MC.ForEachRow(sql, db, (stmt) =>
|
||||
{
|
||||
try
|
||||
{
|
||||
string m = Sqlite3MC.ColumnText(stmt, 0);
|
||||
string n = Sqlite3MC.ColumnText(stmt, 1);
|
||||
string h = Sqlite3MC.ColumnText(stmt, 2);
|
||||
string c = Sqlite3MC.ColumnText(stmt, 3);
|
||||
string d = Sqlite3MC.ColumnText(stmt, 4);
|
||||
string e = Sqlite3MC.ColumnText(stmt, 5);
|
||||
|
||||
if (string.IsNullOrEmpty(m))
|
||||
{
|
||||
Debug.LogWarning("Skipping row: empty type string (m).");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Enum.TryParse<UmaFileType>(m, /*ignoreCase*/ false, out UmaFileType type))
|
||||
{
|
||||
Debug.LogWarning($"Unrecognized EntryType Enum Value :{m}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(n))
|
||||
{
|
||||
Debug.LogError($"Invalid entry name '{n}' or URL '{h}'. Skipping row.");
|
||||
return;
|
||||
}
|
||||
|
||||
var entry = new UmaDatabaseEntry
|
||||
{
|
||||
Type = type,
|
||||
Name = n,
|
||||
Url = h,
|
||||
Checksum = c,
|
||||
Prerequisites = d,
|
||||
Key = e
|
||||
};
|
||||
|
||||
if (!meta.ContainsKey(entry.Name))
|
||||
{
|
||||
meta.Add(entry.Name, entry);
|
||||
}
|
||||
}
|
||||
catch (Exception exRow)
|
||||
{
|
||||
Debug.LogError("Error caught while reading row: " + exRow);
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError("ReadMetaFromEncryptedDb failed: " + ex);
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (db != IntPtr.Zero)
|
||||
{
|
||||
try { Sqlite3MC.Close(db); }
|
||||
catch (Exception e) { Debug.LogError("Closing DB failed: " + e); }
|
||||
}
|
||||
}
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
static void SetDefaultGameRegion(SqliteConnection conn)
|
||||
{
|
||||
var str = ReadMaster(conn, "SELECT text FROM text_data LIMIT 1");
|
||||
@@ -307,4 +447,15 @@ public class UmaDatabaseController
|
||||
GC.WaitForPendingFinalizers();
|
||||
}
|
||||
|
||||
public byte[] GenFinalKey(byte[] key)
|
||||
{
|
||||
var baseKey = Config.Instance.DBBaseKey;
|
||||
if (baseKey.Length < 13)
|
||||
throw new Exception("Invalid Base Key length");
|
||||
for (int i = 0; i < key.Length; i++)
|
||||
{
|
||||
key[i] = (byte)(key[i] ^ baseKey[i % 13]);
|
||||
}
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,41 @@ public class UmaDatabaseEntry
|
||||
public string Url;
|
||||
public string Checksum;
|
||||
public string Prerequisites;
|
||||
public string Key;
|
||||
private byte[] fKey;
|
||||
|
||||
[NonSerialized]
|
||||
public List<UmaDatabaseEntry> CachedPrerequisites = null;
|
||||
|
||||
public byte[] FKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (fKey == null && !string.IsNullOrEmpty(Key))
|
||||
{
|
||||
var baseKeys = Config.Instance.ABKey;
|
||||
int baseLen = baseKeys.Length;
|
||||
int keysLen = baseLen * 8;
|
||||
byte[] keys = new byte[keysLen];
|
||||
for (int i = 0; i < baseLen; ++i)
|
||||
{
|
||||
byte[] keyBytes = BitConverter.GetBytes(long.Parse(Key));
|
||||
if (!BitConverter.IsLittleEndian)
|
||||
Array.Reverse(keyBytes);
|
||||
|
||||
byte b = baseKeys[i];
|
||||
int baseOffset = i << 3; // i * 8
|
||||
for (int j = 0; j < 8; ++j)
|
||||
{
|
||||
keys[baseOffset + j] = (byte)(b ^ keyBytes[j]);
|
||||
}
|
||||
}
|
||||
fKey = keys;
|
||||
}
|
||||
return fKey;
|
||||
}
|
||||
}
|
||||
|
||||
public string FilePath
|
||||
{
|
||||
get
|
||||
|
||||
@@ -270,32 +270,57 @@ public class UmaViewerBuilder : MonoBehaviour
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//修改(载入专用尾巴相关)
|
||||
if (tailId > 0)
|
||||
{
|
||||
string tailName = $"tail{tailId.ToString().PadLeft(4, '0')}_00";
|
||||
string tailPath = $"3d/chara/tail/{tailName}/";
|
||||
string tailPfb = tailPath + $"pfb_{tailName}";
|
||||
asset = null;
|
||||
Main.AbList.TryGetValue(tailPfb, out asset);
|
||||
if (asset != null)
|
||||
string costumePrefixForTail = $"{id}_{costumeId}";
|
||||
string exclusiveTailName = $"tail{costumePrefixForTail}";
|
||||
string exclusiveTailPath = $"3d/chara/tail/{exclusiveTailName}/";
|
||||
string exclusiveTailPfb = $"{exclusiveTailPath}pfb_{exclusiveTailName}";
|
||||
|
||||
if (Main.AbList.TryGetValue(exclusiveTailPfb, out asset))
|
||||
{
|
||||
foreach (var asset2 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith($"{tailPath}textures/tex_{tailName}_{head_id}") || a.Name.StartsWith($"{tailPath}textures/tex_{tailName}_0000")))
|
||||
umaContainer.LoadExclusiveTail(asset);
|
||||
|
||||
string clothPath = $"{exclusiveTailPath}clothes/pfb_{exclusiveTailName}_cloth00";
|
||||
if (Main.AbList.TryGetValue(clothPath, out var clothAsset))
|
||||
{
|
||||
umaContainer.LoadTextures(asset2);
|
||||
umaContainer.LoadPhysics(clothAsset);
|
||||
}
|
||||
|
||||
umaContainer.LoadTail(asset);
|
||||
|
||||
//Load Physics
|
||||
var asset1 = Main.AbList[$"{tailPath}clothes/pfb_{tailName}_cloth00"];
|
||||
umaContainer.LoadPhysics(asset1);
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("no tail");
|
||||
string tailName = $"tail{tailId.ToString().PadLeft(4, '0')}_00";
|
||||
string tailPath = $"3d/chara/tail/{tailName}/";
|
||||
string tailPfb = $"{tailPath}pfb_{tailName}";
|
||||
asset = null;
|
||||
|
||||
if (Main.AbList.TryGetValue(tailPfb, out asset) && asset != null)
|
||||
{
|
||||
foreach (var asset2 in UmaViewerMain.Instance.AbChara.Where(a => a.Name.StartsWith($"{tailPath}textures/tex_{tailName}_{head_id}") || a.Name.StartsWith($"{tailPath}textures/tex_{tailName}_0000")))
|
||||
{
|
||||
umaContainer.LoadTextures(asset2);
|
||||
}
|
||||
|
||||
umaContainer.LoadTail(asset);
|
||||
|
||||
string clothPath = $"{tailPath}clothes/pfb_{tailName}_cloth00";
|
||||
if (Main.AbList.TryGetValue(clothPath, out var clothAsset))
|
||||
{
|
||||
umaContainer.LoadPhysics(clothAsset);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("no tail");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
umaContainer.LoadPhysics();
|
||||
umaContainer.SetDynamicBoneEnable(ModelSettings.DynamicBoneEnable);
|
||||
umaContainer.LoadFaceMorph(id, costumeId);
|
||||
@@ -316,6 +341,13 @@ public class UmaViewerBuilder : MonoBehaviour
|
||||
umaContainer.LoadAnimation(entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)
|
||||
UI.ClearColorSetButtons();
|
||||
UI.LoadColorSetButtons();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void LoadMobUma(UmaContainerCharacter umaContainer, CharaEntry chara, string costumeId, int bodyid = -1, bool loadMotion = false)
|
||||
@@ -539,6 +571,12 @@ public class UmaViewerBuilder : MonoBehaviour
|
||||
umaContainer.LoadAnimation(entry);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)
|
||||
UI.ClearColorSetButtons();
|
||||
|
||||
|
||||
}
|
||||
|
||||
private void LoadMiniUma(UmaContainerCharacter umaContainer, CharaEntry chara, string costumeId)
|
||||
@@ -678,6 +716,12 @@ public class UmaViewerBuilder : MonoBehaviour
|
||||
{
|
||||
umaContainer.LoadAnimation(asset);
|
||||
}
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)
|
||||
UI.ClearColorSetButtons();
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void LoadProp(UmaDatabaseEntry entry)
|
||||
|
||||
@@ -59,6 +59,12 @@ public class UmaViewerMain : MonoBehaviour
|
||||
var MobCharaData = UmaDatabaseController.Instance.MobCharaData;
|
||||
var loadingUI = UmaSceneController.instance;
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)
|
||||
var charaDressColor = UmaDatabaseController.Instance.CharaDressColor;
|
||||
|
||||
|
||||
|
||||
//EN Translations
|
||||
loadingUI.LoadingProgressChange(loadingStep++, loadingStepsTotal, "Downloading Translations");
|
||||
if (Config.Instance.Language == Language.En)
|
||||
|
||||
@@ -9,6 +9,8 @@ using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.Rendering;
|
||||
using UnityEngine.UI;
|
||||
//修改(载入通用服装ColorSet相关)(保存当前选中的颜色配置到TXT文件)
|
||||
using System.Text;
|
||||
|
||||
public class UmaViewerUI : MonoBehaviour
|
||||
{
|
||||
@@ -46,6 +48,11 @@ public class UmaViewerUI : MonoBehaviour
|
||||
public PoseManager PoseManager;
|
||||
public HandleManager HandleManager;
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)
|
||||
public ScrollRect DressColorSetList;
|
||||
|
||||
|
||||
[Header("lists")]
|
||||
public ScrollRect EmotionList;
|
||||
public Transform FacialList;
|
||||
@@ -102,6 +109,12 @@ public class UmaViewerUI : MonoBehaviour
|
||||
public bool LiveTime = false;
|
||||
public bool isRecordVMD;
|
||||
public bool isRequireStage = true;
|
||||
|
||||
|
||||
//修改(Live演出的显隐控制和位置变换相关)
|
||||
public bool isControlMode = true;
|
||||
|
||||
|
||||
public Dropdown LiveRecoedToggle;
|
||||
public int LiveMode = 1;
|
||||
|
||||
@@ -372,6 +385,9 @@ public class UmaViewerUI : MonoBehaviour
|
||||
action(UmaContainerSliderPrefab, target, targetMorph, OtherList);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//修改(Mini角色表情滑条修复)
|
||||
public void LoadFacialPanelsMini(Material Eye, Material MayuL, Material MayuR, Material Mouth)
|
||||
{
|
||||
currentFaceDrivenKeyTarget = null;
|
||||
@@ -387,24 +403,37 @@ public class UmaViewerUI : MonoBehaviour
|
||||
container.Name = name;
|
||||
container.Slider.wholeNumbers = true;
|
||||
container.Slider.value = 0;
|
||||
container.Slider.maxValue = 15;
|
||||
|
||||
if (type == 0)
|
||||
container.Slider.maxValue = 18;
|
||||
else if (type == 1)
|
||||
container.Slider.maxValue = 14;
|
||||
else if (type == 2)
|
||||
container.Slider.maxValue = 14;
|
||||
else if (type == 3)
|
||||
container.Slider.maxValue = 8;
|
||||
|
||||
container.Slider.minValue = 0;
|
||||
container.Slider.onValueChanged.AddListener((a) =>
|
||||
{
|
||||
var val = Convert.ToInt32(a);
|
||||
if (type == 0)
|
||||
{
|
||||
mat.mainTextureOffset = new Vector2(0.25f * (val % 4), -0.25f * (val / 4));
|
||||
mat.mainTextureOffset = new Vector2(0.25f * (val % 4), -0.125f * (val / 4));
|
||||
}
|
||||
else if (type == 1)
|
||||
{
|
||||
var vector = mat.GetVector("_UVOffset");
|
||||
mat.SetVector("_UVOffset", new Vector4(0.25f * (val % 4), -0.25f * (val / 4), vector.z, vector.w));
|
||||
mat.SetVector("_UVOffset", new Vector4(0.25f * (val % 4), -0.125f * (val / 4), vector.z, vector.w));
|
||||
}
|
||||
else if (type == 2)
|
||||
{
|
||||
var vector = mat.GetVector("_UVOffset");
|
||||
mat.SetVector("_UVOffset", new Vector4(vector.x, vector.y, 0.25f * (val % 4), -0.25f * (val / 4)));
|
||||
mat.SetVector("_UVOffset", new Vector4(vector.x, vector.y, 0.25f * (val % 4), -0.125f * (val / 4)));
|
||||
}
|
||||
else if (type == 3)
|
||||
{
|
||||
mat.mainTextureOffset = new Vector2(0.25f * (val % 4), -0.25f * (val / 4));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -412,10 +441,12 @@ public class UmaViewerUI : MonoBehaviour
|
||||
action(EyeList, "Eye_L_Select", Eye, 2);
|
||||
action(EyeList, "Eye_R_Select", Eye, 1);
|
||||
action(MouthList, "Mouth_Select", Mouth, 0);
|
||||
action(EyeBrowList, "Mayu_L_Select", MayuL, 0);
|
||||
action(EyeBrowList, "Mayu_R_Select", MayuR, 0);
|
||||
action(EyeBrowList, "Mayu_L_Select", MayuL, 3);
|
||||
action(EyeBrowList, "Mayu_R_Select", MayuR, 3);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void UpdateFacialPanels()
|
||||
{
|
||||
if (currentFaceDrivenKeyTarget)
|
||||
@@ -1113,6 +1144,12 @@ public class UmaViewerUI : MonoBehaviour
|
||||
|
||||
public void SetLiveRequireStage(bool val) { isRequireStage = val; }
|
||||
|
||||
|
||||
//修改(Live演出的显隐控制和位置变换相关)
|
||||
public void SetLiveControlMode(bool val) { isControlMode = val; }
|
||||
|
||||
|
||||
|
||||
public void ShowMessage(string msg, UIMessageType type)
|
||||
{
|
||||
if (!MessageText) return;
|
||||
@@ -1133,4 +1170,181 @@ public class UmaViewerUI : MonoBehaviour
|
||||
{
|
||||
go.SetActive(!go.activeSelf);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)(颜色组UI)
|
||||
public static string CurrentSelectedColorSetId;
|
||||
|
||||
public void LoadColorSetButtons()
|
||||
{
|
||||
ClearColorSetButtons();
|
||||
|
||||
var uma = Builder.CurrentUMAContainer;
|
||||
if (uma == null || string.IsNullOrEmpty(uma.VarCostumeIdShort)) return;
|
||||
|
||||
var parts = uma.VarCostumeIdShort.Split('_');
|
||||
if (parts.Length < 2) return;
|
||||
|
||||
if (!int.TryParse(parts[0], out int bodyType)) return;
|
||||
if (!int.TryParse(parts[1], out int bodyTypeSub)) return;
|
||||
|
||||
if (bodyType == 7 || bodyType == 8)
|
||||
{
|
||||
var colorSetIds = UmaDatabaseController.Instance.CharaDressColor
|
||||
.Select(row => row["id"].ToString());
|
||||
|
||||
foreach (var id in colorSetIds)
|
||||
{
|
||||
var buttonObj = Instantiate(UmaContainerPrefab, DressColorSetList.content);
|
||||
var container = buttonObj.GetComponent<UmaUIContainer>();
|
||||
|
||||
container.Name = id;
|
||||
var buttonText = buttonObj.GetComponentInChildren<Text>();
|
||||
if (buttonText != null) buttonText.text = id;
|
||||
|
||||
container.Button.onClick.AddListener(() =>
|
||||
{
|
||||
HighlightChildImage(DressColorSetList.content, container);
|
||||
CurrentSelectedColorSetId = id;
|
||||
Builder.CurrentUMAContainer?.RefreshDressColor();
|
||||
});
|
||||
|
||||
if (container.ToggleImage != null)
|
||||
container.ToggleImage.enabled = (id == CurrentSelectedColorSetId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var dressRow = UmaDatabaseController.Instance.DressData.FirstOrDefault(row =>
|
||||
int.Parse(row["body_type"].ToString()) == bodyType &&
|
||||
int.Parse(row["body_type_sub"].ToString()) == bodyTypeSub);
|
||||
|
||||
if (dressRow == null || dressRow["body_setting"].ToString() != "4") return;
|
||||
|
||||
string dressId = dressRow["id"].ToString();
|
||||
|
||||
var colorSetRows = UmaDatabaseController.Instance.CharaDressColor
|
||||
.Where(row => row["dress_id"].ToString() == dressId);
|
||||
|
||||
foreach (var row in colorSetRows)
|
||||
{
|
||||
string id = row["id"].ToString();
|
||||
|
||||
var buttonObj = Instantiate(UmaContainerPrefab, DressColorSetList.content);
|
||||
var container = buttonObj.GetComponent<UmaUIContainer>();
|
||||
|
||||
container.Name = id;
|
||||
var buttonText = buttonObj.GetComponentInChildren<Text>();
|
||||
if (buttonText != null) buttonText.text = id;
|
||||
|
||||
container.Button.onClick.AddListener(() =>
|
||||
{
|
||||
HighlightChildImage(DressColorSetList.content, container);
|
||||
CurrentSelectedColorSetId = id;
|
||||
Builder.CurrentUMAContainer?.RefreshDressColor();
|
||||
});
|
||||
|
||||
if (container.ToggleImage != null)
|
||||
container.ToggleImage.enabled = (id == CurrentSelectedColorSetId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)(清理所有颜色组按钮)
|
||||
public void ClearColorSetButtons()
|
||||
{
|
||||
foreach (Transform child in DressColorSetList.content)
|
||||
{
|
||||
Destroy(child.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)(解析颜色字符串)
|
||||
private static bool TryParseHexColor(
|
||||
string hex,
|
||||
out Tuple<byte, byte, byte> rgb255,
|
||||
out Tuple<float, float, float> rgbNormalized)
|
||||
{
|
||||
rgb255 = null;
|
||||
rgbNormalized = null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(hex) || !hex.StartsWith("#") || hex.Length != 7)
|
||||
return false;
|
||||
|
||||
byte r, g, b;
|
||||
bool okR = byte.TryParse(hex.Substring(1, 2), System.Globalization.NumberStyles.HexNumber, null, out r);
|
||||
bool okG = byte.TryParse(hex.Substring(3, 2), System.Globalization.NumberStyles.HexNumber, null, out g);
|
||||
bool okB = byte.TryParse(hex.Substring(5, 2), System.Globalization.NumberStyles.HexNumber, null, out b);
|
||||
|
||||
if (!okR || !okG || !okB)
|
||||
return false;
|
||||
|
||||
rgb255 = Tuple.Create(r, g, b);
|
||||
rgbNormalized = Tuple.Create(r / 255f, g / 255f, b / 255f);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//修改(载入通用服装ColorSet相关)(保存当前选中的颜色配置到TXT文件)
|
||||
public static void SaveCurrentColorSetRow()
|
||||
{
|
||||
if (string.IsNullOrEmpty(CurrentSelectedColorSetId))
|
||||
{
|
||||
UmaViewerUI.Instance.ShowMessage("No color set ID selected!", UIMessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
var charaColor = UmaDatabaseController.Instance.CharaDressColor
|
||||
.FirstOrDefault(row => row["id"].ToString() == CurrentSelectedColorSetId);
|
||||
|
||||
if (charaColor == null)
|
||||
{
|
||||
UmaViewerUI.Instance.ShowMessage($"Color set ID {CurrentSelectedColorSetId} not found!", UIMessageType.Error);
|
||||
return;
|
||||
}
|
||||
|
||||
#if UNITY_ANDROID && !UNITY_EDITOR
|
||||
string fileDirectory = Application.persistentDataPath + "/../ColorCode/";
|
||||
#else
|
||||
string fileDirectory = Application.dataPath + "/../ColorCode/";
|
||||
#endif
|
||||
Directory.CreateDirectory(fileDirectory);
|
||||
|
||||
string idValue = charaColor["id"]?.ToString() ?? "unknown";
|
||||
string fileName = $"{idValue}.txt";
|
||||
string fullPath = Path.GetFullPath(Path.Combine(fileDirectory, fileName));
|
||||
|
||||
List<string> lines = new List<string>();
|
||||
foreach (var column in charaColor.Table.Columns)
|
||||
{
|
||||
string columnName = column.ToString();
|
||||
string value = charaColor[columnName]?.ToString() ?? "";
|
||||
|
||||
if (TryParseHexColor(value, out var rgb255, out var rgbNormalized))
|
||||
{
|
||||
string prefix = (columnName + ":").PadRight(17);
|
||||
lines.Add(
|
||||
$"{prefix}\t{value}\t" +
|
||||
$"({rgb255.Item1,3}, {rgb255.Item2,3}, {rgb255.Item3,3})\t" +
|
||||
$"({rgbNormalized.Item1:0.####}, {rgbNormalized.Item2:0.####}, {rgbNormalized.Item3:0.####})"
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
lines.Add($"{columnName}: {value}");
|
||||
}
|
||||
}
|
||||
|
||||
string content = string.Join("\n", lines);
|
||||
File.WriteAllText(fullPath, content, Encoding.UTF8);
|
||||
|
||||
UmaViewerUI.Instance.ShowMessage($"ColorSet data saved: {fullPath}", UIMessageType.Success);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,7 @@ namespace Gallop
|
||||
public bool IsEnableHipMoveParam;
|
||||
public float HipMoveInfluenceDistance;
|
||||
public float HipMoveInfluenceMaxDistance;
|
||||
public bool UseCorrectScaleCalc;
|
||||
|
||||
public List<DynamicBone> DynamicBones = new List<DynamicBone>();
|
||||
|
||||
@@ -43,6 +44,39 @@ namespace Gallop
|
||||
child.transform.localScale = Vector3.one;
|
||||
colliders.Add(child.name, child.transform);
|
||||
|
||||
|
||||
//修改(动骨与碰撞相关)
|
||||
if (collider._collisionName == "Col_B_Hip_Tail")
|
||||
{
|
||||
collider._radius *= 0.96f;
|
||||
}
|
||||
else if (collider._collisionName == "Col_B_Chest_Tail")
|
||||
{
|
||||
collider._radius *= 1.14f;
|
||||
}
|
||||
else if (collider._collisionName.Contains("Col_B_Hip_Skirt"))
|
||||
{
|
||||
collider._radius *= 0.8f;
|
||||
}
|
||||
else if (collider._collisionName.Contains("Col_B_Hip_Jacket"))
|
||||
{
|
||||
collider._radius *= 1f;
|
||||
}
|
||||
else if (collider._collisionName == "Col_Elbow_R_Hair")
|
||||
{
|
||||
collider._radius *= 0.3f;
|
||||
}
|
||||
else if (collider._collisionName == "Col_Elbow_L_Hair")
|
||||
{
|
||||
collider._radius *= 0.3f;
|
||||
}
|
||||
else
|
||||
{
|
||||
collider._radius *= 0.9f;
|
||||
}
|
||||
|
||||
|
||||
|
||||
switch (collider._type)
|
||||
{
|
||||
case CySpringCollisionData.CollisionType.Capsule:
|
||||
@@ -77,72 +111,117 @@ namespace Gallop
|
||||
return colliders;
|
||||
}
|
||||
|
||||
|
||||
|
||||
//修改(动骨与碰撞相关)
|
||||
public void InitializePhysics(Dictionary<string, Transform> bones, Dictionary<string, Transform> colliders)
|
||||
{
|
||||
DynamicBones.Clear();
|
||||
|
||||
// 获取对象名称并判断类型
|
||||
string nameLower = gameObject.name.ToLower();
|
||||
bool isTailObject = nameLower.Contains("tail"); // 判断是否为尾巴
|
||||
bool isClothObject = !isTailObject && nameLower.Contains("chr"); // 判断是否为头发,排除尾巴避免重复匹配
|
||||
|
||||
foreach (CySpringParamDataElement spring in springParam)
|
||||
{
|
||||
if (bones.TryGetValue(spring._boneName,out Transform bone))
|
||||
if (!bones.TryGetValue(spring._boneName, out Transform bone)) continue;
|
||||
|
||||
var dynamic = bone.gameObject.AddComponent<DynamicBone>();
|
||||
dynamic.m_Root = bone;
|
||||
|
||||
// 设置重力
|
||||
dynamic.m_Gravity = new Vector3(0, Mathf.Clamp01(-30f / spring._gravity), 0);
|
||||
dynamic.m_LimitAngel_Min = spring._limitAngleMin;
|
||||
dynamic.m_LimitAngel_Max = spring._limitAngleMax;
|
||||
|
||||
// 主参数设置
|
||||
if (isTailObject)
|
||||
{
|
||||
bool isTail = gameObject.name.Contains("tail");//The tail needs less traction
|
||||
var dynamic = bone.gameObject.AddComponent<DynamicBone>();
|
||||
dynamic.m_Root = bone;
|
||||
dynamic.m_Gravity = new Vector3(0, Mathf.Clamp01(-30 / spring._gravity), 0);
|
||||
dynamic.m_LimitAngel_Min = spring._limitAngleMin;
|
||||
dynamic.m_LimitAngel_Max = spring._limitAngleMax;
|
||||
if (isTail)
|
||||
// 尾巴主骨骼
|
||||
dynamic.m_Damping = 0.05f;
|
||||
dynamic.m_Stiffness = Mathf.Clamp01(20f / spring._stiffnessForce);
|
||||
dynamic.m_Elasticity = Mathf.Clamp01(20f / spring._dragForce);
|
||||
dynamic.m_Radius = spring._collisionRadius * 0.9f;
|
||||
dynamic.m_Inert = spring.MoveSpringApplyRate / 4f;
|
||||
}
|
||||
else if (isClothObject)
|
||||
{
|
||||
// 头发主骨骼
|
||||
dynamic.m_Damping = 0.1f;
|
||||
dynamic.m_Stiffness = Mathf.Clamp01(15f / spring._stiffnessForce);
|
||||
dynamic.m_Elasticity = Mathf.Clamp01(20f / spring._dragForce);
|
||||
dynamic.m_Radius = spring._collisionRadius * 0.9f;
|
||||
dynamic.m_Inert = spring.MoveSpringApplyRate / 2.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 衣服主骨骼
|
||||
dynamic.m_Damping = 0.1f;
|
||||
dynamic.m_Stiffness = Mathf.Clamp01(20f / spring._stiffnessForce);
|
||||
dynamic.m_Elasticity = Mathf.Clamp01(30f / spring._dragForce);
|
||||
dynamic.m_Radius = spring._collisionRadius * 0.8f;
|
||||
dynamic.m_Inert = spring.MoveSpringApplyRate / 3f;
|
||||
}
|
||||
|
||||
dynamic.SetupParticles();
|
||||
DynamicBones.Add(dynamic);
|
||||
|
||||
// 主碰撞器添加
|
||||
foreach (string collisionName in spring._collisionNameList)
|
||||
{
|
||||
if (colliders.TryGetValue(collisionName, out Transform tmp))
|
||||
{
|
||||
dynamic.m_Damping = 0.1f;
|
||||
dynamic.m_Stiffness = Mathf.Clamp01(10 / spring._stiffnessForce);
|
||||
dynamic.m_Elasticity = Mathf.Clamp01(10 / spring._dragForce);
|
||||
dynamic.Particles[0].m_Colliders.Add(tmp.GetComponent<DynamicBoneColliderBase>());
|
||||
}
|
||||
}
|
||||
|
||||
// 子参数设置
|
||||
foreach (var child in spring._childElements)
|
||||
{
|
||||
var tempParticle = dynamic.Particles.Find(p => p.m_Transform.gameObject.name == child._boneName);
|
||||
if (tempParticle == null) continue;
|
||||
|
||||
if (isTailObject)
|
||||
{
|
||||
// 尾巴子粒子
|
||||
tempParticle.m_Damping = 0.05f;
|
||||
tempParticle.m_Stiffness = Mathf.Clamp01(20f / child._stiffnessForce);
|
||||
tempParticle.m_Elasticity = Mathf.Clamp01(20f / child._dragForce);
|
||||
tempParticle.m_Radius = child._collisionRadius * 1f;
|
||||
tempParticle.m_Inert = child.MoveSpringApplyRate / 4f;
|
||||
}
|
||||
else if (isClothObject)
|
||||
{
|
||||
// 头发子粒子
|
||||
tempParticle.m_Damping = 0.1f;
|
||||
tempParticle.m_Stiffness = Mathf.Clamp01(15f / child._stiffnessForce);
|
||||
tempParticle.m_Elasticity = Mathf.Clamp01(20f / child._dragForce);
|
||||
tempParticle.m_Radius = child._collisionRadius * 1f;
|
||||
tempParticle.m_Inert = child.MoveSpringApplyRate / 2.5f;
|
||||
}
|
||||
else
|
||||
{
|
||||
dynamic.m_Damping = 0.2f;
|
||||
dynamic.m_Stiffness = Mathf.Clamp01(45 / spring._stiffnessForce);
|
||||
dynamic.m_Elasticity = Mathf.Clamp01(45 / spring._dragForce);
|
||||
|
||||
// 衣服子粒子
|
||||
tempParticle.m_Damping = 0.1f;
|
||||
tempParticle.m_Stiffness = Mathf.Clamp01(20f / child._stiffnessForce);
|
||||
tempParticle.m_Elasticity = Mathf.Clamp01(30f / child._dragForce);
|
||||
tempParticle.m_Radius = child._collisionRadius * 0.8f;
|
||||
tempParticle.m_Inert = child.MoveSpringApplyRate / 2f;
|
||||
}
|
||||
dynamic.m_Radius = spring._collisionRadius;
|
||||
dynamic.m_Inert = spring.MoveSpringApplyRate / 3;
|
||||
dynamic.SetupParticles();
|
||||
DynamicBones.Add(dynamic);
|
||||
|
||||
foreach (string collisionName in spring._collisionNameList)
|
||||
|
||||
tempParticle.m_LimitAngel_Min = child._limitAngleMin;
|
||||
tempParticle.m_LimitAngel_Max = child._limitAngleMax;
|
||||
|
||||
// 子碰撞器添加
|
||||
foreach (string collisionName in child._collisionNameList)
|
||||
{
|
||||
if (colliders.TryGetValue(collisionName,out Transform tmp))
|
||||
if (colliders.TryGetValue(collisionName, out Transform tmp))
|
||||
{
|
||||
dynamic.Particles[0].m_Colliders.Add(tmp.GetComponent<DynamicBoneColliderBase>());
|
||||
}
|
||||
}
|
||||
|
||||
foreach (CySpringParamDataChildElement Childcollision in spring._childElements)
|
||||
{
|
||||
var tempParticle = dynamic.Particles.Find(a => { return a.m_Transform.gameObject.name == Childcollision._boneName; });
|
||||
if (tempParticle != null)
|
||||
{
|
||||
if (isTail)
|
||||
var collider = tmp.GetComponent<DynamicBoneColliderBase>();
|
||||
if (collider != null)
|
||||
{
|
||||
tempParticle.m_Damping = 0.1f;
|
||||
tempParticle.m_Stiffness = Mathf.Clamp01(10 / Childcollision._stiffnessForce);
|
||||
tempParticle.m_Elasticity = Mathf.Clamp01(10 / Childcollision._dragForce);
|
||||
}
|
||||
else
|
||||
{
|
||||
tempParticle.m_Damping = 0.2f;
|
||||
tempParticle.m_Stiffness = Mathf.Clamp01(45 / Childcollision._stiffnessForce);
|
||||
tempParticle.m_Elasticity = Mathf.Clamp01(45 / Childcollision._dragForce);
|
||||
}
|
||||
tempParticle.m_Inert = Childcollision.MoveSpringApplyRate / 3;
|
||||
tempParticle.m_Radius = Childcollision._collisionRadius;
|
||||
tempParticle.m_LimitAngel_Min = Childcollision._limitAngleMin;
|
||||
tempParticle.m_LimitAngel_Max = Childcollision._limitAngleMax;
|
||||
foreach (string collisionName in Childcollision._collisionNameList)
|
||||
{
|
||||
if (colliders.TryGetValue(collisionName,out Transform tmp))
|
||||
{
|
||||
tempParticle.m_Colliders.Add(tmp.GetComponent<DynamicBoneColliderBase>());
|
||||
}
|
||||
tempParticle.m_Colliders.Add(collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -150,6 +229,8 @@ namespace Gallop
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void EnablePhysics(bool isOn)
|
||||
{
|
||||
foreach(DynamicBone dynamic in DynamicBones)
|
||||
|
||||
@@ -616,72 +616,101 @@ namespace Gallop.Live.Cutt
|
||||
return LiveTimelineEasing.GetValue(liveTimelineKeyWithInterpolate2.easingType, curFrame - (float)from.frame, 0f, 1f, (float)num);
|
||||
}
|
||||
|
||||
|
||||
|
||||
//修改(Live演出的显隐控制和位置变换相关)
|
||||
public void LateUpdateFormationOffset_Transform(int targetIndex, LiveTimelineKeyIndex curKeyIndex, float time)
|
||||
{
|
||||
bool ControlMode = UmaViewerUI.Instance != null && UmaViewerUI.Instance.isControlMode;
|
||||
|
||||
LiveTimelineKeyFormationOffsetData curKey = curKeyIndex.key as LiveTimelineKeyFormationOffsetData;
|
||||
LiveTimelineKeyFormationOffsetData nextKey = curKeyIndex.nextKey as LiveTimelineKeyFormationOffsetData;
|
||||
|
||||
var chara = Director.instance.CharaContainerScript[targetIndex];
|
||||
if (!chara) return;
|
||||
|
||||
if(chara.LiveVisible != curKey.visible)
|
||||
{
|
||||
chara.Materials.ForEach(m =>
|
||||
{
|
||||
foreach (var key in m.Renderers.Keys)
|
||||
{
|
||||
if (key.gameObject.activeSelf != curKey.visible)
|
||||
key.gameObject.SetActive(curKey.visible);
|
||||
}
|
||||
});
|
||||
chara.LiveVisible = curKey.visible;
|
||||
}
|
||||
|
||||
|
||||
if (curKey.visible || IsRecordVMD)
|
||||
if (ControlMode)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(curKey.ParentObjectName))
|
||||
if (chara.LiveVisible != curKey.visible)
|
||||
{
|
||||
var parent_transform = curKey.GetParentObjectTransform(this);
|
||||
if (parent_transform)
|
||||
chara.Materials.ForEach(m =>
|
||||
{
|
||||
if (chara.transform.parent != parent_transform)
|
||||
foreach (var key in m.Renderers.Keys)
|
||||
{
|
||||
if (key.gameObject.activeSelf != curKey.visible)
|
||||
key.gameObject.SetActive(curKey.visible);
|
||||
}
|
||||
});
|
||||
chara.LiveVisible = curKey.visible;
|
||||
}
|
||||
|
||||
|
||||
if (curKey.visible || IsRecordVMD)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(curKey.ParentObjectName))
|
||||
{
|
||||
var parent_transform = curKey.GetParentObjectTransform(this);
|
||||
if (parent_transform)
|
||||
{
|
||||
if (chara.transform.parent != parent_transform)
|
||||
{
|
||||
chara.transform.SetParent(parent_transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (chara.transform.parent)
|
||||
{
|
||||
chara.transform.SetParent(null);
|
||||
}
|
||||
|
||||
if (nextKey != null && nextKey.interpolateType != LiveCameraInterpolateType.None)
|
||||
{
|
||||
float ratio = CalculateInterpolationValue(curKey, nextKey, time * 60);
|
||||
chara.transform.localPosition = Vector3.Lerp(curKey.Position, nextKey.Position, ratio);
|
||||
var x = chara.transform.eulerAngles.x;
|
||||
var z = chara.transform.eulerAngles.z;
|
||||
chara.transform.eulerAngles = new Vector3(x, Mathf.Lerp(curKey.RotationY, nextKey.RotationY, ratio), z);
|
||||
|
||||
var local_x = chara.Position.localEulerAngles.x;
|
||||
var local_z = chara.Position.localEulerAngles.z;
|
||||
chara.Position.localEulerAngles = new Vector3(local_x, Mathf.Lerp(curKey.LocalRotationY, nextKey.LocalRotationY, ratio), local_z);
|
||||
}
|
||||
else
|
||||
{
|
||||
chara.transform.localPosition = curKey.Position;
|
||||
var x = chara.transform.eulerAngles.x;
|
||||
var z = chara.transform.eulerAngles.z;
|
||||
chara.transform.eulerAngles = new Vector3(x, curKey.RotationY, z);
|
||||
|
||||
var local_x = chara.Position.localEulerAngles.x;
|
||||
var local_z = chara.Position.localEulerAngles.z;
|
||||
chara.Position.localEulerAngles = new Vector3(local_x, curKey.LocalRotationY, local_z);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (curKey.visible || IsRecordVMD)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(curKey.ParentObjectName))
|
||||
{
|
||||
var parent_transform = curKey.GetParentObjectTransform(this);
|
||||
if (parent_transform && chara.transform.parent != parent_transform)
|
||||
{
|
||||
chara.transform.SetParent(parent_transform);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (chara.transform.parent)
|
||||
{
|
||||
chara.transform.SetParent(null);
|
||||
}
|
||||
|
||||
if (nextKey != null && nextKey.interpolateType != LiveCameraInterpolateType.None)
|
||||
{
|
||||
float ratio = CalculateInterpolationValue(curKey, nextKey, time * 60);
|
||||
chara.transform.localPosition = Vector3.Lerp(curKey.Position, nextKey.Position, ratio);
|
||||
var x = chara.transform.eulerAngles.x;
|
||||
var z = chara.transform.eulerAngles.z;
|
||||
chara.transform.eulerAngles = new Vector3(x, Mathf.Lerp(curKey.RotationY, nextKey.RotationY, ratio), z);
|
||||
|
||||
var local_x = chara.Position.localEulerAngles.x;
|
||||
var local_z = chara.Position.localEulerAngles.z;
|
||||
chara.Position.localEulerAngles = new Vector3(local_x, Mathf.Lerp(curKey.LocalRotationY, nextKey.LocalRotationY, ratio), local_z);
|
||||
}
|
||||
else
|
||||
{
|
||||
chara.transform.localPosition = curKey.Position;
|
||||
var x = chara.transform.eulerAngles.x;
|
||||
var z = chara.transform.eulerAngles.z;
|
||||
chara.transform.eulerAngles = new Vector3(x, curKey.RotationY, z);
|
||||
|
||||
var local_x = chara.Position.localEulerAngles.x;
|
||||
var local_z = chara.Position.localEulerAngles.z;
|
||||
chara.Position.localEulerAngles = new Vector3(local_x, curKey.LocalRotationY, local_z);
|
||||
else if (chara.transform.parent)
|
||||
{
|
||||
chara.transform.SetParent(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static void FindTimelineKeyCurrent(out LiveTimelineKey curKey, ILiveTimelineKeyDataList keys, float curFrame)
|
||||
{
|
||||
LiveTimelineKey nextKey;
|
||||
|
||||
@@ -18,6 +18,9 @@ public class CySpringCollisionData
|
||||
[SerializeField]
|
||||
public string _targetObjectName;
|
||||
|
||||
[SerializeField]
|
||||
public bool _isOtherTarget;
|
||||
|
||||
[SerializeField]
|
||||
public Vector3 _offset;
|
||||
|
||||
@@ -27,12 +30,17 @@ public class CySpringCollisionData
|
||||
[SerializeField]
|
||||
public float _radius;
|
||||
|
||||
[SerializeField]
|
||||
public float _distance;
|
||||
|
||||
[SerializeField]
|
||||
public Vector3 _normal;
|
||||
|
||||
[SerializeField]
|
||||
public CollisionType _type;
|
||||
|
||||
public Transform _transform;
|
||||
|
||||
public float _distance;
|
||||
public Vector3 _normal;
|
||||
[SerializeField]
|
||||
public bool _isInner;
|
||||
|
||||
public Transform _transform;
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 13dfe63a6961723469de3873728ddf8e
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Binary file not shown.
@@ -0,0 +1,21 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ea8076455073e4d4d836f95d66cfeaca
|
||||
TrueTypeFontImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 4
|
||||
fontSize: 16
|
||||
forceTextureCase: -2
|
||||
characterSpacing: 0
|
||||
characterPadding: 1
|
||||
includeFontData: 1
|
||||
fontNames:
|
||||
- UMAMUSUME-RodinWanpaku Pro
|
||||
fallbackFontReferences: []
|
||||
customCharacters:
|
||||
fontRenderingMode: 0
|
||||
ascentCalculationMode: 1
|
||||
useLegacyBoundsCalculation: 0
|
||||
shouldRoundAdvanceValue: 1
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user