feat: support asset bundle
This commit is contained in:
@@ -59,3 +59,6 @@ crashlytics-build.properties
|
||||
# Temporary auto-generated Android Assets
|
||||
/Assets/StreamingAssets/aa.meta
|
||||
/Assets/StreamingAssets/aa/*
|
||||
|
||||
/Assets/StreamingAssets/
|
||||
/Assets/StreamingAssets.meta
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ad7387f41068f465fbabd034439fbd89
|
||||
labels:
|
||||
- Audio
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07dda0761dca5405ebf092d473b8e0e3
|
||||
labels:
|
||||
- Audio
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0d356a0e07bd4566b4e303a87d7e3545
|
||||
timeCreated: 1778817283
|
||||
@@ -0,0 +1,28 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OCES
|
||||
{
|
||||
public class AssetBundleAssetProvider : IAssetProvider
|
||||
{
|
||||
readonly AssetBundle m_assetBundle;
|
||||
|
||||
public AssetBundleAssetProvider(string bundleFilePath)
|
||||
{
|
||||
this.m_assetBundle = AssetBundle.LoadFromFile(bundleFilePath);
|
||||
if (this.m_assetBundle is null)
|
||||
{
|
||||
Debug.LogError($"[OCES] 无法加载: {bundleFilePath}");
|
||||
}
|
||||
}
|
||||
|
||||
public T Load<T>(string path) where T : UnityEngine.Object
|
||||
{
|
||||
return this.m_assetBundle.LoadAsset<T>(path);
|
||||
}
|
||||
public ResourceRequest LoadAsync<T>(string path) where T : UnityEngine.Object
|
||||
{
|
||||
return this.m_assetBundle.LoadAssetAsync<T>(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 12e6fce897c74d9fbb347666d13123d1
|
||||
timeCreated: 1778817328
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 94c95f233df548289406144c77e05d0b
|
||||
timeCreated: 1778828249
|
||||
@@ -0,0 +1,107 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace OCES.Editor
|
||||
{
|
||||
public static class AssetBundleBuilder
|
||||
{
|
||||
static string BuildBundle(string bundleName, string outputDir, BuildTarget buildTarget, params string[] sourceDirs)
|
||||
{
|
||||
// 1. 收集所有文件(递归)
|
||||
List<string> assetNames = new();
|
||||
List<string> addressableNames = new();
|
||||
|
||||
foreach (string dir in sourceDirs)
|
||||
{
|
||||
if (!Directory.Exists(dir))
|
||||
{
|
||||
Debug.LogWarning($"[AssetBundleBuilder] 目录不存在,跳过: {dir}");
|
||||
continue;
|
||||
}
|
||||
|
||||
string[] files = Directory.GetFiles(dir, "*.*", SearchOption.AllDirectories);
|
||||
foreach (string file in files)
|
||||
{
|
||||
if (file.EndsWith(".meta")) continue;
|
||||
if (file.EndsWith(".DS_Store")) continue;
|
||||
if (file.StartsWith("._")) continue;
|
||||
if (file.EndsWith(".pkf")) continue;
|
||||
|
||||
string assetPath = file.Replace("\\", "/");
|
||||
assetNames.Add(assetPath);
|
||||
|
||||
string relative = assetPath.Replace("Assets/Resources/", "");
|
||||
string withoutExt = Path.ChangeExtension(relative, null);
|
||||
addressableNames.Add(withoutExt);
|
||||
}
|
||||
}
|
||||
|
||||
if (assetNames.Count == 0)
|
||||
{
|
||||
return $"[AssetBundleBuilder] {bundleName}: 无文件";
|
||||
}
|
||||
Directory.CreateDirectory(outputDir);
|
||||
|
||||
// 2. 构建
|
||||
AssetBundleBuild build = new()
|
||||
{
|
||||
assetBundleName = bundleName + ".ab",
|
||||
assetNames = assetNames.ToArray(),
|
||||
addressableNames = addressableNames.ToArray()
|
||||
};
|
||||
|
||||
AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles(
|
||||
outputDir,
|
||||
new[] { build },
|
||||
BuildAssetBundleOptions.None,
|
||||
buildTarget);
|
||||
|
||||
return !manifest ? $"[{bundleName}.ab] 构建失败"
|
||||
: $"[AssetBundleBuilder] {bundleName}.ab 构建完成,包含 {assetNames.Count} 个资源";
|
||||
|
||||
}
|
||||
|
||||
internal static string BuildBundles(
|
||||
bool buildAudio, string audioBundlePath,
|
||||
bool buildHaptic, string hapticBundlePath,
|
||||
BuildTarget buildTarget)
|
||||
{
|
||||
string log = "";
|
||||
|
||||
if (buildAudio)
|
||||
log += BuildBundleFromPath(audioBundlePath, buildTarget,
|
||||
"Assets/Resources/Audios",
|
||||
"Assets/Resources/AudioData") + "\n";
|
||||
|
||||
if (buildHaptic)
|
||||
log += BuildBundleFromPath(hapticBundlePath, buildTarget,
|
||||
"Assets/Resources/Haptics",
|
||||
"Assets/Resources/HapticData") + "\n";
|
||||
|
||||
AssetDatabase.Refresh();
|
||||
return log.TrimEnd('\n');
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据配置中的相对路径构建单个 Bundle。
|
||||
/// </summary>
|
||||
/// <param name="relativePath">配置中的路径,如 "Bundles/audios.ab"</param>
|
||||
/// <param name="target"></param>
|
||||
/// <param name="sourceDirs"></param>
|
||||
public static string BuildBundleFromPath(
|
||||
string relativePath, BuildTarget target, params string[] sourceDirs)
|
||||
{
|
||||
// 解析路径 → 输出目录 + Bundle 名
|
||||
string fullPath = Path.Combine(Application.streamingAssetsPath, relativePath);
|
||||
string outputDir = Path.GetDirectoryName(fullPath);
|
||||
string bundleName = Path.GetFileNameWithoutExtension(fullPath);
|
||||
|
||||
return string.IsNullOrEmpty(bundleName)
|
||||
? $"[AssetBundleBuilder] 无效路径: {relativePath}"
|
||||
: BuildBundle(bundleName, outputDir, target, sourceDirs);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c370f95cedb246969a5831a5bda067c2
|
||||
timeCreated: 1778828259
|
||||
@@ -0,0 +1,131 @@
|
||||
using System.Linq;
|
||||
using OCES.Audio;
|
||||
using OCES.Haptic;
|
||||
using UnityEngine;
|
||||
using UnityEditor;
|
||||
|
||||
namespace OCES.Editor
|
||||
{
|
||||
public class AssetBundleBuilderWindow : EditorWindow
|
||||
{
|
||||
bool m_buildAudio = true;
|
||||
bool m_buildHaptic = true;
|
||||
int m_targetIndex;
|
||||
string m_audioBundleSourcePath;
|
||||
string m_hapticBundleSourcePath;
|
||||
|
||||
string m_log = "";
|
||||
Vector2 m_scrollPos;
|
||||
|
||||
// Settings 资源路径
|
||||
const string k_audioSettingsPath = "Assets/Settings/AudioExtendSettings.asset";
|
||||
const string k_hapticSettingsPath = "Assets/Settings/HapticSettings.asset";
|
||||
|
||||
static readonly (BuildTarget target, string label)[] s_platforms =
|
||||
{
|
||||
(BuildTarget.Android, "Android"),
|
||||
(BuildTarget.iOS, "iOS"),
|
||||
(BuildTarget.StandaloneOSX, "macOS"),
|
||||
(BuildTarget.StandaloneWindows64, "Windows (x64)"),
|
||||
(BuildTarget.StandaloneLinux64, "Linux (x64)"),
|
||||
};
|
||||
|
||||
static readonly string[] s_platformLabels = s_platforms.Select(p => p.label).ToArray();
|
||||
|
||||
[MenuItem("Tools/OCES/Asset Bundle Builder")]
|
||||
public static void ShowWindow()
|
||||
{
|
||||
AssetBundleBuilderWindow window = GetWindow<AssetBundleBuilderWindow>(
|
||||
false, "Bundle Builder", true);
|
||||
window.minSize = new Vector2(360, 400);
|
||||
window.Show();
|
||||
}
|
||||
|
||||
void OnEnable()
|
||||
{
|
||||
// 默认选中当前 Player 平台
|
||||
BuildTarget current = EditorUserBuildSettings.activeBuildTarget;
|
||||
for (int i = 0; i < s_platforms.Length; i++)
|
||||
{
|
||||
if (s_platforms[i].target != current)
|
||||
continue;
|
||||
this.m_targetIndex = i;
|
||||
return;
|
||||
}
|
||||
// 兜底:Android
|
||||
this.m_targetIndex = 13;
|
||||
}
|
||||
|
||||
void OnGUI()
|
||||
{
|
||||
GUILayout.Space(10);
|
||||
|
||||
// ── Bundle 选择 ──
|
||||
EditorGUILayout.LabelField("Bundles to Build", EditorStyles.boldLabel);
|
||||
this.m_buildAudio = EditorGUILayout.ToggleLeft("Audio Bundle (audios.ab)", this.m_buildAudio);
|
||||
this.m_buildHaptic = EditorGUILayout.ToggleLeft("Haptic Bundle (haptic.ab)", this.m_buildHaptic);
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
// ── 平台选择 ──
|
||||
EditorGUILayout.LabelField("Target Platform", EditorStyles.boldLabel);
|
||||
this.m_targetIndex = EditorGUILayout.Popup(this.m_targetIndex, s_platformLabels);
|
||||
|
||||
// ── 输出路径预览 ──
|
||||
AudioExtendSettings audioSettings =
|
||||
AssetDatabase.LoadAssetAtPath<AudioExtendSettings>(k_audioSettingsPath);
|
||||
HapticSettings hapticSettings =
|
||||
AssetDatabase.LoadAssetAtPath<HapticSettings>(k_hapticSettingsPath);
|
||||
|
||||
if (audioSettings && this.m_buildAudio)
|
||||
EditorGUILayout.LabelField(
|
||||
$" Audio → {Application.streamingAssetsPath}/{audioSettings.audioBundlePath}",
|
||||
EditorStyles.miniLabel);
|
||||
|
||||
if (hapticSettings && this.m_buildHaptic)
|
||||
EditorGUILayout.LabelField(
|
||||
$" Haptic → {Application.streamingAssetsPath}/{hapticSettings.hapticBundlePath}",
|
||||
EditorStyles.miniLabel);
|
||||
|
||||
if (!audioSettings && !hapticSettings)
|
||||
EditorGUILayout.HelpBox(
|
||||
$"未找到 Settings 资产。\n 请初始化对应系统。",
|
||||
MessageType.Warning);
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
// ── 构建按钮 ──
|
||||
bool canBuild = (this.m_buildAudio || this.m_buildHaptic)
|
||||
&& (audioSettings || !this.m_buildAudio)
|
||||
&& (hapticSettings || !this.m_buildHaptic);
|
||||
|
||||
EditorGUI.BeginDisabledGroup(!canBuild);
|
||||
if (GUILayout.Button("Build", GUILayout.Height(32)))
|
||||
{
|
||||
string result = AssetBundleBuilder.BuildBundles(this.m_buildAudio,
|
||||
audioSettings ? audioSettings.audioBundlePath : "", this.m_buildHaptic,
|
||||
hapticSettings != null ? hapticSettings.hapticBundlePath : "",
|
||||
s_platforms[this.m_targetIndex].target);
|
||||
this.m_log += result + "\n";
|
||||
}
|
||||
EditorGUI.EndDisabledGroup();
|
||||
|
||||
GUILayout.Space(10);
|
||||
|
||||
// ── 日志区 ──
|
||||
EditorGUILayout.LabelField("Build Log", EditorStyles.boldLabel);
|
||||
this.m_scrollPos = EditorGUILayout.BeginScrollView(this.m_scrollPos,
|
||||
GUILayout.ExpandHeight(true));
|
||||
EditorGUILayout.TextArea(this.m_log,
|
||||
GUILayout.ExpandHeight(true));
|
||||
EditorGUILayout.EndScrollView();
|
||||
|
||||
// 底部清空日志按钮
|
||||
if (string.IsNullOrEmpty(this.m_log))
|
||||
return;
|
||||
GUILayout.Space(4);
|
||||
if (GUILayout.Button("Clear Log", GUILayout.Width(80)))
|
||||
this.m_log = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f327fedaa18441e2bf347cf46810592d
|
||||
timeCreated: 1778829708
|
||||
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OCES
|
||||
{
|
||||
public interface IAssetProvider
|
||||
{
|
||||
T Load<T>(string path) where T : UnityEngine.Object;
|
||||
ResourceRequest LoadAsync<T>(string path) where T : UnityEngine.Object;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 35a140a1c75444b49ad470726512b817
|
||||
timeCreated: 1778815286
|
||||
+29
-2
@@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using OCES.Audio;
|
||||
using UnityEngine;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
@@ -11,6 +12,12 @@ namespace OCES
|
||||
{
|
||||
readonly Dictionary<string, Object> m_cachedObjects = new();
|
||||
readonly Dictionary<string, List<Action<Object>>> m_pendingCallbacks = new();
|
||||
IAssetProvider m_assetProvider;
|
||||
|
||||
public void SetProvider(IAssetProvider assetProvider)
|
||||
{
|
||||
this.m_assetProvider = assetProvider;
|
||||
}
|
||||
|
||||
[CanBeNull]
|
||||
internal T LoadSync<T>(string path) where T : Object
|
||||
@@ -20,7 +27,17 @@ namespace OCES
|
||||
return cachedObject as T;
|
||||
}
|
||||
|
||||
T newObject = Resources.Load<T>(path);
|
||||
T newObject;
|
||||
if (this.m_assetProvider is not null)
|
||||
{
|
||||
newObject = this.m_assetProvider.Load<T>(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
newObject = Resources.Load<T>(path);
|
||||
Debug.LogWarning($"[ResourceLoader] No IAssetProvider set, falling back to Resources.Load for '{path}'");
|
||||
}
|
||||
|
||||
this.m_cachedObjects.Add(path, newObject);
|
||||
return newObject;
|
||||
}
|
||||
@@ -40,7 +57,17 @@ namespace OCES
|
||||
}
|
||||
|
||||
this.m_pendingCallbacks[path] = new List<Action<Object>> { obj => onComplete?.Invoke(obj as T) };
|
||||
ResourceRequest newRequest = Resources.LoadAsync<T>(path);
|
||||
ResourceRequest newRequest;
|
||||
if (this.m_assetProvider is not null)
|
||||
{
|
||||
newRequest = this.m_assetProvider.LoadAsync<T>(path);
|
||||
}
|
||||
else
|
||||
{
|
||||
newRequest = Resources.LoadAsync<T>(path);
|
||||
Debug.LogWarning($"[ResourceLoader] No IAssetProvider set, falling back to Resources.Load for '{path}'");
|
||||
}
|
||||
|
||||
StartCoroutine(HandleAsyncLoadCompletion(path, newRequest));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace OCES
|
||||
{
|
||||
public class ResourcesAssetProvider : IAssetProvider
|
||||
{
|
||||
public T Load<T>(string path) where T : UnityEngine.Object
|
||||
{
|
||||
return Resources.Load<T>(path);
|
||||
}
|
||||
public ResourceRequest LoadAsync<T>(string path) where T : UnityEngine.Object
|
||||
{
|
||||
return Resources.LoadAsync<T>(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e5c672cc770b4032bfcd888f36766b45
|
||||
timeCreated: 1778817303
|
||||
@@ -12,7 +12,7 @@ namespace OCES.Audio
|
||||
public class AudioExtendSettings : ScriptableObject
|
||||
{
|
||||
// ========== Resource Paths ==========
|
||||
[Header("Resource Paths")]
|
||||
[Header("Resources")]
|
||||
[Tooltip("Resources 子目录:音频配置文件(.bytes)")]
|
||||
public string audioConfigPath = "AudioData";
|
||||
|
||||
@@ -22,6 +22,9 @@ namespace OCES.Audio
|
||||
[Tooltip("Resources 路径:AudioMixer 资产")]
|
||||
public string audioMixerPath = "Audios/Master";
|
||||
|
||||
public bool useAssetBundle = false;
|
||||
public string audioBundlePath = "Bundles/audios.ab";
|
||||
|
||||
// ========== AudioMixer Group Paths ==========
|
||||
[Header("AudioMixer Groups")]
|
||||
public string sfxGroupPath = "Master/Regular/SFX";
|
||||
|
||||
@@ -289,6 +289,17 @@ namespace OCES.Audio
|
||||
|
||||
this.ResourceLoader = gameObject.AddComponent<ResourceLoader>();
|
||||
|
||||
if (AudioExtendSettings.Instance.useAssetBundle)
|
||||
{
|
||||
string bundlePath = Path.Combine(Application.streamingAssetsPath,
|
||||
AudioExtendSettings.Instance.audioBundlePath);
|
||||
this.ResourceLoader.SetProvider(new AssetBundleAssetProvider(bundlePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ResourceLoader.SetProvider(new ResourcesAssetProvider());
|
||||
}
|
||||
|
||||
string audioConfigPath = AudioExtendSettings.Instance.audioConfigPath;
|
||||
|
||||
this.m_mixer = this.ResourceLoader.LoadSync<AudioMixer>(AudioExtendSettings.Instance.audioMixerPath);
|
||||
@@ -388,8 +399,6 @@ namespace OCES.Audio
|
||||
AudioObject childContainer = this.m_audioObjects.GetMappingResult(switchContainer.Id, currentStateValue);
|
||||
return childContainer ?? this.m_audioObjects.GetDefaultSwitchOrFallback(switchContainer);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static class AudioConfigLoader
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace OCES.Audio.Editor
|
||||
};
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Audio/Create AudioExtendSettings Asset")]
|
||||
[MenuItem("Tools/OCES/Audio/Create AudioExtendSettings Asset")]
|
||||
static void CreateDefaultAsset()
|
||||
{
|
||||
if (!AssetDatabase.IsValidFolder("Assets/Settings"))
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace OCES.Audio
|
||||
get { return Path.Combine(Application.dataPath, "Resources", AudioExtendSettings.Instance.audioConfigPath); }
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Audio/Apply Audio Import Settings")]
|
||||
[MenuItem("Tools/OCES/Audio/Apply Audio Import Settings")]
|
||||
public static void RunFromMenu()
|
||||
{
|
||||
if (EditorUtility.DisplayDialog(
|
||||
|
||||
@@ -47,7 +47,7 @@ namespace OCES.Haptic.Editor
|
||||
};
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Haptic/Create Haptic Settings Asset")]
|
||||
[MenuItem("Tools/OCES/Haptic/Create Haptic Settings Asset")]
|
||||
static void CreateDefaultAsset()
|
||||
{
|
||||
if (!AssetDatabase.IsValidFolder("Assets/Settings"))
|
||||
|
||||
@@ -15,6 +15,9 @@ namespace OCES.Haptic
|
||||
[Tooltip("Resources 子目录:触感资源文件(.haptic)")]
|
||||
public string hapticResourcePath = "Haptics/";
|
||||
|
||||
public bool useAssetBundle = false;
|
||||
public string hapticBundlePath = "Bundles/haptics.ab";
|
||||
|
||||
public static HapticSettings Instance { get; internal set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Lofelt.NiceVibrations;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -117,6 +118,16 @@ namespace OCES.Haptic
|
||||
DontDestroyOnLoad(gameObject);
|
||||
|
||||
this.ResourceLoader = gameObject.AddComponent<ResourceLoader>();
|
||||
if (HapticSettings.Instance.useAssetBundle)
|
||||
{
|
||||
string bundlePath = Path.Combine(Application.streamingAssetsPath,
|
||||
HapticSettings.Instance.hapticBundlePath);
|
||||
this.ResourceLoader.SetProvider(new AssetBundleAssetProvider(bundlePath));
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ResourceLoader.SetProvider(new ResourcesAssetProvider());
|
||||
}
|
||||
|
||||
this.m_hapticObjects = HapticConfigLoader.Load<HapticObjectConfig>(HapticSettings.Instance.hapticConfigPath + "HapticObject");
|
||||
this.IsHapticSupported = DeviceCapabilities.isVersionSupported;
|
||||
|
||||
@@ -15,6 +15,8 @@ MonoBehaviour:
|
||||
audioConfigPath: AudioData
|
||||
audioResourcePath: Audios
|
||||
audioMixerPath: Audios/Master
|
||||
useAssetBundle: 1
|
||||
audioBundlePath: Bundles/audios.ab
|
||||
sfxGroupPath: Master/Regular/SFX
|
||||
voiceGroupPath: Master/Regular/Voice
|
||||
accentSfxGroupPath: Master/SFX_Accent
|
||||
|
||||
@@ -14,3 +14,5 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier:
|
||||
hapticConfigPath: HapticData/
|
||||
hapticResourcePath: Haptics/
|
||||
useAssetBundle: 0
|
||||
hapticBundlePath: Bundles/haptics.ab
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=lofelt/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
<s:Boolean x:Key="/Default/UserDictionary/Words/=Lofelt/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
Reference in New Issue
Block a user