com.amanotes.gdk 0.2.77 → 0.2.79
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/Editor/Extra/SDKVersionTracking.cs +29 -0
- package/Extra/AmaGDKInstaller.unitypackage +0 -0
- package/Extra/AutoEventQC.unitypackage +0 -0
- package/Extra/CheckDiskSpace.unitypackage +0 -0
- package/Extra/Consent.unitypackage +0 -0
- package/Extra/ForceUpdate.unitypackage +0 -0
- package/Extra/LegacyGDKUpdateHelper.unitypackage +0 -0
- package/Extra/PostProcessor.unitypackage +0 -0
- package/Packages/AmaGDKConfig.unitypackage +0 -0
- package/Packages/AmaGDKExample.unitypackage +0 -0
- package/Packages/AmaGDKTest.unitypackage +0 -0
- package/Packages/AppsFlyerAdapter.PurchaseConnector.unitypackage +0 -0
- package/Packages/AppsFlyerAdapter.unitypackage +0 -0
- package/Packages/FirebaseAnalyticsAdapter.unitypackage +0 -0
- package/Packages/FirebaseRemoteConfigAdapter.unitypackage +0 -0
- package/Packages/IronSourceAdapter.AdQuality.unitypackage +0 -0
- package/Packages/IronSourceAdapter.unitypackage +0 -0
- package/Packages/MaxAdNetworkAdapter.unitypackage +0 -0
- package/Packages/RevenueCatAdapter.unitypackage +0 -0
- package/Packages/SqliteAnalyticsAdapter.unitypackage +0 -0
- package/Runtime/AmaGDK.Adapters.cs +2 -2
- package/Runtime/AmaGDK.IAP.cs +87 -3
- package/Runtime/AmaGDK.cs +66 -3
- package/Runtime/Internal/AmaGDK.Utils.cs +38 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.2.79] - 2024-11-13
|
|
4
|
+
- [Dev] Add callbacks for IAP module
|
|
5
|
+
- [Fix] Return empty collections instead of null in RevenueCatAdapter
|
|
6
|
+
- [GameOps] Remove durationInSecs
|
|
7
|
+
|
|
8
|
+
## [0.2.78] - 2024-11-11
|
|
9
|
+
- [Dev] Add iOS and Android ad unit configuration for MAX
|
|
10
|
+
- [Dev] Track Adapters and Third-party packages used in games
|
|
11
|
+
|
|
3
12
|
## [0.2.77] - 2024-11-07
|
|
4
13
|
- [Release] 0.2.76
|
|
5
14
|
- [Dev] Remove GDKAudio
|
|
@@ -12,6 +12,8 @@ using UnityEditor.Build.Reporting;
|
|
|
12
12
|
using UnityEngine;
|
|
13
13
|
using System.Threading.Tasks;
|
|
14
14
|
using UnityEditor.Callbacks;
|
|
15
|
+
using System.Reflection;
|
|
16
|
+
|
|
15
17
|
|
|
16
18
|
#if UNITY_IOS
|
|
17
19
|
using UnityEditor.iOS.Xcode;
|
|
@@ -43,6 +45,7 @@ namespace Amanotes.Editor
|
|
|
43
45
|
|
|
44
46
|
private const int MAX_VERSION_KEY_LENGTH = 30;
|
|
45
47
|
private static Dictionary<string, object> _dicThirdPartyVersion = new Dictionary<string, object>();
|
|
48
|
+
private static Dictionary<string, object> _dicImportedGDKAdapter = new Dictionary<string, object>();
|
|
46
49
|
private static Dictionary<string, object> _dicExtraVersion = new Dictionary<string, object>();
|
|
47
50
|
private static Dictionary<string, object> _dicIronSourceAdapterVersion = new Dictionary<string, object>();
|
|
48
51
|
|
|
@@ -192,6 +195,29 @@ namespace Amanotes.Editor
|
|
|
192
195
|
};
|
|
193
196
|
}
|
|
194
197
|
|
|
198
|
+
public static Dictionary<string, object> GetImportedGDKAdapter()
|
|
199
|
+
{
|
|
200
|
+
var dicGDKAdapters = new Dictionary<string, object>();
|
|
201
|
+
var config = Resources.Load<ConfigAsset>("AmaGDKConfig");
|
|
202
|
+
foreach (var field in config
|
|
203
|
+
.GetType()
|
|
204
|
+
.GetFields(BindingFlags.Instance | BindingFlags.NonPublic))
|
|
205
|
+
{
|
|
206
|
+
var fieldValue = field.GetValue(config);
|
|
207
|
+
if (fieldValue is Adapter2 adapter)
|
|
208
|
+
{
|
|
209
|
+
string gdkAdapterName = field.Name + "Adapter";
|
|
210
|
+
dicGDKAdapters[gdkAdapterName] = adapter.enabled? "Enabled" : "Disabled";
|
|
211
|
+
}
|
|
212
|
+
else
|
|
213
|
+
{
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return dicGDKAdapters;
|
|
219
|
+
}
|
|
220
|
+
|
|
195
221
|
private static void WriteToStreamingAssets()
|
|
196
222
|
{
|
|
197
223
|
try
|
|
@@ -201,6 +227,7 @@ namespace Amanotes.Editor
|
|
|
201
227
|
|
|
202
228
|
AppendVersion(sb, "BUILD INFO", GetBuildInfo());
|
|
203
229
|
AppendVersion(sb, "THIRD PARTY", _dicThirdPartyVersion);
|
|
230
|
+
AppendVersion(sb, "IMPORTED GDK ADAPTERS", _dicImportedGDKAdapter);
|
|
204
231
|
AppendVersion(sb, "IS ADAPTER", _dicIronSourceAdapterVersion);
|
|
205
232
|
AppendVersion(sb, "EXTRA", _dicExtraVersion);
|
|
206
233
|
|
|
@@ -226,6 +253,7 @@ namespace Amanotes.Editor
|
|
|
226
253
|
public void OnPreprocessBuild(BuildReport report)
|
|
227
254
|
{
|
|
228
255
|
_dicThirdPartyVersion = AmaGDKExtra.GetThirdPartyVersions();
|
|
256
|
+
_dicImportedGDKAdapter = GetImportedGDKAdapter();
|
|
229
257
|
_dicIronSourceAdapterVersion = AmaGDKExtra.GetIronSourceAdapterVersions();
|
|
230
258
|
_dicExtraVersion = _getExtraSDKVersion?.Invoke() ?? _dicExtraVersion;
|
|
231
259
|
|
|
@@ -242,6 +270,7 @@ namespace Amanotes.Editor
|
|
|
242
270
|
public static void OnPostProcessBuild(BuildTarget target, string pathToBuiltProject)
|
|
243
271
|
{
|
|
244
272
|
var rawData = _dicThirdPartyVersion
|
|
273
|
+
.Concat(_dicImportedGDKAdapter)
|
|
245
274
|
.Concat(_dicExtraVersion)
|
|
246
275
|
.Concat(_dicIronSourceAdapterVersion.ToDictionary(kvp => kvp.Key, kvp => (object)("IS_" + kvp.Value)))
|
|
247
276
|
.ToDictionary(x => x.Key, x => x.Value);
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -24,7 +24,7 @@ namespace Amanotes.Core.Internal
|
|
|
24
24
|
[HideInNormalInspector] [SerializeField] internal int initOrder;
|
|
25
25
|
[HideInNormalInspector] [SerializeField] internal bool enabled = true;
|
|
26
26
|
[NonSerialized] internal string adapterId;
|
|
27
|
-
[NonSerialized] internal string
|
|
27
|
+
[NonSerialized] internal string compatibleSDKVersion;
|
|
28
28
|
[NonSerialized] protected internal Status status = Status.None;
|
|
29
29
|
|
|
30
30
|
protected abstract void Init(Action<bool> onComplete);
|
|
@@ -75,7 +75,7 @@ namespace Amanotes.Core.Internal
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
adapter.adapterId = id;
|
|
78
|
-
adapter.
|
|
78
|
+
adapter.compatibleSDKVersion = version;
|
|
79
79
|
GDKUtils.AddUnityCallbacks(adapter);
|
|
80
80
|
adapterMap.Add(id, adapter);
|
|
81
81
|
return true;
|
package/Runtime/AmaGDK.IAP.cs
CHANGED
|
@@ -23,6 +23,58 @@ namespace Amanotes.Core
|
|
|
23
23
|
public static readonly Dictionary<string, ProductInfo> AllProducts = new Dictionary<string, ProductInfo>();
|
|
24
24
|
private const string DATE_FORMAT = "yyyy/MM/yy HH:mm:ss";
|
|
25
25
|
|
|
26
|
+
public static void SetCallback_OnModuleReady(Action onReady)
|
|
27
|
+
{
|
|
28
|
+
if (onReady == null) return;
|
|
29
|
+
|
|
30
|
+
if (IsModuleReady)
|
|
31
|
+
{
|
|
32
|
+
onReady();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
_onModuleReady -= onReady;
|
|
37
|
+
_onModuleReady += onReady;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public static void SetCallback_OnGetProductsComplete(Action onComplete)
|
|
41
|
+
{
|
|
42
|
+
if (onComplete == null) return;
|
|
43
|
+
|
|
44
|
+
if (isGetProductComplete)
|
|
45
|
+
{
|
|
46
|
+
onComplete();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
_onGetProductsComplete -= onComplete;
|
|
51
|
+
_onGetProductsComplete += onComplete;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
public static void SetCallback_OnGetCustomerInfoComplete(Action onComplete)
|
|
55
|
+
{
|
|
56
|
+
if (onComplete == null) return;
|
|
57
|
+
|
|
58
|
+
if (isGetCustomerInfoComplete)
|
|
59
|
+
{
|
|
60
|
+
onComplete();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_onGetCustomerInfoComplete -= onComplete;
|
|
65
|
+
_onGetCustomerInfoComplete += onComplete;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public static void SyncCustomerInfo(Action<bool> onComplete = null)
|
|
69
|
+
{
|
|
70
|
+
if (_adapter == null)
|
|
71
|
+
{
|
|
72
|
+
onComplete?.Invoke(false);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
_adapter.SyncCustomerInfo(onComplete);
|
|
76
|
+
}
|
|
77
|
+
|
|
26
78
|
public static ISetupBuilder GetProducts(bool appendExistingList = false)
|
|
27
79
|
{
|
|
28
80
|
if (!appendExistingList)
|
|
@@ -129,6 +181,13 @@ namespace Amanotes.Core
|
|
|
129
181
|
}
|
|
130
182
|
}
|
|
131
183
|
|
|
184
|
+
private static bool isGetProductComplete;
|
|
185
|
+
private static bool isGetCustomerInfoComplete;
|
|
186
|
+
private static bool IsModuleReady => isGetProductComplete && isGetCustomerInfoComplete;
|
|
187
|
+
private static Action _onGetProductsComplete;
|
|
188
|
+
private static Action _onGetCustomerInfoComplete;
|
|
189
|
+
private static Action _onModuleReady;
|
|
190
|
+
|
|
132
191
|
internal static bool InitModule()
|
|
133
192
|
{
|
|
134
193
|
_adapter = Adapter2.GetAdapter<IAPAdapter>();
|
|
@@ -140,8 +199,31 @@ namespace Amanotes.Core
|
|
|
140
199
|
internal static void StartModule()
|
|
141
200
|
{
|
|
142
201
|
if (_adapter == null) return;
|
|
202
|
+
_adapter.SyncCustomerInfo(
|
|
203
|
+
success =>
|
|
204
|
+
{
|
|
205
|
+
isGetCustomerInfoComplete = true;
|
|
206
|
+
GDKUtils.SafeInvokeAndClear(ref _onGetCustomerInfoComplete);
|
|
207
|
+
if (IsModuleReady) GDKUtils.SafeInvokeAndClear(ref _onModuleReady);
|
|
208
|
+
});
|
|
143
209
|
localData.Sync();
|
|
144
|
-
if (Config.iap.autoGetProducts)
|
|
210
|
+
if (Config.iap.autoGetProducts)
|
|
211
|
+
{
|
|
212
|
+
GetProducts()
|
|
213
|
+
.OnSuccess(_ => OnGetProductsComplete())
|
|
214
|
+
.OnFail(_ => OnGetProductsComplete());
|
|
215
|
+
}
|
|
216
|
+
else
|
|
217
|
+
{
|
|
218
|
+
OnGetProductsComplete();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
private static void OnGetProductsComplete()
|
|
223
|
+
{
|
|
224
|
+
isGetProductComplete = true;
|
|
225
|
+
GDKUtils.SafeInvokeAndClear(ref _onGetProductsComplete);
|
|
226
|
+
if (IsModuleReady) GDKUtils.SafeInvokeAndClear(ref _onModuleReady);
|
|
145
227
|
}
|
|
146
228
|
}
|
|
147
229
|
|
|
@@ -442,6 +524,7 @@ namespace Amanotes.Core.Internal
|
|
|
442
524
|
if (ls == null || ls.Count == 0)
|
|
443
525
|
{
|
|
444
526
|
LogWarning("There is no IAP product");
|
|
527
|
+
onSuccess?.Invoke(AllProducts);
|
|
445
528
|
return;
|
|
446
529
|
}
|
|
447
530
|
|
|
@@ -506,8 +589,7 @@ namespace Amanotes.Core.Internal
|
|
|
506
589
|
{
|
|
507
590
|
foreach (var p in pList)
|
|
508
591
|
{
|
|
509
|
-
|
|
510
|
-
else AllProducts.Add(p.id, p);
|
|
592
|
+
AllProducts[p.id] = p;
|
|
511
593
|
}
|
|
512
594
|
GDKUtils.DelayCall(0f, GetNextProductsInQueue);
|
|
513
595
|
}, error => onFail?.Invoke(error));
|
|
@@ -573,5 +655,7 @@ namespace Amanotes.Core.Internal
|
|
|
573
655
|
public abstract DateTime? GetUnsubscribedDate();
|
|
574
656
|
public abstract HashSet<PurchaseRecord> GetPurchaseHistory();
|
|
575
657
|
public abstract DateTime? GetLastPurchaseDate(string productAndPlanId);
|
|
658
|
+
|
|
659
|
+
public abstract void SyncCustomerInfo(Action<bool> onComplete = null);
|
|
576
660
|
}
|
|
577
661
|
}
|
package/Runtime/AmaGDK.cs
CHANGED
|
@@ -14,17 +14,20 @@ using Debug = UnityEngine.Debug;
|
|
|
14
14
|
using Amanotes.Core.Internal;
|
|
15
15
|
using static Amanotes.Core.GDKDebug;
|
|
16
16
|
using static Amanotes.Core.Internal.Messsage;
|
|
17
|
+
using System.Threading.Tasks;
|
|
18
|
+
|
|
17
19
|
|
|
18
20
|
#if UNITY_EDITOR
|
|
19
21
|
using UnityEditor;
|
|
20
22
|
using UnityEditor.PackageManager;
|
|
21
23
|
#endif
|
|
22
24
|
|
|
25
|
+
|
|
23
26
|
namespace Amanotes.Core
|
|
24
27
|
{
|
|
25
28
|
public partial class AmaGDK : MonoBehaviour
|
|
26
29
|
{
|
|
27
|
-
public const string VERSION = "0.2.
|
|
30
|
+
public const string VERSION = "0.2.79";
|
|
28
31
|
|
|
29
32
|
internal static Status _status = Status.None;
|
|
30
33
|
private static ConfigAsset _config = null;
|
|
@@ -46,6 +49,7 @@ namespace Amanotes.Core
|
|
|
46
49
|
{
|
|
47
50
|
// public const string GDK_HOOK_START = nameof(GDK_HOOK_START);
|
|
48
51
|
// public const string GDK_HOOK_END = nameof(GDK_HOOK_END);
|
|
52
|
+
|
|
49
53
|
// public const string GDK_INIT = nameof(GDK_INIT);
|
|
50
54
|
// public const string GDK_READY = nameof(GDK_READY);
|
|
51
55
|
// public const string ADAPTER_READY = nameof(ADAPTER_READY);
|
|
@@ -233,7 +237,9 @@ namespace Amanotes.Core
|
|
|
233
237
|
GDKUtils.SafeInvokeAndClear(ref _onGDKReady);
|
|
234
238
|
|
|
235
239
|
float initDuration = Time.realtimeSinceStartup - startTime;
|
|
236
|
-
|
|
240
|
+
|
|
241
|
+
yield return LogEvent_GDKInitAsync(Mathf.RoundToInt(initDuration));
|
|
242
|
+
|
|
237
243
|
|
|
238
244
|
sb.Append($"\nMapping UserId: {User.UserId}\n");
|
|
239
245
|
ForceLog($"v{VERSION} | {READY} in {initDuration:#0.00}s\n\n{sb}");
|
|
@@ -263,7 +269,60 @@ namespace Amanotes.Core
|
|
|
263
269
|
}
|
|
264
270
|
}
|
|
265
271
|
|
|
266
|
-
|
|
272
|
+
public static async Task<Dictionary<string, object>> ParseThirdPartyVersionsAsync()
|
|
273
|
+
{
|
|
274
|
+
Dictionary<string, object> thirdPartyVersions = new Dictionary<string, object>();
|
|
275
|
+
string fileContent = await GDKFileUtils.LoadFromStreamingAssets("SDKVersion.txt");
|
|
276
|
+
if (string.IsNullOrEmpty(fileContent))
|
|
277
|
+
{
|
|
278
|
+
LogWarning($"Failed to load SDKVersion.txt.");
|
|
279
|
+
}
|
|
280
|
+
else
|
|
281
|
+
{
|
|
282
|
+
ProcessFileContent(fileContent, thirdPartyVersions);
|
|
283
|
+
}
|
|
284
|
+
return thirdPartyVersions;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
private static void ProcessFileContent(string fileContent, Dictionary<string, object> thirdPartyVersions)
|
|
288
|
+
{
|
|
289
|
+
bool foundThirdPartySection = false;
|
|
290
|
+
using (StringReader sr = new StringReader(fileContent))
|
|
291
|
+
{
|
|
292
|
+
string line;
|
|
293
|
+
while ((line = sr.ReadLine()) != null)
|
|
294
|
+
{
|
|
295
|
+
if (line.Contains("// THIRD PARTY"))
|
|
296
|
+
{
|
|
297
|
+
foundThirdPartySection = true;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (line.Contains("//-"))
|
|
302
|
+
continue;
|
|
303
|
+
|
|
304
|
+
if (foundThirdPartySection)
|
|
305
|
+
{
|
|
306
|
+
if (string.IsNullOrWhiteSpace(line) || line.Contains("//"))
|
|
307
|
+
break;
|
|
308
|
+
|
|
309
|
+
string[] parts = line.Split(new[] { '=' }, 2, StringSplitOptions.RemoveEmptyEntries);
|
|
310
|
+
if (parts.Length == 2)
|
|
311
|
+
{
|
|
312
|
+
string key = parts[0].Trim(); // Extract the key (name of the third-party library)
|
|
313
|
+
string value = parts[1].Trim(); // Extract the version number
|
|
314
|
+
thirdPartyVersions[key] = value;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
if (!foundThirdPartySection)
|
|
320
|
+
{
|
|
321
|
+
LogWarning("Failed to fetch third-party packages' version from SDKVersions.txt.");
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private static async Task LogEvent_GDKInitAsync(int initDuration)
|
|
267
326
|
{
|
|
268
327
|
var BToMB = 1.0 / (1024 * 1024);
|
|
269
328
|
var reservedMemory = Math.Round(Profiler.GetTotalReservedMemoryLong() * BToMB, 2); // in MB
|
|
@@ -292,6 +351,10 @@ namespace Amanotes.Core
|
|
|
292
351
|
AppendServiceKey(AdapterID.APPSFLYER, "appsflyer_key");
|
|
293
352
|
AppendServiceKey(AdapterID.IRONSOURCE, "ironsource_key");
|
|
294
353
|
AppendServiceKey(AdapterID.REVENUECAT, "revenuecat_key");
|
|
354
|
+
|
|
355
|
+
var thirdPartyVersions = await ParseThirdPartyVersionsAsync();
|
|
356
|
+
_dicKey = _dicKey.Concat(thirdPartyVersions).ToDictionary(x=>x.Key, x=>x.Value);
|
|
357
|
+
|
|
295
358
|
Analytics.LogEvent("gdk_init", _dicKey);
|
|
296
359
|
}
|
|
297
360
|
}
|
|
@@ -10,6 +10,9 @@ using UnityEngine;
|
|
|
10
10
|
using UnityEngine.Profiling;
|
|
11
11
|
using static Amanotes.Core.GDKDebug;
|
|
12
12
|
|
|
13
|
+
using System.Threading.Tasks;
|
|
14
|
+
using UnityEngine.Networking;
|
|
15
|
+
|
|
13
16
|
namespace Amanotes.Core.Internal
|
|
14
17
|
{
|
|
15
18
|
public static partial class GDKUtils
|
|
@@ -251,6 +254,41 @@ namespace Amanotes.Core.Internal
|
|
|
251
254
|
return content;
|
|
252
255
|
}
|
|
253
256
|
|
|
257
|
+
public static async Task<string> LoadFromStreamingAssets(string fileName)
|
|
258
|
+
{
|
|
259
|
+
string filePath = Path.Combine(Application.streamingAssetsPath, fileName);
|
|
260
|
+
|
|
261
|
+
// Android
|
|
262
|
+
if (Application.platform == RuntimePlatform.Android)
|
|
263
|
+
{
|
|
264
|
+
using (UnityWebRequest request = UnityWebRequest.Get(filePath))
|
|
265
|
+
{
|
|
266
|
+
var operation = request.SendWebRequest();
|
|
267
|
+
while (!operation.isDone)
|
|
268
|
+
{
|
|
269
|
+
await Task.Yield();
|
|
270
|
+
}
|
|
271
|
+
if (request.result != UnityWebRequest.Result.Success)
|
|
272
|
+
{
|
|
273
|
+
LogWarning($"Error reading file: {request.error}");
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
return request.downloadHandler.text;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Other
|
|
281
|
+
try
|
|
282
|
+
{
|
|
283
|
+
return File.ReadAllText(filePath);
|
|
284
|
+
}
|
|
285
|
+
catch (Exception e)
|
|
286
|
+
{
|
|
287
|
+
LogWarning($"Error reading file: {e.Message}");
|
|
288
|
+
}
|
|
289
|
+
return null;
|
|
290
|
+
}
|
|
291
|
+
|
|
254
292
|
internal static void Delete(string fileName)
|
|
255
293
|
{
|
|
256
294
|
string path = GetPath(fileName);
|