init commit
commit
a0532a5ced
|
|
@ -0,0 +1,2 @@
|
||||||
|
# Mac auto-generated system files
|
||||||
|
*.DS_Store*
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Guru Template Change Log
|
||||||
|
|
||||||
|
## 2.0.3
|
||||||
|
|
||||||
|
- Firebase
|
||||||
|
- 整体升级到 10.1.1. 作为Unity组统一标准
|
||||||
|
- Max 广告聚合
|
||||||
|
- 升级Max聚合的所有渠道, 对齐[中台需求](https://docs.google.com/spreadsheets/d/161UnDimGerqetIYNiMCfUBmJ7qozht8z1baxnxRdCnI/edit#gid=311231520)
|
||||||
|
- GuruBuildTools
|
||||||
|
- 新增 Deps Reporter, 用于在Jenkins环境, 上报打包依赖 (Android/iOS). 理论上报告工具不影响本地打包, 不影响打包机打包. 出问题的话也不会中断打包流程.
|
||||||
|
- 修复 IOS 打包成功后, 上传TF时报Info.plist内没有设置版本号的报错
|
||||||
|
- GuruAds
|
||||||
|
- 根据中台的需求, 新增广告属性打点: `bads_imp`, `bads_loaded`
|
||||||
|
- 去掉使用三方广告SDK, 需要预先设置 AD_AMAZON 和 AD_PUBMATIC 的流程. 目前默认全部接入
|
||||||
|
- GuruAnalytics
|
||||||
|
- 更新和添加 Worker 支持, 预期可让用户在线时长更加精准
|
||||||
|
- DeviceUtil
|
||||||
|
- 更新Android端获取系统版本号的的接口, 方便程序判断 Android API 33 以及后继的响应
|
||||||
|
|
||||||
|
|
||||||
|
## 2.0.2
|
||||||
|
|
||||||
|
各模块对应的内容更新
|
||||||
|
|
||||||
|
- FBService
|
||||||
|
- 添加IOS平台打点开启语句 `FB.Mobile.SetAdvertiserTrackingEnabled(true);`
|
||||||
|
- AdjustService
|
||||||
|
- 自动创建生命周期对象, 优化部分逻辑
|
||||||
|
- Firebase
|
||||||
|
- 修复 Firebase(8.1.0) 无法生成 google-service.json 的问题. 替换了Firebase.Editor.dll
|
||||||
|
- Guru L10n 更新
|
||||||
|
- 更新GuruL10N -> 0.4.0 新增单独翻译google sheet的设置
|
||||||
|
- 更新插件 backup.csv 缓存路径, 请各个项目将 **`backup.csv`** 提交到代码内进行跟踪!
|
||||||
|
- 更新了语言编码去重识别, 根据Alpha表重排的功能
|
||||||
|
- Guru Analytics
|
||||||
|
- 广告打点中加入了 Waterfall 信息上报
|
||||||
|
- 添加了用户属性缓存表
|
||||||
|
- Guru Build Tools
|
||||||
|
- 新增 DebugView 联调参数注入, 需要项目安装验证
|
||||||
|
- 更新打包管线相关逻辑, IOS Target Version 升级为 13.0
|
||||||
|
- RemoteConfig
|
||||||
|
- 修复了AROConfig的初始化逻辑, 避免初始化时报空引发报错
|
||||||
|
- IAPService
|
||||||
|
- 添加了本地的订单数据缓存, 防止特殊操作的时候, iOS的订单重复上报的问题.
|
||||||
|
|
||||||
|
|
||||||
|
## 2.0.1
|
||||||
|
- 更新自打点库原生依赖
|
||||||
|
- Android 原生库版本 (0.2.10)
|
||||||
|
- IOS 原生库版本 (0.2.16)
|
||||||
|
- 更新 L10n 版本 (3.2.0)
|
||||||
|
- 更新了导入表格会把code行也导入进来的BUG
|
||||||
|
- 更新配置多语言时 nb-NO 语言的识别功能
|
||||||
|
|
||||||
|
|
||||||
|
## 1.6.0
|
||||||
|
- 添加了 Amazon (1.0.0) 广告扩展,使用说明详见: [GuruAds 扩展 Amazon安装说明](GuruAds/README.md)
|
||||||
|
- 添加了 GuruAnalytics (1.0.3) 广告自打点插件,使用说明详见: [Guru Unity Analytics 自打点插件](GuruAnalytics/README.md)
|
||||||
|
- 添加了 StandardProperties 标准属性点类, 用于记录游戏内的标准事件和属性
|
||||||
|
- 添加了 GuruBuildTool 模块, 收集和整理了目前为止所有 BuildTool 相关的逻辑
|
||||||
|
- 扩展了GuruSettings的配置格式(Amazon广告配置相关)
|
||||||
|
- 在 AdjustService 内添加获取 AdjustId 以及 FirebaseID 的逻辑
|
||||||
|
- 在 AdjustService 内添加上报 DeviceID 的逻辑
|
||||||
|
- 修复了若干插件显示层以及构建管线的BUG
|
||||||
|
- 调整了框架整体的文件结构
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6acd89dd2c454676834609a8981c136a
|
||||||
|
TextScriptImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e3ff74f6837b4366b71c8bfe322a4562
|
||||||
|
timeCreated: 1671010724
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c2bda7db652148e7a73fd2179e22be09
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 347116c7f46946f5945ca097a6e964db
|
||||||
|
timeCreated: 1687241187
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
#if UNITY_ANDROID
|
||||||
|
namespace Guru
|
||||||
|
{
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using UnityEditor;
|
||||||
|
using UnityEditor.Build;
|
||||||
|
using UnityEditor.Build.Reporting;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Android混淆器内容填充
|
||||||
|
/// 于应用构建前执行
|
||||||
|
/// </summary>
|
||||||
|
public class UserProguardHelper: IPreprocessBuildWithReport
|
||||||
|
{
|
||||||
|
public int callbackOrder { get; } = 0;
|
||||||
|
public void OnPreprocessBuild(BuildReport report)
|
||||||
|
{
|
||||||
|
// 修复ProguardFile
|
||||||
|
OnApplyProguardFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnApplyProguardFiles()
|
||||||
|
{
|
||||||
|
string proguardPath = $"{Application.dataPath}/Plugins/Android/proguard-user.txt";
|
||||||
|
if (File.Exists(proguardPath))
|
||||||
|
{
|
||||||
|
List<string> keeps = new List<string>();
|
||||||
|
DirectoryInfo dir = new DirectoryInfo(Application.dataPath);
|
||||||
|
string raw = File.ReadAllText(proguardPath);
|
||||||
|
|
||||||
|
if (dir.Exists)
|
||||||
|
{
|
||||||
|
var editors = dir.GetDirectories("Editor", SearchOption.AllDirectories);
|
||||||
|
List<FileInfo> files = new List<FileInfo>();
|
||||||
|
foreach (var e in editors)
|
||||||
|
{
|
||||||
|
files.AddRange(e.GetFiles("*Proguards.txt", SearchOption.AllDirectories));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug.Log($"--- Proguard Files: {files.Length}");
|
||||||
|
|
||||||
|
string[] lens = null;
|
||||||
|
string l = "";
|
||||||
|
for (int i = 0; i < files.Count; i++)
|
||||||
|
{
|
||||||
|
lens = File.ReadAllLines(files[i].FullName);
|
||||||
|
foreach (var s in lens)
|
||||||
|
{
|
||||||
|
l = s.TrimStart();
|
||||||
|
if(string.IsNullOrEmpty(l)) continue;
|
||||||
|
if(raw.Contains(l)) continue;
|
||||||
|
keeps.Add(l);
|
||||||
|
Debug.Log($"--- ✏️ Apply: [ {l} ]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (keeps.Count == 0) return; // 无注入文件则退出
|
||||||
|
|
||||||
|
List<string> lines = File.ReadAllLines(proguardPath).ToList();
|
||||||
|
lines.Add("");
|
||||||
|
lines.AddRange(keeps);
|
||||||
|
|
||||||
|
File.WriteAllLines(proguardPath, lines.ToArray());
|
||||||
|
Debug.Log($"--- Update proguard-user.txt done! ☀️ ---");
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
[MenuItem("Tools/Android/Add proguard-user")]
|
||||||
|
private static void EditorAddProguardUser()
|
||||||
|
{
|
||||||
|
OnApplyProguardFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 548800163ca249de8edcfa039836449d
|
||||||
|
timeCreated: 1687241187
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c4d05b56a8b854c2c93c1db55c5bc13b
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
%YAML 1.1
|
||||||
|
%TAG !u! tag:unity3d.com,2011:
|
||||||
|
--- !u!114 &11400000
|
||||||
|
MonoBehaviour:
|
||||||
|
m_ObjectHideFlags: 0
|
||||||
|
m_CorrespondingSourceObject: {fileID: 0}
|
||||||
|
m_PrefabInstance: {fileID: 0}
|
||||||
|
m_PrefabAsset: {fileID: 0}
|
||||||
|
m_GameObject: {fileID: 0}
|
||||||
|
m_Enabled: 1
|
||||||
|
m_EditorHideFlags: 0
|
||||||
|
m_Script: {fileID: 11500000, guid: a403a09e241a0480a957591ea60fb785, type: 3}
|
||||||
|
m_Name: settings
|
||||||
|
m_EditorClassIdentifier:
|
||||||
|
usesCleartextTraffic: 0
|
||||||
|
writeExternalStorage: 0
|
||||||
|
accessFineLocation: 0
|
||||||
|
addsKotlin: 0
|
||||||
|
addsAndroidBrowser: 0
|
||||||
|
enableJetifier: 1
|
||||||
|
authCallbackUrls: []
|
||||||
|
supportLINELogin: 0
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0e00e1700888048628304b0c69a1c61e
|
||||||
|
NativeFormatImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
mainObjectFileID: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a99ba0cf57ee14adbb2be18231643025
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a2b9d9fa87e634e99b4932bcaaec2f40
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0c5075eff6f44aad872f5c8160b94132
|
||||||
|
timeCreated: 1703228621
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"name": "Guru.LitJson",
|
||||||
|
"rootNamespace": "Guru.LitJson",
|
||||||
|
"references": [],
|
||||||
|
"includePlatforms": [],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": false,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"precompiledReferences": [],
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [],
|
||||||
|
"noEngineReferences": false
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 2bd307385b74f48c2bdf31bd24d78056
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 63a6db46b50de451a9008227af468125
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
|
||||||
|
[assembly: CLSCompliant (true)]
|
||||||
|
|
||||||
|
[assembly: AssemblyTitle ("LitJson")]
|
||||||
|
[assembly: AssemblyDescription ("LitJSON library")]
|
||||||
|
[assembly: AssemblyConfiguration ("")]
|
||||||
|
[assembly: AssemblyCompany ("")]
|
||||||
|
[assembly: AssemblyProduct ("LitJSON")]
|
||||||
|
[assembly: AssemblyCopyright (
|
||||||
|
"The authors disclaim copyright to this source code")]
|
||||||
|
[assembly: AssemblyTrademark ("")]
|
||||||
|
[assembly: AssemblyCulture ("")]
|
||||||
|
|
||||||
|
[assembly: AssemblyVersion ("@ASSEMBLY_VERSION@")]
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b7cf5dc50b2d64dca9830e9144c06b2c
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
#region Header
|
||||||
|
/**
|
||||||
|
* IJsonWrapper.cs
|
||||||
|
* Interface that represents a type capable of handling all kinds of JSON
|
||||||
|
* data. This is mainly used when mapping objects through JsonMapper, and
|
||||||
|
* it's implemented by JsonData.
|
||||||
|
*
|
||||||
|
* The authors disclaim copyright to this source code. For more details, see
|
||||||
|
* the COPYING file included with this distribution.
|
||||||
|
**/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Guru.LitJson
|
||||||
|
{
|
||||||
|
public enum JsonType
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
|
||||||
|
Object,
|
||||||
|
Array,
|
||||||
|
String,
|
||||||
|
Int,
|
||||||
|
Long,
|
||||||
|
Double,
|
||||||
|
Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface IJsonWrapper : IList, IOrderedDictionary
|
||||||
|
{
|
||||||
|
bool IsArray { get; }
|
||||||
|
bool IsBoolean { get; }
|
||||||
|
bool IsDouble { get; }
|
||||||
|
bool IsInt { get; }
|
||||||
|
bool IsLong { get; }
|
||||||
|
bool IsObject { get; }
|
||||||
|
bool IsString { get; }
|
||||||
|
|
||||||
|
bool GetBoolean ();
|
||||||
|
double GetDouble ();
|
||||||
|
int GetInt ();
|
||||||
|
JsonType GetJsonType ();
|
||||||
|
long GetLong ();
|
||||||
|
string GetString ();
|
||||||
|
|
||||||
|
void SetBoolean (bool val);
|
||||||
|
void SetDouble (double val);
|
||||||
|
void SetInt (int val);
|
||||||
|
void SetJsonType (JsonType type);
|
||||||
|
void SetLong (long val);
|
||||||
|
void SetString (string val);
|
||||||
|
|
||||||
|
string ToJson ();
|
||||||
|
void ToJson (JsonWriter writer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 394f32e20d93e474487e7f8a07415298
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 5559251be0dd54c669a5b225ad53126e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
#region Header
|
||||||
|
/**
|
||||||
|
* JsonException.cs
|
||||||
|
* Base class throwed by LitJSON when a parsing error occurs.
|
||||||
|
*
|
||||||
|
* The authors disclaim copyright to this source code. For more details, see
|
||||||
|
* the COPYING file included with this distribution.
|
||||||
|
**/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Guru.LitJson
|
||||||
|
{
|
||||||
|
public class JsonException :
|
||||||
|
#if NETSTANDARD1_5
|
||||||
|
Exception
|
||||||
|
#else
|
||||||
|
ApplicationException
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
public JsonException () : base ()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal JsonException (ParserToken token) :
|
||||||
|
base (String.Format (
|
||||||
|
"Invalid token '{0}' in input string", token))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal JsonException (ParserToken token,
|
||||||
|
Exception inner_exception) :
|
||||||
|
base (String.Format (
|
||||||
|
"Invalid token '{0}' in input string", token),
|
||||||
|
inner_exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal JsonException (int c) :
|
||||||
|
base (String.Format (
|
||||||
|
"Invalid character '{0}' in input string", (char) c))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal JsonException (int c, Exception inner_exception) :
|
||||||
|
base (String.Format (
|
||||||
|
"Invalid character '{0}' in input string", (char) c),
|
||||||
|
inner_exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public JsonException (string message) : base (message)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonException (string message, Exception inner_exception) :
|
||||||
|
base (message, inner_exception)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 35e97fa5a8f99447a90bffb890cfd55d
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,995 @@
|
||||||
|
#region Header
|
||||||
|
/**
|
||||||
|
* JsonMapper.cs
|
||||||
|
* JSON to .Net object and object to JSON conversions.
|
||||||
|
*
|
||||||
|
* The authors disclaim copyright to this source code. For more details, see
|
||||||
|
* the COPYING file included with this distribution.
|
||||||
|
**/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Reflection;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Guru.LitJson
|
||||||
|
{
|
||||||
|
internal struct PropertyMetadata
|
||||||
|
{
|
||||||
|
public MemberInfo Info;
|
||||||
|
public bool IsField;
|
||||||
|
public Type Type;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal struct ArrayMetadata
|
||||||
|
{
|
||||||
|
private Type element_type;
|
||||||
|
private bool is_array;
|
||||||
|
private bool is_list;
|
||||||
|
|
||||||
|
|
||||||
|
public Type ElementType {
|
||||||
|
get {
|
||||||
|
if (element_type == null)
|
||||||
|
return typeof (JsonData);
|
||||||
|
|
||||||
|
return element_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
set { element_type = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsArray {
|
||||||
|
get { return is_array; }
|
||||||
|
set { is_array = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsList {
|
||||||
|
get { return is_list; }
|
||||||
|
set { is_list = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal struct ObjectMetadata
|
||||||
|
{
|
||||||
|
private Type element_type;
|
||||||
|
private bool is_dictionary;
|
||||||
|
|
||||||
|
private IDictionary<string, PropertyMetadata> properties;
|
||||||
|
|
||||||
|
|
||||||
|
public Type ElementType {
|
||||||
|
get {
|
||||||
|
if (element_type == null)
|
||||||
|
return typeof (JsonData);
|
||||||
|
|
||||||
|
return element_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
set { element_type = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsDictionary {
|
||||||
|
get { return is_dictionary; }
|
||||||
|
set { is_dictionary = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public IDictionary<string, PropertyMetadata> Properties {
|
||||||
|
get { return properties; }
|
||||||
|
set { properties = value; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal delegate void ExporterFunc (object obj, JsonWriter writer);
|
||||||
|
public delegate void ExporterFunc<T> (T obj, JsonWriter writer);
|
||||||
|
|
||||||
|
internal delegate object ImporterFunc (object input);
|
||||||
|
public delegate TValue ImporterFunc<TJson, TValue> (TJson input);
|
||||||
|
|
||||||
|
public delegate IJsonWrapper WrapperFactory ();
|
||||||
|
|
||||||
|
|
||||||
|
public class JsonMapper
|
||||||
|
{
|
||||||
|
#region Fields
|
||||||
|
private static readonly int max_nesting_depth;
|
||||||
|
|
||||||
|
private static readonly IFormatProvider datetime_format;
|
||||||
|
|
||||||
|
private static readonly IDictionary<Type, ExporterFunc> base_exporters_table;
|
||||||
|
private static readonly IDictionary<Type, ExporterFunc> custom_exporters_table;
|
||||||
|
private static readonly object custom_exporters_table_lock = new Object();
|
||||||
|
|
||||||
|
private static readonly IDictionary<Type,
|
||||||
|
IDictionary<Type, ImporterFunc>> base_importers_table;
|
||||||
|
private static readonly IDictionary<Type,
|
||||||
|
IDictionary<Type, ImporterFunc>> custom_importers_table;
|
||||||
|
private static readonly object custom_importers_table_lock = new Object();
|
||||||
|
|
||||||
|
private static readonly IDictionary<Type, ArrayMetadata> array_metadata;
|
||||||
|
private static readonly object array_metadata_lock = new Object ();
|
||||||
|
|
||||||
|
private static readonly IDictionary<Type,
|
||||||
|
IDictionary<Type, MethodInfo>> conv_ops;
|
||||||
|
private static readonly object conv_ops_lock = new Object ();
|
||||||
|
|
||||||
|
private static readonly IDictionary<Type, ObjectMetadata> object_metadata;
|
||||||
|
private static readonly object object_metadata_lock = new Object ();
|
||||||
|
|
||||||
|
private static readonly IDictionary<Type,
|
||||||
|
IList<PropertyMetadata>> type_properties;
|
||||||
|
private static readonly object type_properties_lock = new Object ();
|
||||||
|
|
||||||
|
private static readonly JsonWriter static_writer;
|
||||||
|
private static readonly object static_writer_lock = new Object ();
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
static JsonMapper ()
|
||||||
|
{
|
||||||
|
max_nesting_depth = 100;
|
||||||
|
|
||||||
|
array_metadata = new Dictionary<Type, ArrayMetadata> ();
|
||||||
|
conv_ops = new Dictionary<Type, IDictionary<Type, MethodInfo>> ();
|
||||||
|
object_metadata = new Dictionary<Type, ObjectMetadata> ();
|
||||||
|
type_properties = new Dictionary<Type,
|
||||||
|
IList<PropertyMetadata>> ();
|
||||||
|
|
||||||
|
static_writer = new JsonWriter ();
|
||||||
|
|
||||||
|
datetime_format = DateTimeFormatInfo.InvariantInfo;
|
||||||
|
|
||||||
|
base_exporters_table = new Dictionary<Type, ExporterFunc> ();
|
||||||
|
custom_exporters_table = new Dictionary<Type, ExporterFunc> ();
|
||||||
|
|
||||||
|
base_importers_table = new Dictionary<Type,
|
||||||
|
IDictionary<Type, ImporterFunc>> ();
|
||||||
|
custom_importers_table = new Dictionary<Type,
|
||||||
|
IDictionary<Type, ImporterFunc>> ();
|
||||||
|
|
||||||
|
RegisterBaseExporters ();
|
||||||
|
RegisterBaseImporters ();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
private static void AddArrayMetadata (Type type)
|
||||||
|
{
|
||||||
|
if (array_metadata.ContainsKey (type))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ArrayMetadata data = new ArrayMetadata ();
|
||||||
|
|
||||||
|
data.IsArray = type.IsArray;
|
||||||
|
|
||||||
|
if (type.GetInterface ("System.Collections.IList") != null)
|
||||||
|
data.IsList = true;
|
||||||
|
|
||||||
|
foreach (PropertyInfo p_info in type.GetProperties ()) {
|
||||||
|
if (p_info.Name != "Item")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ParameterInfo[] parameters = p_info.GetIndexParameters ();
|
||||||
|
|
||||||
|
if (parameters.Length != 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (parameters[0].ParameterType == typeof (int))
|
||||||
|
data.ElementType = p_info.PropertyType;
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (array_metadata_lock) {
|
||||||
|
try {
|
||||||
|
array_metadata.Add (type, data);
|
||||||
|
} catch (ArgumentException) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddObjectMetadata (Type type)
|
||||||
|
{
|
||||||
|
if (object_metadata.ContainsKey (type))
|
||||||
|
return;
|
||||||
|
|
||||||
|
ObjectMetadata data = new ObjectMetadata ();
|
||||||
|
|
||||||
|
if (type.GetInterface ("System.Collections.IDictionary") != null)
|
||||||
|
data.IsDictionary = true;
|
||||||
|
|
||||||
|
data.Properties = new Dictionary<string, PropertyMetadata> ();
|
||||||
|
|
||||||
|
foreach (PropertyInfo p_info in type.GetProperties ()) {
|
||||||
|
if (p_info.Name == "Item") {
|
||||||
|
ParameterInfo[] parameters = p_info.GetIndexParameters ();
|
||||||
|
|
||||||
|
if (parameters.Length != 1)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (parameters[0].ParameterType == typeof (string))
|
||||||
|
data.ElementType = p_info.PropertyType;
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
PropertyMetadata p_data = new PropertyMetadata ();
|
||||||
|
p_data.Info = p_info;
|
||||||
|
p_data.Type = p_info.PropertyType;
|
||||||
|
|
||||||
|
data.Properties.Add (p_info.Name, p_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (FieldInfo f_info in type.GetFields ()) {
|
||||||
|
PropertyMetadata p_data = new PropertyMetadata ();
|
||||||
|
p_data.Info = f_info;
|
||||||
|
p_data.IsField = true;
|
||||||
|
p_data.Type = f_info.FieldType;
|
||||||
|
|
||||||
|
data.Properties.Add (f_info.Name, p_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (object_metadata_lock) {
|
||||||
|
try {
|
||||||
|
object_metadata.Add (type, data);
|
||||||
|
} catch (ArgumentException) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddTypeProperties (Type type)
|
||||||
|
{
|
||||||
|
if (type_properties.ContainsKey (type))
|
||||||
|
return;
|
||||||
|
|
||||||
|
IList<PropertyMetadata> props = new List<PropertyMetadata> ();
|
||||||
|
|
||||||
|
foreach (PropertyInfo p_info in type.GetProperties ()) {
|
||||||
|
if (p_info.Name == "Item")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PropertyMetadata p_data = new PropertyMetadata ();
|
||||||
|
p_data.Info = p_info;
|
||||||
|
p_data.IsField = false;
|
||||||
|
props.Add (p_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (FieldInfo f_info in type.GetFields ()) {
|
||||||
|
PropertyMetadata p_data = new PropertyMetadata ();
|
||||||
|
p_data.Info = f_info;
|
||||||
|
p_data.IsField = true;
|
||||||
|
|
||||||
|
props.Add (p_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (type_properties_lock) {
|
||||||
|
try {
|
||||||
|
type_properties.Add (type, props);
|
||||||
|
} catch (ArgumentException) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MethodInfo GetConvOp (Type t1, Type t2)
|
||||||
|
{
|
||||||
|
lock (conv_ops_lock) {
|
||||||
|
if (! conv_ops.ContainsKey (t1))
|
||||||
|
conv_ops.Add (t1, new Dictionary<Type, MethodInfo> ());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conv_ops[t1].ContainsKey (t2))
|
||||||
|
return conv_ops[t1][t2];
|
||||||
|
|
||||||
|
MethodInfo op = t1.GetMethod (
|
||||||
|
"op_Implicit", new Type[] { t2 });
|
||||||
|
|
||||||
|
lock (conv_ops_lock) {
|
||||||
|
try {
|
||||||
|
conv_ops[t1].Add (t2, op);
|
||||||
|
} catch (ArgumentException) {
|
||||||
|
return conv_ops[t1][t2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return op;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static object ReadValue (Type inst_type, JsonReader reader)
|
||||||
|
{
|
||||||
|
reader.Read ();
|
||||||
|
|
||||||
|
if (reader.Token == JsonToken.ArrayEnd)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
Type underlying_type = Nullable.GetUnderlyingType(inst_type);
|
||||||
|
Type value_type = underlying_type ?? inst_type;
|
||||||
|
|
||||||
|
if (reader.Token == JsonToken.Null) {
|
||||||
|
#if NETSTANDARD1_5
|
||||||
|
if (inst_type.IsClass() || underlying_type != null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (inst_type.IsClass || underlying_type != null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
throw new JsonException (String.Format (
|
||||||
|
"Can't assign null to an instance of type {0}",
|
||||||
|
inst_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.Token == JsonToken.Double ||
|
||||||
|
reader.Token == JsonToken.Int ||
|
||||||
|
reader.Token == JsonToken.Long ||
|
||||||
|
reader.Token == JsonToken.String ||
|
||||||
|
reader.Token == JsonToken.Boolean) {
|
||||||
|
|
||||||
|
Type json_type = reader.Value.GetType ();
|
||||||
|
|
||||||
|
if (value_type.IsAssignableFrom (json_type))
|
||||||
|
return reader.Value;
|
||||||
|
|
||||||
|
// If there's a custom importer that fits, use it
|
||||||
|
lock (custom_importers_table_lock) {
|
||||||
|
if (custom_importers_table.TryGetValue(json_type, out IDictionary<Type, ImporterFunc> customImporterTablesValue) &&
|
||||||
|
customImporterTablesValue.TryGetValue(value_type, out ImporterFunc customImporter)) {
|
||||||
|
|
||||||
|
return customImporter(reader.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe there's a base importer that works
|
||||||
|
if (base_importers_table.TryGetValue(json_type, out IDictionary<Type, ImporterFunc> baseImporterTablesValue) &&
|
||||||
|
baseImporterTablesValue.TryGetValue(value_type, out ImporterFunc baseImporter)) {
|
||||||
|
|
||||||
|
return baseImporter(reader.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe it's an enum
|
||||||
|
#if NETSTANDARD1_5
|
||||||
|
if (value_type.IsEnum())
|
||||||
|
return Enum.ToObject (value_type, reader.Value);
|
||||||
|
#else
|
||||||
|
if (value_type.IsEnum)
|
||||||
|
return Enum.ToObject (value_type, reader.Value);
|
||||||
|
#endif
|
||||||
|
// Try using an implicit conversion operator
|
||||||
|
MethodInfo conv_op = GetConvOp (value_type, json_type);
|
||||||
|
|
||||||
|
if (conv_op != null)
|
||||||
|
return conv_op.Invoke (null,
|
||||||
|
new object[] { reader.Value });
|
||||||
|
|
||||||
|
// No luck
|
||||||
|
throw new JsonException (String.Format (
|
||||||
|
"Can't assign value '{0}' (type {1}) to type {2}",
|
||||||
|
reader.Value, json_type, inst_type));
|
||||||
|
}
|
||||||
|
|
||||||
|
object instance = null;
|
||||||
|
|
||||||
|
if (reader.Token == JsonToken.ArrayStart) {
|
||||||
|
|
||||||
|
AddArrayMetadata (inst_type);
|
||||||
|
ArrayMetadata t_data = array_metadata[inst_type];
|
||||||
|
|
||||||
|
if (! t_data.IsArray && ! t_data.IsList)
|
||||||
|
throw new JsonException (String.Format (
|
||||||
|
"Type {0} can't act as an array",
|
||||||
|
inst_type));
|
||||||
|
|
||||||
|
IList list;
|
||||||
|
Type elem_type;
|
||||||
|
|
||||||
|
if (! t_data.IsArray) {
|
||||||
|
list = (IList) Activator.CreateInstance (inst_type);
|
||||||
|
elem_type = t_data.ElementType;
|
||||||
|
} else {
|
||||||
|
list = new ArrayList ();
|
||||||
|
elem_type = inst_type.GetElementType ();
|
||||||
|
}
|
||||||
|
|
||||||
|
list.Clear();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
object item = ReadValue (elem_type, reader);
|
||||||
|
if (item == null && reader.Token == JsonToken.ArrayEnd)
|
||||||
|
break;
|
||||||
|
|
||||||
|
list.Add (item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (t_data.IsArray) {
|
||||||
|
int n = list.Count;
|
||||||
|
instance = Array.CreateInstance (elem_type, n);
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
((Array) instance).SetValue (list[i], i);
|
||||||
|
} else
|
||||||
|
instance = list;
|
||||||
|
|
||||||
|
} else if (reader.Token == JsonToken.ObjectStart) {
|
||||||
|
AddObjectMetadata (value_type);
|
||||||
|
ObjectMetadata t_data = object_metadata[value_type];
|
||||||
|
|
||||||
|
instance = Activator.CreateInstance (value_type);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
reader.Read ();
|
||||||
|
|
||||||
|
if (reader.Token == JsonToken.ObjectEnd)
|
||||||
|
break;
|
||||||
|
|
||||||
|
string property = (string) reader.Value;
|
||||||
|
|
||||||
|
if (t_data.Properties.ContainsKey (property)) {
|
||||||
|
PropertyMetadata prop_data =
|
||||||
|
t_data.Properties[property];
|
||||||
|
|
||||||
|
if (prop_data.IsField) {
|
||||||
|
((FieldInfo) prop_data.Info).SetValue (
|
||||||
|
instance, ReadValue (prop_data.Type, reader));
|
||||||
|
} else {
|
||||||
|
PropertyInfo p_info =
|
||||||
|
(PropertyInfo) prop_data.Info;
|
||||||
|
|
||||||
|
if (p_info.CanWrite)
|
||||||
|
p_info.SetValue (
|
||||||
|
instance,
|
||||||
|
ReadValue (prop_data.Type, reader),
|
||||||
|
null);
|
||||||
|
else
|
||||||
|
ReadValue (prop_data.Type, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (! t_data.IsDictionary) {
|
||||||
|
|
||||||
|
if (! reader.SkipNonMembers) {
|
||||||
|
throw new JsonException (String.Format (
|
||||||
|
"The type {0} doesn't have the " +
|
||||||
|
"property '{1}'",
|
||||||
|
inst_type, property));
|
||||||
|
} else {
|
||||||
|
ReadSkip (reader);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
((IDictionary) instance).Add (
|
||||||
|
property, ReadValue (
|
||||||
|
t_data.ElementType, reader));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IJsonWrapper ReadValue (WrapperFactory factory,
|
||||||
|
JsonReader reader)
|
||||||
|
{
|
||||||
|
reader.Read ();
|
||||||
|
|
||||||
|
if (reader.Token == JsonToken.ArrayEnd ||
|
||||||
|
reader.Token == JsonToken.Null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
IJsonWrapper instance = factory ();
|
||||||
|
|
||||||
|
if (reader.Token == JsonToken.String) {
|
||||||
|
instance.SetString ((string) reader.Value);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.Token == JsonToken.Double) {
|
||||||
|
instance.SetDouble ((double) reader.Value);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.Token == JsonToken.Int) {
|
||||||
|
instance.SetInt ((int) reader.Value);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.Token == JsonToken.Long) {
|
||||||
|
instance.SetLong ((long) reader.Value);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.Token == JsonToken.Boolean) {
|
||||||
|
instance.SetBoolean ((bool) reader.Value);
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader.Token == JsonToken.ArrayStart) {
|
||||||
|
instance.SetJsonType (JsonType.Array);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
IJsonWrapper item = ReadValue (factory, reader);
|
||||||
|
if (item == null && reader.Token == JsonToken.ArrayEnd)
|
||||||
|
break;
|
||||||
|
|
||||||
|
((IList) instance).Add (item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (reader.Token == JsonToken.ObjectStart) {
|
||||||
|
instance.SetJsonType (JsonType.Object);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
reader.Read ();
|
||||||
|
|
||||||
|
if (reader.Token == JsonToken.ObjectEnd)
|
||||||
|
break;
|
||||||
|
|
||||||
|
string property = (string) reader.Value;
|
||||||
|
|
||||||
|
((IDictionary) instance)[property] = ReadValue (
|
||||||
|
factory, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReadSkip (JsonReader reader)
|
||||||
|
{
|
||||||
|
ToWrapper (
|
||||||
|
delegate { return new JsonMockWrapper (); }, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RegisterBaseExporters ()
|
||||||
|
{
|
||||||
|
// This method is only called from the static initializer,
|
||||||
|
// so there is no need to explicitly lock any static members here
|
||||||
|
base_exporters_table[typeof (byte)] =
|
||||||
|
delegate (object obj, JsonWriter writer) {
|
||||||
|
writer.Write (Convert.ToInt32 ((byte) obj));
|
||||||
|
};
|
||||||
|
|
||||||
|
base_exporters_table[typeof (char)] =
|
||||||
|
delegate (object obj, JsonWriter writer) {
|
||||||
|
writer.Write (Convert.ToString ((char) obj));
|
||||||
|
};
|
||||||
|
|
||||||
|
base_exporters_table[typeof (DateTime)] =
|
||||||
|
delegate (object obj, JsonWriter writer) {
|
||||||
|
writer.Write (Convert.ToString ((DateTime) obj,
|
||||||
|
datetime_format));
|
||||||
|
};
|
||||||
|
|
||||||
|
base_exporters_table[typeof (decimal)] =
|
||||||
|
delegate (object obj, JsonWriter writer) {
|
||||||
|
writer.Write ((decimal) obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
base_exporters_table[typeof (sbyte)] =
|
||||||
|
delegate (object obj, JsonWriter writer) {
|
||||||
|
writer.Write (Convert.ToInt32 ((sbyte) obj));
|
||||||
|
};
|
||||||
|
|
||||||
|
base_exporters_table[typeof (short)] =
|
||||||
|
delegate (object obj, JsonWriter writer) {
|
||||||
|
writer.Write (Convert.ToInt32 ((short) obj));
|
||||||
|
};
|
||||||
|
|
||||||
|
base_exporters_table[typeof (ushort)] =
|
||||||
|
delegate (object obj, JsonWriter writer) {
|
||||||
|
writer.Write (Convert.ToInt32 ((ushort) obj));
|
||||||
|
};
|
||||||
|
|
||||||
|
base_exporters_table[typeof (uint)] =
|
||||||
|
delegate (object obj, JsonWriter writer) {
|
||||||
|
writer.Write (Convert.ToUInt64 ((uint) obj));
|
||||||
|
};
|
||||||
|
|
||||||
|
base_exporters_table[typeof (ulong)] =
|
||||||
|
delegate (object obj, JsonWriter writer) {
|
||||||
|
writer.Write ((ulong) obj);
|
||||||
|
};
|
||||||
|
|
||||||
|
base_exporters_table[typeof(DateTimeOffset)] =
|
||||||
|
delegate (object obj, JsonWriter writer) {
|
||||||
|
writer.Write(((DateTimeOffset)obj).ToString("yyyy-MM-ddTHH:mm:ss.fffffffzzz", datetime_format));
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RegisterBaseImporters ()
|
||||||
|
{
|
||||||
|
// This method is only called from the static initializer,
|
||||||
|
// so there is no need to explicitly lock any static members here
|
||||||
|
ImporterFunc importer;
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToByte ((int) input);
|
||||||
|
};
|
||||||
|
RegisterImporter (base_importers_table, typeof (int),
|
||||||
|
typeof (byte), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToUInt64 ((int) input);
|
||||||
|
};
|
||||||
|
RegisterImporter (base_importers_table, typeof (int),
|
||||||
|
typeof (ulong), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToInt64((int)input);
|
||||||
|
};
|
||||||
|
RegisterImporter(base_importers_table, typeof(int),
|
||||||
|
typeof(long), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToSByte ((int) input);
|
||||||
|
};
|
||||||
|
RegisterImporter (base_importers_table, typeof (int),
|
||||||
|
typeof (sbyte), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToInt16 ((int) input);
|
||||||
|
};
|
||||||
|
RegisterImporter (base_importers_table, typeof (int),
|
||||||
|
typeof (short), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToUInt16 ((int) input);
|
||||||
|
};
|
||||||
|
RegisterImporter (base_importers_table, typeof (int),
|
||||||
|
typeof (ushort), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToUInt32 ((int) input);
|
||||||
|
};
|
||||||
|
RegisterImporter (base_importers_table, typeof (int),
|
||||||
|
typeof (uint), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToSingle ((int) input);
|
||||||
|
};
|
||||||
|
RegisterImporter (base_importers_table, typeof (int),
|
||||||
|
typeof (float), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToDouble ((int) input);
|
||||||
|
};
|
||||||
|
RegisterImporter (base_importers_table, typeof (int),
|
||||||
|
typeof (double), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToDecimal ((double) input);
|
||||||
|
};
|
||||||
|
RegisterImporter (base_importers_table, typeof (double),
|
||||||
|
typeof (decimal), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToSingle((double)input);
|
||||||
|
};
|
||||||
|
RegisterImporter(base_importers_table, typeof(double),
|
||||||
|
typeof(float), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToUInt32 ((long) input);
|
||||||
|
};
|
||||||
|
RegisterImporter (base_importers_table, typeof (long),
|
||||||
|
typeof (uint), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToChar ((string) input);
|
||||||
|
};
|
||||||
|
RegisterImporter (base_importers_table, typeof (string),
|
||||||
|
typeof (char), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return Convert.ToDateTime ((string) input, datetime_format);
|
||||||
|
};
|
||||||
|
RegisterImporter (base_importers_table, typeof (string),
|
||||||
|
typeof (DateTime), importer);
|
||||||
|
|
||||||
|
importer = delegate (object input) {
|
||||||
|
return DateTimeOffset.Parse((string)input, datetime_format);
|
||||||
|
};
|
||||||
|
RegisterImporter(base_importers_table, typeof(string),
|
||||||
|
typeof(DateTimeOffset), importer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RegisterImporter (
|
||||||
|
IDictionary<Type, IDictionary<Type, ImporterFunc>> table,
|
||||||
|
Type json_type, Type value_type, ImporterFunc importer)
|
||||||
|
{
|
||||||
|
if (! table.ContainsKey (json_type))
|
||||||
|
table.Add (json_type, new Dictionary<Type, ImporterFunc> ());
|
||||||
|
|
||||||
|
table[json_type][value_type] = importer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void WriteValue (object obj, JsonWriter writer,
|
||||||
|
bool writer_is_private,
|
||||||
|
int depth)
|
||||||
|
{
|
||||||
|
if (depth > max_nesting_depth)
|
||||||
|
throw new JsonException (
|
||||||
|
String.Format ("Max allowed object depth reached while " +
|
||||||
|
"trying to export from type {0}",
|
||||||
|
obj.GetType ()));
|
||||||
|
|
||||||
|
if (obj == null) {
|
||||||
|
writer.Write (null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj is IJsonWrapper) {
|
||||||
|
if (writer_is_private)
|
||||||
|
writer.TextWriter.Write (((IJsonWrapper) obj).ToJson ());
|
||||||
|
else
|
||||||
|
((IJsonWrapper) obj).ToJson (writer);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj is String) {
|
||||||
|
writer.Write ((string) obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj is Double) {
|
||||||
|
writer.Write ((double) obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj is Single)
|
||||||
|
{
|
||||||
|
writer.Write((float)obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj is Int32) {
|
||||||
|
writer.Write ((int) obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj is Boolean) {
|
||||||
|
writer.Write ((bool) obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj is Int64) {
|
||||||
|
writer.Write ((long) obj);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj is Array) {
|
||||||
|
writer.WriteArrayStart ();
|
||||||
|
|
||||||
|
foreach (object elem in (Array) obj)
|
||||||
|
WriteValue (elem, writer, writer_is_private, depth + 1);
|
||||||
|
|
||||||
|
writer.WriteArrayEnd ();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj is IList) {
|
||||||
|
writer.WriteArrayStart ();
|
||||||
|
foreach (object elem in (IList) obj)
|
||||||
|
WriteValue (elem, writer, writer_is_private, depth + 1);
|
||||||
|
writer.WriteArrayEnd ();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (obj is IDictionary dictionary) {
|
||||||
|
writer.WriteObjectStart ();
|
||||||
|
foreach (DictionaryEntry entry in dictionary) {
|
||||||
|
var propertyName = entry.Key is string key ?
|
||||||
|
key
|
||||||
|
: Convert.ToString(entry.Key, CultureInfo.InvariantCulture);
|
||||||
|
writer.WritePropertyName (propertyName);
|
||||||
|
WriteValue (entry.Value, writer, writer_is_private,
|
||||||
|
depth + 1);
|
||||||
|
}
|
||||||
|
writer.WriteObjectEnd ();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Type obj_type = obj.GetType ();
|
||||||
|
|
||||||
|
// See if there's a custom exporter for the object
|
||||||
|
lock (custom_exporters_table_lock) {
|
||||||
|
if (custom_exporters_table.TryGetValue(obj_type, out ExporterFunc customExporter)) {
|
||||||
|
customExporter(obj, writer);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If not, maybe there's a base exporter
|
||||||
|
if (base_exporters_table.TryGetValue(obj_type, out ExporterFunc baseExporter)) {
|
||||||
|
baseExporter(obj, writer);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last option, let's see if it's an enum
|
||||||
|
if (obj is Enum) {
|
||||||
|
Type e_type = Enum.GetUnderlyingType (obj_type);
|
||||||
|
|
||||||
|
if (e_type == typeof (long))
|
||||||
|
writer.Write ((long) obj);
|
||||||
|
else if (e_type == typeof (uint))
|
||||||
|
writer.Write ((uint) obj);
|
||||||
|
else if (e_type == typeof (ulong))
|
||||||
|
writer.Write ((ulong) obj);
|
||||||
|
else if (e_type == typeof(ushort))
|
||||||
|
writer.Write ((ushort)obj);
|
||||||
|
else if (e_type == typeof(short))
|
||||||
|
writer.Write ((short)obj);
|
||||||
|
else if (e_type == typeof(byte))
|
||||||
|
writer.Write ((byte)obj);
|
||||||
|
else if (e_type == typeof(sbyte))
|
||||||
|
writer.Write ((sbyte)obj);
|
||||||
|
else
|
||||||
|
writer.Write ((int) obj);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Okay, so it looks like the input should be exported as an
|
||||||
|
// object
|
||||||
|
AddTypeProperties (obj_type);
|
||||||
|
IList<PropertyMetadata> props = type_properties[obj_type];
|
||||||
|
|
||||||
|
writer.WriteObjectStart ();
|
||||||
|
foreach (PropertyMetadata p_data in props) {
|
||||||
|
if (p_data.IsField) {
|
||||||
|
writer.WritePropertyName (p_data.Info.Name);
|
||||||
|
WriteValue (((FieldInfo) p_data.Info).GetValue (obj),
|
||||||
|
writer, writer_is_private, depth + 1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
PropertyInfo p_info = (PropertyInfo) p_data.Info;
|
||||||
|
|
||||||
|
if (p_info.CanRead) {
|
||||||
|
writer.WritePropertyName (p_data.Info.Name);
|
||||||
|
WriteValue (p_info.GetValue (obj, null),
|
||||||
|
writer, writer_is_private, depth + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
writer.WriteObjectEnd ();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
public static string ToJson (object obj)
|
||||||
|
{
|
||||||
|
lock (static_writer_lock) {
|
||||||
|
static_writer.Reset ();
|
||||||
|
|
||||||
|
WriteValue (obj, static_writer, true, 0);
|
||||||
|
|
||||||
|
return static_writer.ToString ();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ToJson (object obj, JsonWriter writer)
|
||||||
|
{
|
||||||
|
WriteValue (obj, writer, false, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonData ToObject (JsonReader reader)
|
||||||
|
{
|
||||||
|
return (JsonData) ToWrapper (
|
||||||
|
delegate { return new JsonData (); }, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonData ToObject (TextReader reader)
|
||||||
|
{
|
||||||
|
JsonReader json_reader = new JsonReader (reader);
|
||||||
|
|
||||||
|
return (JsonData) ToWrapper (
|
||||||
|
delegate { return new JsonData (); }, json_reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static JsonData ToObject (string json)
|
||||||
|
{
|
||||||
|
return (JsonData) ToWrapper (
|
||||||
|
delegate { return new JsonData (); }, json);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T ToObject<T> (JsonReader reader)
|
||||||
|
{
|
||||||
|
return (T) ReadValue (typeof (T), reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T ToObject<T> (TextReader reader)
|
||||||
|
{
|
||||||
|
JsonReader json_reader = new JsonReader (reader);
|
||||||
|
|
||||||
|
return (T) ReadValue (typeof (T), json_reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static T ToObject<T> (string json)
|
||||||
|
{
|
||||||
|
JsonReader reader = new JsonReader (json);
|
||||||
|
|
||||||
|
return (T) ReadValue (typeof (T), reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static object ToObject(string json, Type ConvertType )
|
||||||
|
{
|
||||||
|
JsonReader reader = new JsonReader(json);
|
||||||
|
|
||||||
|
return ReadValue(ConvertType, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IJsonWrapper ToWrapper (WrapperFactory factory,
|
||||||
|
JsonReader reader)
|
||||||
|
{
|
||||||
|
return ReadValue (factory, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static IJsonWrapper ToWrapper (WrapperFactory factory,
|
||||||
|
string json)
|
||||||
|
{
|
||||||
|
JsonReader reader = new JsonReader (json);
|
||||||
|
|
||||||
|
return ReadValue (factory, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RegisterExporter<T> (ExporterFunc<T> exporter)
|
||||||
|
{
|
||||||
|
ExporterFunc exporter_wrapper =
|
||||||
|
delegate (object obj, JsonWriter writer) {
|
||||||
|
exporter ((T) obj, writer);
|
||||||
|
};
|
||||||
|
|
||||||
|
lock (custom_exporters_table_lock) {
|
||||||
|
custom_exporters_table[typeof (T)] = exporter_wrapper;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RegisterImporter<TJson, TValue> (
|
||||||
|
ImporterFunc<TJson, TValue> importer)
|
||||||
|
{
|
||||||
|
ImporterFunc importer_wrapper =
|
||||||
|
delegate (object input) {
|
||||||
|
return importer ((TJson) input);
|
||||||
|
};
|
||||||
|
|
||||||
|
lock (custom_importers_table_lock) {
|
||||||
|
RegisterImporter (custom_importers_table, typeof (TJson),
|
||||||
|
typeof (TValue), importer_wrapper);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UnregisterExporters ()
|
||||||
|
{
|
||||||
|
lock (custom_exporters_table_lock) {
|
||||||
|
custom_exporters_table.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void UnregisterImporters ()
|
||||||
|
{
|
||||||
|
lock (custom_importers_table_lock) {
|
||||||
|
custom_importers_table.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3e14ed6947460448ea1c1512947ced5d
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
#region Header
|
||||||
|
/**
|
||||||
|
* JsonMockWrapper.cs
|
||||||
|
* Mock object implementing IJsonWrapper, to facilitate actions like
|
||||||
|
* skipping data more efficiently.
|
||||||
|
*
|
||||||
|
* The authors disclaim copyright to this source code. For more details, see
|
||||||
|
* the COPYING file included with this distribution.
|
||||||
|
**/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Collections.Specialized;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Guru.LitJson
|
||||||
|
{
|
||||||
|
public class JsonMockWrapper : IJsonWrapper
|
||||||
|
{
|
||||||
|
public bool IsArray { get { return false; } }
|
||||||
|
public bool IsBoolean { get { return false; } }
|
||||||
|
public bool IsDouble { get { return false; } }
|
||||||
|
public bool IsInt { get { return false; } }
|
||||||
|
public bool IsLong { get { return false; } }
|
||||||
|
public bool IsObject { get { return false; } }
|
||||||
|
public bool IsString { get { return false; } }
|
||||||
|
|
||||||
|
public bool GetBoolean () { return false; }
|
||||||
|
public double GetDouble () { return 0.0; }
|
||||||
|
public int GetInt () { return 0; }
|
||||||
|
public JsonType GetJsonType () { return JsonType.None; }
|
||||||
|
public long GetLong () { return 0L; }
|
||||||
|
public string GetString () { return ""; }
|
||||||
|
|
||||||
|
public void SetBoolean (bool val) {}
|
||||||
|
public void SetDouble (double val) {}
|
||||||
|
public void SetInt (int val) {}
|
||||||
|
public void SetJsonType (JsonType type) {}
|
||||||
|
public void SetLong (long val) {}
|
||||||
|
public void SetString (string val) {}
|
||||||
|
|
||||||
|
public string ToJson () { return ""; }
|
||||||
|
public void ToJson (JsonWriter writer) {}
|
||||||
|
|
||||||
|
|
||||||
|
bool IList.IsFixedSize { get { return true; } }
|
||||||
|
bool IList.IsReadOnly { get { return true; } }
|
||||||
|
|
||||||
|
object IList.this[int index] {
|
||||||
|
get { return null; }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
|
||||||
|
int IList.Add (object value) { return 0; }
|
||||||
|
void IList.Clear () {}
|
||||||
|
bool IList.Contains (object value) { return false; }
|
||||||
|
int IList.IndexOf (object value) { return -1; }
|
||||||
|
void IList.Insert (int i, object v) {}
|
||||||
|
void IList.Remove (object value) {}
|
||||||
|
void IList.RemoveAt (int index) {}
|
||||||
|
|
||||||
|
|
||||||
|
int ICollection.Count { get { return 0; } }
|
||||||
|
bool ICollection.IsSynchronized { get { return false; } }
|
||||||
|
object ICollection.SyncRoot { get { return null; } }
|
||||||
|
|
||||||
|
void ICollection.CopyTo (Array array, int index) {}
|
||||||
|
|
||||||
|
|
||||||
|
IEnumerator IEnumerable.GetEnumerator () { return null; }
|
||||||
|
|
||||||
|
|
||||||
|
bool IDictionary.IsFixedSize { get { return true; } }
|
||||||
|
bool IDictionary.IsReadOnly { get { return true; } }
|
||||||
|
|
||||||
|
ICollection IDictionary.Keys { get { return null; } }
|
||||||
|
ICollection IDictionary.Values { get { return null; } }
|
||||||
|
|
||||||
|
object IDictionary.this[object key] {
|
||||||
|
get { return null; }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IDictionary.Add (object k, object v) {}
|
||||||
|
void IDictionary.Clear () {}
|
||||||
|
bool IDictionary.Contains (object key) { return false; }
|
||||||
|
void IDictionary.Remove (object key) {}
|
||||||
|
|
||||||
|
IDictionaryEnumerator IDictionary.GetEnumerator () { return null; }
|
||||||
|
|
||||||
|
|
||||||
|
object IOrderedDictionary.this[int idx] {
|
||||||
|
get { return null; }
|
||||||
|
set {}
|
||||||
|
}
|
||||||
|
|
||||||
|
IDictionaryEnumerator IOrderedDictionary.GetEnumerator () {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
void IOrderedDictionary.Insert (int i, object k, object v) {}
|
||||||
|
void IOrderedDictionary.RemoveAt (int i) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b54da0524de4e4164893c964b0b7b443
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,478 @@
|
||||||
|
#region Header
|
||||||
|
/**
|
||||||
|
* JsonReader.cs
|
||||||
|
* Stream-like access to JSON text.
|
||||||
|
*
|
||||||
|
* The authors disclaim copyright to this source code. For more details, see
|
||||||
|
* the COPYING file included with this distribution.
|
||||||
|
**/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Guru.LitJson
|
||||||
|
{
|
||||||
|
public enum JsonToken
|
||||||
|
{
|
||||||
|
None,
|
||||||
|
|
||||||
|
ObjectStart,
|
||||||
|
PropertyName,
|
||||||
|
ObjectEnd,
|
||||||
|
|
||||||
|
ArrayStart,
|
||||||
|
ArrayEnd,
|
||||||
|
|
||||||
|
Int,
|
||||||
|
Long,
|
||||||
|
Double,
|
||||||
|
|
||||||
|
String,
|
||||||
|
|
||||||
|
Boolean,
|
||||||
|
Null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class JsonReader
|
||||||
|
{
|
||||||
|
#region Fields
|
||||||
|
private static readonly IDictionary<int, IDictionary<int, int[]>> parse_table;
|
||||||
|
|
||||||
|
private Stack<int> automaton_stack;
|
||||||
|
private int current_input;
|
||||||
|
private int current_symbol;
|
||||||
|
private bool end_of_json;
|
||||||
|
private bool end_of_input;
|
||||||
|
private Lexer lexer;
|
||||||
|
private bool parser_in_string;
|
||||||
|
private bool parser_return;
|
||||||
|
private bool read_started;
|
||||||
|
private TextReader reader;
|
||||||
|
private bool reader_is_owned;
|
||||||
|
private bool skip_non_members;
|
||||||
|
private object token_value;
|
||||||
|
private JsonToken token;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Public Properties
|
||||||
|
public bool AllowComments {
|
||||||
|
get { return lexer.AllowComments; }
|
||||||
|
set { lexer.AllowComments = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowSingleQuotedStrings {
|
||||||
|
get { return lexer.AllowSingleQuotedStrings; }
|
||||||
|
set { lexer.AllowSingleQuotedStrings = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool SkipNonMembers {
|
||||||
|
get { return skip_non_members; }
|
||||||
|
set { skip_non_members = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EndOfInput {
|
||||||
|
get { return end_of_input; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EndOfJson {
|
||||||
|
get { return end_of_json; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonToken Token {
|
||||||
|
get { return token; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Value {
|
||||||
|
get { return token_value; }
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
static JsonReader ()
|
||||||
|
{
|
||||||
|
parse_table = PopulateParseTable ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonReader (string json_text) :
|
||||||
|
this (new StringReader (json_text), true)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonReader (TextReader reader) :
|
||||||
|
this (reader, false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private JsonReader (TextReader reader, bool owned)
|
||||||
|
{
|
||||||
|
if (reader == null)
|
||||||
|
throw new ArgumentNullException ("reader");
|
||||||
|
|
||||||
|
parser_in_string = false;
|
||||||
|
parser_return = false;
|
||||||
|
|
||||||
|
read_started = false;
|
||||||
|
automaton_stack = new Stack<int> ();
|
||||||
|
automaton_stack.Push ((int) ParserToken.End);
|
||||||
|
automaton_stack.Push ((int) ParserToken.Text);
|
||||||
|
|
||||||
|
lexer = new Lexer (reader);
|
||||||
|
|
||||||
|
end_of_input = false;
|
||||||
|
end_of_json = false;
|
||||||
|
|
||||||
|
skip_non_members = true;
|
||||||
|
|
||||||
|
this.reader = reader;
|
||||||
|
reader_is_owned = owned;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Static Methods
|
||||||
|
private static IDictionary<int, IDictionary<int, int[]>> PopulateParseTable ()
|
||||||
|
{
|
||||||
|
// See section A.2. of the manual for details
|
||||||
|
IDictionary<int, IDictionary<int, int[]>> parse_table = new Dictionary<int, IDictionary<int, int[]>> ();
|
||||||
|
|
||||||
|
TableAddRow (parse_table, ParserToken.Array);
|
||||||
|
TableAddCol (parse_table, ParserToken.Array, '[',
|
||||||
|
'[',
|
||||||
|
(int) ParserToken.ArrayPrime);
|
||||||
|
|
||||||
|
TableAddRow (parse_table, ParserToken.ArrayPrime);
|
||||||
|
TableAddCol (parse_table, ParserToken.ArrayPrime, '"',
|
||||||
|
(int) ParserToken.Value,
|
||||||
|
|
||||||
|
(int) ParserToken.ValueRest,
|
||||||
|
']');
|
||||||
|
TableAddCol (parse_table, ParserToken.ArrayPrime, '[',
|
||||||
|
(int) ParserToken.Value,
|
||||||
|
(int) ParserToken.ValueRest,
|
||||||
|
']');
|
||||||
|
TableAddCol (parse_table, ParserToken.ArrayPrime, ']',
|
||||||
|
']');
|
||||||
|
TableAddCol (parse_table, ParserToken.ArrayPrime, '{',
|
||||||
|
(int) ParserToken.Value,
|
||||||
|
(int) ParserToken.ValueRest,
|
||||||
|
']');
|
||||||
|
TableAddCol (parse_table, ParserToken.ArrayPrime, (int) ParserToken.Number,
|
||||||
|
(int) ParserToken.Value,
|
||||||
|
(int) ParserToken.ValueRest,
|
||||||
|
']');
|
||||||
|
TableAddCol (parse_table, ParserToken.ArrayPrime, (int) ParserToken.True,
|
||||||
|
(int) ParserToken.Value,
|
||||||
|
(int) ParserToken.ValueRest,
|
||||||
|
']');
|
||||||
|
TableAddCol (parse_table, ParserToken.ArrayPrime, (int) ParserToken.False,
|
||||||
|
(int) ParserToken.Value,
|
||||||
|
(int) ParserToken.ValueRest,
|
||||||
|
']');
|
||||||
|
TableAddCol (parse_table, ParserToken.ArrayPrime, (int) ParserToken.Null,
|
||||||
|
(int) ParserToken.Value,
|
||||||
|
(int) ParserToken.ValueRest,
|
||||||
|
']');
|
||||||
|
|
||||||
|
TableAddRow (parse_table, ParserToken.Object);
|
||||||
|
TableAddCol (parse_table, ParserToken.Object, '{',
|
||||||
|
'{',
|
||||||
|
(int) ParserToken.ObjectPrime);
|
||||||
|
|
||||||
|
TableAddRow (parse_table, ParserToken.ObjectPrime);
|
||||||
|
TableAddCol (parse_table, ParserToken.ObjectPrime, '"',
|
||||||
|
(int) ParserToken.Pair,
|
||||||
|
(int) ParserToken.PairRest,
|
||||||
|
'}');
|
||||||
|
TableAddCol (parse_table, ParserToken.ObjectPrime, '}',
|
||||||
|
'}');
|
||||||
|
|
||||||
|
TableAddRow (parse_table, ParserToken.Pair);
|
||||||
|
TableAddCol (parse_table, ParserToken.Pair, '"',
|
||||||
|
(int) ParserToken.String,
|
||||||
|
':',
|
||||||
|
(int) ParserToken.Value);
|
||||||
|
|
||||||
|
TableAddRow (parse_table, ParserToken.PairRest);
|
||||||
|
TableAddCol (parse_table, ParserToken.PairRest, ',',
|
||||||
|
',',
|
||||||
|
(int) ParserToken.Pair,
|
||||||
|
(int) ParserToken.PairRest);
|
||||||
|
TableAddCol (parse_table, ParserToken.PairRest, '}',
|
||||||
|
(int) ParserToken.Epsilon);
|
||||||
|
|
||||||
|
TableAddRow (parse_table, ParserToken.String);
|
||||||
|
TableAddCol (parse_table, ParserToken.String, '"',
|
||||||
|
'"',
|
||||||
|
(int) ParserToken.CharSeq,
|
||||||
|
'"');
|
||||||
|
|
||||||
|
TableAddRow (parse_table, ParserToken.Text);
|
||||||
|
TableAddCol (parse_table, ParserToken.Text, '[',
|
||||||
|
(int) ParserToken.Array);
|
||||||
|
TableAddCol (parse_table, ParserToken.Text, '{',
|
||||||
|
(int) ParserToken.Object);
|
||||||
|
|
||||||
|
TableAddRow (parse_table, ParserToken.Value);
|
||||||
|
TableAddCol (parse_table, ParserToken.Value, '"',
|
||||||
|
(int) ParserToken.String);
|
||||||
|
TableAddCol (parse_table, ParserToken.Value, '[',
|
||||||
|
(int) ParserToken.Array);
|
||||||
|
TableAddCol (parse_table, ParserToken.Value, '{',
|
||||||
|
(int) ParserToken.Object);
|
||||||
|
TableAddCol (parse_table, ParserToken.Value, (int) ParserToken.Number,
|
||||||
|
(int) ParserToken.Number);
|
||||||
|
TableAddCol (parse_table, ParserToken.Value, (int) ParserToken.True,
|
||||||
|
(int) ParserToken.True);
|
||||||
|
TableAddCol (parse_table, ParserToken.Value, (int) ParserToken.False,
|
||||||
|
(int) ParserToken.False);
|
||||||
|
TableAddCol (parse_table, ParserToken.Value, (int) ParserToken.Null,
|
||||||
|
(int) ParserToken.Null);
|
||||||
|
|
||||||
|
TableAddRow (parse_table, ParserToken.ValueRest);
|
||||||
|
TableAddCol (parse_table, ParserToken.ValueRest, ',',
|
||||||
|
',',
|
||||||
|
(int) ParserToken.Value,
|
||||||
|
(int) ParserToken.ValueRest);
|
||||||
|
TableAddCol (parse_table, ParserToken.ValueRest, ']',
|
||||||
|
(int) ParserToken.Epsilon);
|
||||||
|
|
||||||
|
return parse_table;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TableAddCol (IDictionary<int, IDictionary<int, int[]>> parse_table, ParserToken row, int col,
|
||||||
|
params int[] symbols)
|
||||||
|
{
|
||||||
|
parse_table[(int) row].Add (col, symbols);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TableAddRow (IDictionary<int, IDictionary<int, int[]>> parse_table, ParserToken rule)
|
||||||
|
{
|
||||||
|
parse_table.Add ((int) rule, new Dictionary<int, int[]> ());
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
private void ProcessNumber (string number)
|
||||||
|
{
|
||||||
|
if (number.IndexOf ('.') != -1 ||
|
||||||
|
number.IndexOf ('e') != -1 ||
|
||||||
|
number.IndexOf ('E') != -1) {
|
||||||
|
|
||||||
|
double n_double;
|
||||||
|
if (double.TryParse (number, NumberStyles.Any, CultureInfo.InvariantCulture, out n_double)) {
|
||||||
|
token = JsonToken.Double;
|
||||||
|
token_value = n_double;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int n_int32;
|
||||||
|
if (int.TryParse (number, NumberStyles.Integer, CultureInfo.InvariantCulture, out n_int32)) {
|
||||||
|
token = JsonToken.Int;
|
||||||
|
token_value = n_int32;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
long n_int64;
|
||||||
|
if (long.TryParse (number, NumberStyles.Integer, CultureInfo.InvariantCulture, out n_int64)) {
|
||||||
|
token = JsonToken.Long;
|
||||||
|
token_value = n_int64;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong n_uint64;
|
||||||
|
if (ulong.TryParse(number, NumberStyles.Integer, CultureInfo.InvariantCulture, out n_uint64))
|
||||||
|
{
|
||||||
|
token = JsonToken.Long;
|
||||||
|
token_value = n_uint64;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shouldn't happen, but just in case, return something
|
||||||
|
token = JsonToken.Int;
|
||||||
|
token_value = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessSymbol ()
|
||||||
|
{
|
||||||
|
if (current_symbol == '[') {
|
||||||
|
token = JsonToken.ArrayStart;
|
||||||
|
parser_return = true;
|
||||||
|
|
||||||
|
} else if (current_symbol == ']') {
|
||||||
|
token = JsonToken.ArrayEnd;
|
||||||
|
parser_return = true;
|
||||||
|
|
||||||
|
} else if (current_symbol == '{') {
|
||||||
|
token = JsonToken.ObjectStart;
|
||||||
|
parser_return = true;
|
||||||
|
|
||||||
|
} else if (current_symbol == '}') {
|
||||||
|
token = JsonToken.ObjectEnd;
|
||||||
|
parser_return = true;
|
||||||
|
|
||||||
|
} else if (current_symbol == '"') {
|
||||||
|
if (parser_in_string) {
|
||||||
|
parser_in_string = false;
|
||||||
|
|
||||||
|
parser_return = true;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if (token == JsonToken.None)
|
||||||
|
token = JsonToken.String;
|
||||||
|
|
||||||
|
parser_in_string = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (current_symbol == (int) ParserToken.CharSeq) {
|
||||||
|
token_value = lexer.StringValue;
|
||||||
|
|
||||||
|
} else if (current_symbol == (int) ParserToken.False) {
|
||||||
|
token = JsonToken.Boolean;
|
||||||
|
token_value = false;
|
||||||
|
parser_return = true;
|
||||||
|
|
||||||
|
} else if (current_symbol == (int) ParserToken.Null) {
|
||||||
|
token = JsonToken.Null;
|
||||||
|
parser_return = true;
|
||||||
|
|
||||||
|
} else if (current_symbol == (int) ParserToken.Number) {
|
||||||
|
ProcessNumber (lexer.StringValue);
|
||||||
|
|
||||||
|
parser_return = true;
|
||||||
|
|
||||||
|
} else if (current_symbol == (int) ParserToken.Pair) {
|
||||||
|
token = JsonToken.PropertyName;
|
||||||
|
|
||||||
|
} else if (current_symbol == (int) ParserToken.True) {
|
||||||
|
token = JsonToken.Boolean;
|
||||||
|
token_value = true;
|
||||||
|
parser_return = true;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ReadToken ()
|
||||||
|
{
|
||||||
|
if (end_of_input)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
lexer.NextToken ();
|
||||||
|
|
||||||
|
if (lexer.EndOfInput) {
|
||||||
|
Close ();
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_input = lexer.Token;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
public void Close ()
|
||||||
|
{
|
||||||
|
if (end_of_input)
|
||||||
|
return;
|
||||||
|
|
||||||
|
end_of_input = true;
|
||||||
|
end_of_json = true;
|
||||||
|
|
||||||
|
if (reader_is_owned)
|
||||||
|
{
|
||||||
|
using(reader){}
|
||||||
|
}
|
||||||
|
|
||||||
|
reader = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Read ()
|
||||||
|
{
|
||||||
|
if (end_of_input)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (end_of_json) {
|
||||||
|
end_of_json = false;
|
||||||
|
automaton_stack.Clear ();
|
||||||
|
automaton_stack.Push ((int) ParserToken.End);
|
||||||
|
automaton_stack.Push ((int) ParserToken.Text);
|
||||||
|
}
|
||||||
|
|
||||||
|
parser_in_string = false;
|
||||||
|
parser_return = false;
|
||||||
|
|
||||||
|
token = JsonToken.None;
|
||||||
|
token_value = null;
|
||||||
|
|
||||||
|
if (! read_started) {
|
||||||
|
read_started = true;
|
||||||
|
|
||||||
|
if (! ReadToken ())
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int[] entry_symbols;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
if (parser_return) {
|
||||||
|
if (automaton_stack.Peek () == (int) ParserToken.End)
|
||||||
|
end_of_json = true;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
current_symbol = automaton_stack.Pop ();
|
||||||
|
|
||||||
|
ProcessSymbol ();
|
||||||
|
|
||||||
|
if (current_symbol == current_input) {
|
||||||
|
if (! ReadToken ()) {
|
||||||
|
if (automaton_stack.Peek () != (int) ParserToken.End)
|
||||||
|
throw new JsonException (
|
||||||
|
"Input doesn't evaluate to proper JSON text");
|
||||||
|
|
||||||
|
if (parser_return)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
entry_symbols =
|
||||||
|
parse_table[current_symbol][current_input];
|
||||||
|
|
||||||
|
} catch (KeyNotFoundException e) {
|
||||||
|
throw new JsonException ((ParserToken) current_input, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry_symbols[0] == (int) ParserToken.Epsilon)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (int i = entry_symbols.Length - 1; i >= 0; i--)
|
||||||
|
automaton_stack.Push (entry_symbols[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a486d94a05da74fb4a2e1d9d1eb53455
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,484 @@
|
||||||
|
#region Header
|
||||||
|
/**
|
||||||
|
* JsonWriter.cs
|
||||||
|
* Stream-like facility to output JSON text.
|
||||||
|
*
|
||||||
|
* The authors disclaim copyright to this source code. For more details, see
|
||||||
|
* the COPYING file included with this distribution.
|
||||||
|
**/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Guru.LitJson
|
||||||
|
{
|
||||||
|
internal enum Condition
|
||||||
|
{
|
||||||
|
InArray,
|
||||||
|
InObject,
|
||||||
|
NotAProperty,
|
||||||
|
Property,
|
||||||
|
Value
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class WriterContext
|
||||||
|
{
|
||||||
|
public int Count;
|
||||||
|
public bool InArray;
|
||||||
|
public bool InObject;
|
||||||
|
public bool ExpectingValue;
|
||||||
|
public int Padding;
|
||||||
|
}
|
||||||
|
|
||||||
|
public class JsonWriter
|
||||||
|
{
|
||||||
|
#region Fields
|
||||||
|
private static readonly NumberFormatInfo number_format;
|
||||||
|
|
||||||
|
private WriterContext context;
|
||||||
|
private Stack<WriterContext> ctx_stack;
|
||||||
|
private bool has_reached_end;
|
||||||
|
private char[] hex_seq;
|
||||||
|
private int indentation;
|
||||||
|
private int indent_value;
|
||||||
|
private StringBuilder inst_string_builder;
|
||||||
|
private bool pretty_print;
|
||||||
|
private bool validate;
|
||||||
|
private bool lower_case_properties;
|
||||||
|
private TextWriter writer;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
public int IndentValue {
|
||||||
|
get { return indent_value; }
|
||||||
|
set {
|
||||||
|
indentation = (indentation / indent_value) * value;
|
||||||
|
indent_value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool PrettyPrint {
|
||||||
|
get { return pretty_print; }
|
||||||
|
set { pretty_print = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextWriter TextWriter {
|
||||||
|
get { return writer; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Validate {
|
||||||
|
get { return validate; }
|
||||||
|
set { validate = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool LowerCaseProperties {
|
||||||
|
get { return lower_case_properties; }
|
||||||
|
set { lower_case_properties = value; }
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
static JsonWriter ()
|
||||||
|
{
|
||||||
|
number_format = NumberFormatInfo.InvariantInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonWriter ()
|
||||||
|
{
|
||||||
|
inst_string_builder = new StringBuilder ();
|
||||||
|
writer = new StringWriter (inst_string_builder);
|
||||||
|
|
||||||
|
Init ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonWriter (StringBuilder sb) :
|
||||||
|
this (new StringWriter (sb))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public JsonWriter (TextWriter writer)
|
||||||
|
{
|
||||||
|
if (writer == null)
|
||||||
|
throw new ArgumentNullException ("writer");
|
||||||
|
|
||||||
|
this.writer = writer;
|
||||||
|
|
||||||
|
Init ();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Private Methods
|
||||||
|
private void DoValidation (Condition cond)
|
||||||
|
{
|
||||||
|
if (! context.ExpectingValue)
|
||||||
|
context.Count++;
|
||||||
|
|
||||||
|
if (! validate)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (has_reached_end)
|
||||||
|
throw new JsonException (
|
||||||
|
"A complete JSON symbol has already been written");
|
||||||
|
|
||||||
|
switch (cond) {
|
||||||
|
case Condition.InArray:
|
||||||
|
if (! context.InArray)
|
||||||
|
throw new JsonException (
|
||||||
|
"Can't close an array here");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Condition.InObject:
|
||||||
|
if (! context.InObject || context.ExpectingValue)
|
||||||
|
throw new JsonException (
|
||||||
|
"Can't close an object here");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Condition.NotAProperty:
|
||||||
|
if (context.InObject && ! context.ExpectingValue)
|
||||||
|
throw new JsonException (
|
||||||
|
"Expected a property");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Condition.Property:
|
||||||
|
if (! context.InObject || context.ExpectingValue)
|
||||||
|
throw new JsonException (
|
||||||
|
"Can't add a property here");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Condition.Value:
|
||||||
|
if (! context.InArray &&
|
||||||
|
(! context.InObject || ! context.ExpectingValue))
|
||||||
|
throw new JsonException (
|
||||||
|
"Can't add a value here");
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Init ()
|
||||||
|
{
|
||||||
|
has_reached_end = false;
|
||||||
|
hex_seq = new char[4];
|
||||||
|
indentation = 0;
|
||||||
|
indent_value = 4;
|
||||||
|
pretty_print = false;
|
||||||
|
validate = true;
|
||||||
|
lower_case_properties = false;
|
||||||
|
|
||||||
|
ctx_stack = new Stack<WriterContext> ();
|
||||||
|
context = new WriterContext ();
|
||||||
|
ctx_stack.Push (context);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void IntToHex (int n, char[] hex)
|
||||||
|
{
|
||||||
|
int num;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
num = n % 16;
|
||||||
|
|
||||||
|
if (num < 10)
|
||||||
|
hex[3 - i] = (char) ('0' + num);
|
||||||
|
else
|
||||||
|
hex[3 - i] = (char) ('A' + (num - 10));
|
||||||
|
|
||||||
|
n >>= 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Indent ()
|
||||||
|
{
|
||||||
|
if (pretty_print)
|
||||||
|
indentation += indent_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void Put (string str)
|
||||||
|
{
|
||||||
|
if (pretty_print && ! context.ExpectingValue)
|
||||||
|
for (int i = 0; i < indentation; i++)
|
||||||
|
writer.Write (' ');
|
||||||
|
|
||||||
|
writer.Write (str);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PutNewline ()
|
||||||
|
{
|
||||||
|
PutNewline (true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PutNewline (bool add_comma)
|
||||||
|
{
|
||||||
|
if (add_comma && ! context.ExpectingValue &&
|
||||||
|
context.Count > 1)
|
||||||
|
writer.Write (',');
|
||||||
|
|
||||||
|
if (pretty_print && ! context.ExpectingValue)
|
||||||
|
writer.Write (Environment.NewLine);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PutString (string str)
|
||||||
|
{
|
||||||
|
Put (String.Empty);
|
||||||
|
|
||||||
|
writer.Write ('"');
|
||||||
|
|
||||||
|
int n = str.Length;
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
switch (str[i]) {
|
||||||
|
case '\n':
|
||||||
|
writer.Write ("\\n");
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case '\r':
|
||||||
|
writer.Write ("\\r");
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case '\t':
|
||||||
|
writer.Write ("\\t");
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
case '\\':
|
||||||
|
writer.Write ('\\');
|
||||||
|
writer.Write (str[i]);
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case '\f':
|
||||||
|
writer.Write ("\\f");
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case '\b':
|
||||||
|
writer.Write ("\\b");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((int) str[i] >= 32 && (int) str[i] <= 126) {
|
||||||
|
writer.Write (str[i]);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default, turn into a \uXXXX sequence
|
||||||
|
IntToHex ((int) str[i], hex_seq);
|
||||||
|
writer.Write ("\\u");
|
||||||
|
writer.Write (hex_seq);
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Write ('"');
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Unindent ()
|
||||||
|
{
|
||||||
|
if (pretty_print)
|
||||||
|
indentation -= indent_value;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
public override string ToString ()
|
||||||
|
{
|
||||||
|
if (inst_string_builder == null)
|
||||||
|
return String.Empty;
|
||||||
|
|
||||||
|
return inst_string_builder.ToString ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset ()
|
||||||
|
{
|
||||||
|
has_reached_end = false;
|
||||||
|
|
||||||
|
ctx_stack.Clear ();
|
||||||
|
context = new WriterContext ();
|
||||||
|
ctx_stack.Push (context);
|
||||||
|
|
||||||
|
if (inst_string_builder != null)
|
||||||
|
inst_string_builder.Remove (0, inst_string_builder.Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write (bool boolean)
|
||||||
|
{
|
||||||
|
DoValidation (Condition.Value);
|
||||||
|
PutNewline ();
|
||||||
|
|
||||||
|
Put (boolean ? "true" : "false");
|
||||||
|
|
||||||
|
context.ExpectingValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write (decimal number)
|
||||||
|
{
|
||||||
|
DoValidation (Condition.Value);
|
||||||
|
PutNewline ();
|
||||||
|
|
||||||
|
Put (Convert.ToString (number, number_format));
|
||||||
|
|
||||||
|
context.ExpectingValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write (double number)
|
||||||
|
{
|
||||||
|
DoValidation (Condition.Value);
|
||||||
|
PutNewline ();
|
||||||
|
|
||||||
|
string str = Convert.ToString (number, number_format);
|
||||||
|
Put (str);
|
||||||
|
|
||||||
|
if (str.IndexOf ('.') == -1 &&
|
||||||
|
str.IndexOf ('E') == -1)
|
||||||
|
writer.Write (".0");
|
||||||
|
|
||||||
|
context.ExpectingValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write(float number)
|
||||||
|
{
|
||||||
|
DoValidation(Condition.Value);
|
||||||
|
PutNewline();
|
||||||
|
|
||||||
|
string str = Convert.ToString(number, number_format);
|
||||||
|
Put(str);
|
||||||
|
|
||||||
|
context.ExpectingValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write (int number)
|
||||||
|
{
|
||||||
|
DoValidation (Condition.Value);
|
||||||
|
PutNewline ();
|
||||||
|
|
||||||
|
Put (Convert.ToString (number, number_format));
|
||||||
|
|
||||||
|
context.ExpectingValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write (long number)
|
||||||
|
{
|
||||||
|
DoValidation (Condition.Value);
|
||||||
|
PutNewline ();
|
||||||
|
|
||||||
|
Put (Convert.ToString (number, number_format));
|
||||||
|
|
||||||
|
context.ExpectingValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Write (string str)
|
||||||
|
{
|
||||||
|
DoValidation (Condition.Value);
|
||||||
|
PutNewline ();
|
||||||
|
|
||||||
|
if (str == null)
|
||||||
|
Put ("null");
|
||||||
|
else
|
||||||
|
PutString (str);
|
||||||
|
|
||||||
|
context.ExpectingValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CLSCompliant(false)]
|
||||||
|
public void Write (ulong number)
|
||||||
|
{
|
||||||
|
DoValidation (Condition.Value);
|
||||||
|
PutNewline ();
|
||||||
|
|
||||||
|
Put (Convert.ToString (number, number_format));
|
||||||
|
|
||||||
|
context.ExpectingValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteArrayEnd ()
|
||||||
|
{
|
||||||
|
DoValidation (Condition.InArray);
|
||||||
|
PutNewline (false);
|
||||||
|
|
||||||
|
ctx_stack.Pop ();
|
||||||
|
if (ctx_stack.Count == 1)
|
||||||
|
has_reached_end = true;
|
||||||
|
else {
|
||||||
|
context = ctx_stack.Peek ();
|
||||||
|
context.ExpectingValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unindent ();
|
||||||
|
Put ("]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteArrayStart ()
|
||||||
|
{
|
||||||
|
DoValidation (Condition.NotAProperty);
|
||||||
|
PutNewline ();
|
||||||
|
|
||||||
|
Put ("[");
|
||||||
|
|
||||||
|
context = new WriterContext ();
|
||||||
|
context.InArray = true;
|
||||||
|
ctx_stack.Push (context);
|
||||||
|
|
||||||
|
Indent ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteObjectEnd ()
|
||||||
|
{
|
||||||
|
DoValidation (Condition.InObject);
|
||||||
|
PutNewline (false);
|
||||||
|
|
||||||
|
ctx_stack.Pop ();
|
||||||
|
if (ctx_stack.Count == 1)
|
||||||
|
has_reached_end = true;
|
||||||
|
else {
|
||||||
|
context = ctx_stack.Peek ();
|
||||||
|
context.ExpectingValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Unindent ();
|
||||||
|
Put ("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WriteObjectStart ()
|
||||||
|
{
|
||||||
|
DoValidation (Condition.NotAProperty);
|
||||||
|
PutNewline ();
|
||||||
|
|
||||||
|
Put ("{");
|
||||||
|
|
||||||
|
context = new WriterContext ();
|
||||||
|
context.InObject = true;
|
||||||
|
ctx_stack.Push (context);
|
||||||
|
|
||||||
|
Indent ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WritePropertyName (string property_name)
|
||||||
|
{
|
||||||
|
DoValidation (Condition.Property);
|
||||||
|
PutNewline ();
|
||||||
|
string propertyName = (property_name == null || !lower_case_properties)
|
||||||
|
? property_name
|
||||||
|
: property_name.ToLowerInvariant();
|
||||||
|
|
||||||
|
PutString (propertyName);
|
||||||
|
|
||||||
|
if (pretty_print) {
|
||||||
|
if (propertyName.Length > context.Padding)
|
||||||
|
context.Padding = propertyName.Length;
|
||||||
|
|
||||||
|
for (int i = context.Padding - propertyName.Length;
|
||||||
|
i >= 0; i--)
|
||||||
|
writer.Write (' ');
|
||||||
|
|
||||||
|
writer.Write (": ");
|
||||||
|
} else
|
||||||
|
writer.Write (':');
|
||||||
|
|
||||||
|
context.ExpectingValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b5c3c60bc9e974025813d454b53835e1
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,912 @@
|
||||||
|
#region Header
|
||||||
|
/**
|
||||||
|
* Lexer.cs
|
||||||
|
* JSON lexer implementation based on a finite state machine.
|
||||||
|
*
|
||||||
|
* The authors disclaim copyright to this source code. For more details, see
|
||||||
|
* the COPYING file included with this distribution.
|
||||||
|
**/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
|
||||||
|
namespace Guru.LitJson
|
||||||
|
{
|
||||||
|
internal class FsmContext
|
||||||
|
{
|
||||||
|
public bool Return;
|
||||||
|
public int NextState;
|
||||||
|
public Lexer L;
|
||||||
|
public int StateStack;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
internal class Lexer
|
||||||
|
{
|
||||||
|
#region Fields
|
||||||
|
private delegate bool StateHandler (FsmContext ctx);
|
||||||
|
|
||||||
|
private static readonly int[] fsm_return_table;
|
||||||
|
private static readonly StateHandler[] fsm_handler_table;
|
||||||
|
|
||||||
|
private bool allow_comments;
|
||||||
|
private bool allow_single_quoted_strings;
|
||||||
|
private bool end_of_input;
|
||||||
|
private FsmContext fsm_context;
|
||||||
|
private int input_buffer;
|
||||||
|
private int input_char;
|
||||||
|
private TextReader reader;
|
||||||
|
private int state;
|
||||||
|
private StringBuilder string_buffer;
|
||||||
|
private string string_value;
|
||||||
|
private int token;
|
||||||
|
private int unichar;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
public bool AllowComments {
|
||||||
|
get { return allow_comments; }
|
||||||
|
set { allow_comments = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool AllowSingleQuotedStrings {
|
||||||
|
get { return allow_single_quoted_strings; }
|
||||||
|
set { allow_single_quoted_strings = value; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool EndOfInput {
|
||||||
|
get { return end_of_input; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Token {
|
||||||
|
get { return token; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public string StringValue {
|
||||||
|
get { return string_value; }
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
static Lexer ()
|
||||||
|
{
|
||||||
|
PopulateFsmTables (out fsm_handler_table, out fsm_return_table);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Lexer (TextReader reader)
|
||||||
|
{
|
||||||
|
allow_comments = true;
|
||||||
|
allow_single_quoted_strings = true;
|
||||||
|
|
||||||
|
input_buffer = 0;
|
||||||
|
string_buffer = new StringBuilder (128);
|
||||||
|
state = 1;
|
||||||
|
end_of_input = false;
|
||||||
|
this.reader = reader;
|
||||||
|
|
||||||
|
fsm_context = new FsmContext ();
|
||||||
|
fsm_context.L = this;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
#region Static Methods
|
||||||
|
private static int HexValue (int digit)
|
||||||
|
{
|
||||||
|
switch (digit) {
|
||||||
|
case 'a':
|
||||||
|
case 'A':
|
||||||
|
return 10;
|
||||||
|
|
||||||
|
case 'b':
|
||||||
|
case 'B':
|
||||||
|
return 11;
|
||||||
|
|
||||||
|
case 'c':
|
||||||
|
case 'C':
|
||||||
|
return 12;
|
||||||
|
|
||||||
|
case 'd':
|
||||||
|
case 'D':
|
||||||
|
return 13;
|
||||||
|
|
||||||
|
case 'e':
|
||||||
|
case 'E':
|
||||||
|
return 14;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
case 'F':
|
||||||
|
return 15;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return digit - '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PopulateFsmTables (out StateHandler[] fsm_handler_table, out int[] fsm_return_table)
|
||||||
|
{
|
||||||
|
// See section A.1. of the manual for details of the finite
|
||||||
|
// state machine.
|
||||||
|
fsm_handler_table = new StateHandler[28] {
|
||||||
|
State1,
|
||||||
|
State2,
|
||||||
|
State3,
|
||||||
|
State4,
|
||||||
|
State5,
|
||||||
|
State6,
|
||||||
|
State7,
|
||||||
|
State8,
|
||||||
|
State9,
|
||||||
|
State10,
|
||||||
|
State11,
|
||||||
|
State12,
|
||||||
|
State13,
|
||||||
|
State14,
|
||||||
|
State15,
|
||||||
|
State16,
|
||||||
|
State17,
|
||||||
|
State18,
|
||||||
|
State19,
|
||||||
|
State20,
|
||||||
|
State21,
|
||||||
|
State22,
|
||||||
|
State23,
|
||||||
|
State24,
|
||||||
|
State25,
|
||||||
|
State26,
|
||||||
|
State27,
|
||||||
|
State28
|
||||||
|
};
|
||||||
|
|
||||||
|
fsm_return_table = new int[28] {
|
||||||
|
(int) ParserToken.Char,
|
||||||
|
0,
|
||||||
|
(int) ParserToken.Number,
|
||||||
|
(int) ParserToken.Number,
|
||||||
|
0,
|
||||||
|
(int) ParserToken.Number,
|
||||||
|
0,
|
||||||
|
(int) ParserToken.Number,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
(int) ParserToken.True,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
(int) ParserToken.False,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
(int) ParserToken.Null,
|
||||||
|
(int) ParserToken.CharSeq,
|
||||||
|
(int) ParserToken.Char,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
(int) ParserToken.CharSeq,
|
||||||
|
(int) ParserToken.Char,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static char ProcessEscChar (int esc_char)
|
||||||
|
{
|
||||||
|
switch (esc_char) {
|
||||||
|
case '"':
|
||||||
|
case '\'':
|
||||||
|
case '\\':
|
||||||
|
case '/':
|
||||||
|
return Convert.ToChar (esc_char);
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
return '\n';
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
return '\t';
|
||||||
|
|
||||||
|
case 'r':
|
||||||
|
return '\r';
|
||||||
|
|
||||||
|
case 'b':
|
||||||
|
return '\b';
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
return '\f';
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Unreachable
|
||||||
|
return '?';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State1 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
while (ctx.L.GetChar ()) {
|
||||||
|
if (ctx.L.input_char == ' ' ||
|
||||||
|
ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ctx.L.input_char >= '1' && ctx.L.input_char <= '9') {
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
ctx.NextState = 3;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case '"':
|
||||||
|
ctx.NextState = 19;
|
||||||
|
ctx.Return = true;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ',':
|
||||||
|
case ':':
|
||||||
|
case '[':
|
||||||
|
case ']':
|
||||||
|
case '{':
|
||||||
|
case '}':
|
||||||
|
ctx.NextState = 1;
|
||||||
|
ctx.Return = true;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '-':
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
ctx.NextState = 2;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '0':
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
ctx.NextState = 4;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 'f':
|
||||||
|
ctx.NextState = 12;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 'n':
|
||||||
|
ctx.NextState = 16;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 't':
|
||||||
|
ctx.NextState = 9;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '\'':
|
||||||
|
if (! ctx.L.allow_single_quoted_strings)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ctx.L.input_char = '"';
|
||||||
|
ctx.NextState = 23;
|
||||||
|
ctx.Return = true;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '/':
|
||||||
|
if (! ctx.L.allow_comments)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
ctx.NextState = 25;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State2 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
if (ctx.L.input_char >= '1' && ctx.L.input_char<= '9') {
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
ctx.NextState = 3;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case '0':
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
ctx.NextState = 4;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State3 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
while (ctx.L.GetChar ()) {
|
||||||
|
if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9') {
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.L.input_char == ' ' ||
|
||||||
|
ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') {
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case ',':
|
||||||
|
case ']':
|
||||||
|
case '}':
|
||||||
|
ctx.L.UngetChar ();
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '.':
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
ctx.NextState = 5;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 'e':
|
||||||
|
case 'E':
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
ctx.NextState = 7;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State4 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
if (ctx.L.input_char == ' ' ||
|
||||||
|
ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') {
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case ',':
|
||||||
|
case ']':
|
||||||
|
case '}':
|
||||||
|
ctx.L.UngetChar ();
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '.':
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
ctx.NextState = 5;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 'e':
|
||||||
|
case 'E':
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
ctx.NextState = 7;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State5 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9') {
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
ctx.NextState = 6;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State6 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
while (ctx.L.GetChar ()) {
|
||||||
|
if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9') {
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.L.input_char == ' ' ||
|
||||||
|
ctx.L.input_char >= '\t' && ctx.L.input_char <= '\r') {
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case ',':
|
||||||
|
case ']':
|
||||||
|
case '}':
|
||||||
|
ctx.L.UngetChar ();
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case 'e':
|
||||||
|
case 'E':
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
ctx.NextState = 7;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State7 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
if (ctx.L.input_char >= '0' && ctx.L.input_char<= '9') {
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
ctx.NextState = 8;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case '+':
|
||||||
|
case '-':
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
ctx.NextState = 8;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State8 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
while (ctx.L.GetChar ()) {
|
||||||
|
if (ctx.L.input_char >= '0' && ctx.L.input_char<= '9') {
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.L.input_char == ' ' ||
|
||||||
|
ctx.L.input_char >= '\t' && ctx.L.input_char<= '\r') {
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case ',':
|
||||||
|
case ']':
|
||||||
|
case '}':
|
||||||
|
ctx.L.UngetChar ();
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State9 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case 'r':
|
||||||
|
ctx.NextState = 10;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State10 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case 'u':
|
||||||
|
ctx.NextState = 11;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State11 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case 'e':
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State12 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case 'a':
|
||||||
|
ctx.NextState = 13;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State13 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case 'l':
|
||||||
|
ctx.NextState = 14;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State14 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case 's':
|
||||||
|
ctx.NextState = 15;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State15 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case 'e':
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State16 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case 'u':
|
||||||
|
ctx.NextState = 17;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State17 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case 'l':
|
||||||
|
ctx.NextState = 18;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State18 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case 'l':
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State19 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
while (ctx.L.GetChar ()) {
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case '"':
|
||||||
|
ctx.L.UngetChar ();
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 20;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '\\':
|
||||||
|
ctx.StateStack = 19;
|
||||||
|
ctx.NextState = 21;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State20 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case '"':
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State21 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case 'u':
|
||||||
|
ctx.NextState = 22;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
case '\'':
|
||||||
|
case '/':
|
||||||
|
case '\\':
|
||||||
|
case 'b':
|
||||||
|
case 'f':
|
||||||
|
case 'n':
|
||||||
|
case 'r':
|
||||||
|
case 't':
|
||||||
|
ctx.L.string_buffer.Append (
|
||||||
|
ProcessEscChar (ctx.L.input_char));
|
||||||
|
ctx.NextState = ctx.StateStack;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State22 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
int counter = 0;
|
||||||
|
int mult = 4096;
|
||||||
|
|
||||||
|
ctx.L.unichar = 0;
|
||||||
|
|
||||||
|
while (ctx.L.GetChar ()) {
|
||||||
|
|
||||||
|
if (ctx.L.input_char >= '0' && ctx.L.input_char <= '9' ||
|
||||||
|
ctx.L.input_char >= 'A' && ctx.L.input_char <= 'F' ||
|
||||||
|
ctx.L.input_char >= 'a' && ctx.L.input_char <= 'f') {
|
||||||
|
|
||||||
|
ctx.L.unichar += HexValue (ctx.L.input_char) * mult;
|
||||||
|
|
||||||
|
counter++;
|
||||||
|
mult /= 16;
|
||||||
|
|
||||||
|
if (counter == 4) {
|
||||||
|
ctx.L.string_buffer.Append (
|
||||||
|
Convert.ToChar (ctx.L.unichar));
|
||||||
|
ctx.NextState = ctx.StateStack;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State23 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
while (ctx.L.GetChar ()) {
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case '\'':
|
||||||
|
ctx.L.UngetChar ();
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 24;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '\\':
|
||||||
|
ctx.StateStack = 23;
|
||||||
|
ctx.NextState = 21;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
ctx.L.string_buffer.Append ((char) ctx.L.input_char);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State24 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case '\'':
|
||||||
|
ctx.L.input_char = '"';
|
||||||
|
ctx.Return = true;
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State25 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
ctx.L.GetChar ();
|
||||||
|
|
||||||
|
switch (ctx.L.input_char) {
|
||||||
|
case '*':
|
||||||
|
ctx.NextState = 27;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case '/':
|
||||||
|
ctx.NextState = 26;
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State26 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
while (ctx.L.GetChar ()) {
|
||||||
|
if (ctx.L.input_char == '\n') {
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State27 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
while (ctx.L.GetChar ()) {
|
||||||
|
if (ctx.L.input_char == '*') {
|
||||||
|
ctx.NextState = 28;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool State28 (FsmContext ctx)
|
||||||
|
{
|
||||||
|
while (ctx.L.GetChar ()) {
|
||||||
|
if (ctx.L.input_char == '*')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (ctx.L.input_char == '/') {
|
||||||
|
ctx.NextState = 1;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.NextState = 27;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
private bool GetChar ()
|
||||||
|
{
|
||||||
|
if ((input_char = NextChar ()) != -1)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
end_of_input = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int NextChar ()
|
||||||
|
{
|
||||||
|
if (input_buffer != 0) {
|
||||||
|
int tmp = input_buffer;
|
||||||
|
input_buffer = 0;
|
||||||
|
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
return reader.Read ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool NextToken ()
|
||||||
|
{
|
||||||
|
StateHandler handler;
|
||||||
|
fsm_context.Return = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
handler = fsm_handler_table[state - 1];
|
||||||
|
|
||||||
|
if (! handler (fsm_context))
|
||||||
|
throw new JsonException (input_char);
|
||||||
|
|
||||||
|
if (end_of_input)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (fsm_context.Return) {
|
||||||
|
string_value = string_buffer.ToString ();
|
||||||
|
string_buffer.Remove (0, string_buffer.Length);
|
||||||
|
token = fsm_return_table[state - 1];
|
||||||
|
|
||||||
|
if (token == (int) ParserToken.Char)
|
||||||
|
token = input_char;
|
||||||
|
|
||||||
|
state = fsm_context.NextState;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
state = fsm_context.NextState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UngetChar ()
|
||||||
|
{
|
||||||
|
input_buffer = input_char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 0e99ecf149fcd4361bafc43771b80d60
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,87 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>netstandard2.1;netstandard2.0;net45;net48;netstandard1.5;net40;net35;net20;net6.0;net8.0</TargetFrameworks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||||
|
<DebugType>embedded</DebugType>
|
||||||
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
|
<PublishRepositoryUrl>true</PublishRepositoryUrl>
|
||||||
|
<SourceLinkCreate Condition="'$(OS)' == 'Windows_NT'">true</SourceLinkCreate>
|
||||||
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition="'$(OS)' == 'Windows_NT'">
|
||||||
|
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="all" />
|
||||||
|
<DotNetCliToolReference Include="dotnet-sourcelink-git" Version="2.8.3" />
|
||||||
|
<DotNetCliToolReference Include="dotnet-sourcelink" Version="2.8.3" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<PackageId>LitJson</PackageId>
|
||||||
|
<Description>A .NET library to handle conversions from and to JSON (JavaScript Object Notation) strings. Written in C#, and it’s intended to be small, fast and easy to use.
|
||||||
|
It's quick and lean, without external dependencies.</Description>
|
||||||
|
<Copyright>The authors disclaim copyright to this source code.</Copyright>
|
||||||
|
<Authors>Leonardo Boshell, Mattias Karlsson and contributors</Authors>
|
||||||
|
<Company>Leonardo Boshell, Mattias Karlsson and contributors</Company>
|
||||||
|
<PackageLicenseExpression>Unlicense</PackageLicenseExpression>
|
||||||
|
<PackageIcon>litjson.png</PackageIcon>
|
||||||
|
<RepositoryUrl>https://github.com/LitJSON/litjson</RepositoryUrl>
|
||||||
|
<RepositoryType>git</RepositoryType>
|
||||||
|
<PackageTags>JSON;Serializer</PackageTags>
|
||||||
|
<IncludeSource>true</IncludeSource>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="litjson.png" Pack="true" PackagePath="" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' == 'net20' ">
|
||||||
|
<DefineConstants>$(DefineConstants);LEGACY</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' == 'net35' ">
|
||||||
|
<DefineConstants>$(DefineConstants);LEGACY</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(TargetFramework)' == 'net40' ">
|
||||||
|
<DefineConstants>$(DefineConstants);LEGACY</DefineConstants>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.5' ">
|
||||||
|
<PackageReference Include="System.Collections.NonGeneric" Version="4.3.0" />
|
||||||
|
<PackageReference Include="System.Collections.Specialized" Version="4.3.0" />
|
||||||
|
<PackageReference Include="System.Reflection" Version="4.3.0" />
|
||||||
|
<PackageReference Include="System.Reflection.TypeExtensions" Version="4.7.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net20" Version="1.0.3" Condition="'$(TargetFramework)' == 'net20' ">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net35" Version="1.0.3" Condition="'$(TargetFramework)' == 'net35' ">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net40" Version="1.0.3" Condition="'$(TargetFramework)' == 'net40' ">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net45" Version="1.0.3" Condition="'$(TargetFramework)' == 'net45' ">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies.net48" Version="1.0.3" Condition="'$(TargetFramework)' == 'net48' ">
|
||||||
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="../../README.md" Pack="true" PackagePath="\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c5e8519174c484ae1903459114f61f98
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
#if NETSTANDARD1_5
|
||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
|
namespace LitJson
|
||||||
|
{
|
||||||
|
internal static class Netstandard15Polyfill
|
||||||
|
{
|
||||||
|
internal static Type GetInterface(this Type type, string name)
|
||||||
|
{
|
||||||
|
return type.GetTypeInfo().GetInterface(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsClass(this Type type)
|
||||||
|
{
|
||||||
|
return type.GetTypeInfo().IsClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static bool IsEnum(this Type type)
|
||||||
|
{
|
||||||
|
return type.GetTypeInfo().IsEnum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e212e5f514d9d4ec68eed1251080cce4
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
#region Header
|
||||||
|
/**
|
||||||
|
* ParserToken.cs
|
||||||
|
* Internal representation of the tokens used by the lexer and the parser.
|
||||||
|
*
|
||||||
|
* The authors disclaim copyright to this source code. For more details, see
|
||||||
|
* the COPYING file included with this distribution.
|
||||||
|
**/
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
|
namespace Guru.LitJson
|
||||||
|
{
|
||||||
|
internal enum ParserToken
|
||||||
|
{
|
||||||
|
// Lexer tokens (see section A.1.1. of the manual)
|
||||||
|
None = System.Char.MaxValue + 1,
|
||||||
|
Number,
|
||||||
|
True,
|
||||||
|
False,
|
||||||
|
Null,
|
||||||
|
CharSeq,
|
||||||
|
// Single char
|
||||||
|
Char,
|
||||||
|
|
||||||
|
// Parser Rules (see section A.2.1 of the manual)
|
||||||
|
Text,
|
||||||
|
Object,
|
||||||
|
ObjectPrime,
|
||||||
|
Pair,
|
||||||
|
PairRest,
|
||||||
|
Array,
|
||||||
|
ArrayPrime,
|
||||||
|
Value,
|
||||||
|
ValueRest,
|
||||||
|
String,
|
||||||
|
|
||||||
|
// End of input
|
||||||
|
End,
|
||||||
|
|
||||||
|
// The empty rule
|
||||||
|
Epsilon
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6a046907ebdf54072ab870aa49b58f76
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
|
|
@ -0,0 +1,135 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f3a96012ad0784fc7893f9b9eb05788e
|
||||||
|
TextureImporter:
|
||||||
|
internalIDToNameTable: []
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 12
|
||||||
|
mipmaps:
|
||||||
|
mipMapMode: 0
|
||||||
|
enableMipMap: 1
|
||||||
|
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
|
||||||
|
isReadable: 0
|
||||||
|
streamingMipmaps: 0
|
||||||
|
streamingMipmapsPriority: 0
|
||||||
|
vTOnly: 0
|
||||||
|
ignoreMasterTextureLimit: 0
|
||||||
|
grayScaleToAlpha: 0
|
||||||
|
generateCubemap: 6
|
||||||
|
cubemapConvolution: 0
|
||||||
|
seamlessCubemap: 0
|
||||||
|
textureFormat: 1
|
||||||
|
maxTextureSize: 2048
|
||||||
|
textureSettings:
|
||||||
|
serializedVersion: 2
|
||||||
|
filterMode: 1
|
||||||
|
aniso: 1
|
||||||
|
mipBias: 0
|
||||||
|
wrapU: 0
|
||||||
|
wrapV: 0
|
||||||
|
wrapW: 0
|
||||||
|
nPOTScale: 1
|
||||||
|
lightmap: 0
|
||||||
|
compressionQuality: 50
|
||||||
|
spriteMode: 0
|
||||||
|
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: 0
|
||||||
|
spriteTessellationDetail: -1
|
||||||
|
textureType: 0
|
||||||
|
textureShape: 1
|
||||||
|
singleChannelComponent: 0
|
||||||
|
flipbookRows: 1
|
||||||
|
flipbookColumns: 1
|
||||||
|
maxTextureSizeSet: 0
|
||||||
|
compressionQualitySet: 0
|
||||||
|
textureFormatSet: 0
|
||||||
|
ignorePngGamma: 0
|
||||||
|
applyGammaDecoding: 0
|
||||||
|
cookieLightType: 0
|
||||||
|
platformSettings:
|
||||||
|
- serializedVersion: 3
|
||||||
|
buildTarget: DefaultTexturePlatform
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 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
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
buildTarget: Android
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
- serializedVersion: 3
|
||||||
|
buildTarget: Server
|
||||||
|
maxTextureSize: 2048
|
||||||
|
resizeAlgorithm: 0
|
||||||
|
textureFormat: -1
|
||||||
|
textureCompression: 1
|
||||||
|
compressionQuality: 50
|
||||||
|
crunchedCompression: 0
|
||||||
|
allowsAlphaSplitting: 0
|
||||||
|
overridden: 0
|
||||||
|
androidETC2FallbackOverride: 0
|
||||||
|
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||||
|
spriteSheet:
|
||||||
|
serializedVersion: 2
|
||||||
|
sprites: []
|
||||||
|
outline: []
|
||||||
|
physicsShape: []
|
||||||
|
bones: []
|
||||||
|
spriteID:
|
||||||
|
internalID: 0
|
||||||
|
vertices: []
|
||||||
|
indices:
|
||||||
|
edges: []
|
||||||
|
weights: []
|
||||||
|
secondaryTextures: []
|
||||||
|
nameFileIdTable: {}
|
||||||
|
spritePackingTag:
|
||||||
|
pSDRemoveMatte: 0
|
||||||
|
pSDShowRemoveMatteOption: 0
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,100 @@
|
||||||
|
# Guru Framework
|
||||||
|
|
||||||
|
|
||||||
|
**Version 2.1.0**
|
||||||
|
|
||||||
|
- 插件整体调整了文件结构,细分了相关模块的路径,添加了对应的 `asmdef` 文件
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
**Version 2.0.3**
|
||||||
|
|
||||||
|
- [升级须知](#notice)
|
||||||
|
|
||||||
|
|
||||||
|
## 依赖库
|
||||||
|
|
||||||
|
### Firebase
|
||||||
|
- 整体升级为 10.1.1
|
||||||
|
|
||||||
|
### AppLovin Max
|
||||||
|
- 整体升级为 11.11.3
|
||||||
|
- 详细的广告Adapter版本, [详见这里](https://docs.google.com/spreadsheets/d/161UnDimGerqetIYNiMCfUBmJ7qozht8z1baxnxRdCnI)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 子模块
|
||||||
|
|
||||||
|
### GuruCore
|
||||||
|
GuruSDK 核心逻辑类
|
||||||
|
|
||||||
|
### GuruAds
|
||||||
|
GuruSDK 封装了广告服务相关的接口
|
||||||
|
- 新增了 Moloco 和 Pubmatic 两个渠道
|
||||||
|
- 新增了ATTManager 用于管理ATT弹出和相关事件统计
|
||||||
|
|
||||||
|
### GuruAnalytics
|
||||||
|
Guru自打点统计模块
|
||||||
|
- 更新了用户时长统计修复, 修复 Worker 启动报错的问题
|
||||||
|
|
||||||
|
### GuruConsent
|
||||||
|
使用 Funding Choices 作为数据的启动广告隐私权限引导模块
|
||||||
|
|
||||||
|
### GuruBuildTool
|
||||||
|
构建工具合集
|
||||||
|
- 更新了SKADNetwork 数据
|
||||||
|
- 更新 Xcode15 构建支持
|
||||||
|
|
||||||
|
### GuruIAP
|
||||||
|
支付服务相关接口, 底层使用的是 Unity 的 In-App-Purchase 插件
|
||||||
|
|
||||||
|
### GuruEntry
|
||||||
|
游戏入口模块
|
||||||
|
|
||||||
|
|
||||||
|
### GuruL10n
|
||||||
|
Guru的翻译模块, 内部衔接了 I2 Localization 插件, 外部衔接中台自动翻译接口
|
||||||
|
|
||||||
|
|
||||||
|
### GuruRating
|
||||||
|
游戏评分模块
|
||||||
|
|
||||||
|
### Keywords
|
||||||
|
Max Keywords 上报模块
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<span id="notice"></span>
|
||||||
|
## 升级须知
|
||||||
|
|
||||||
|
### Android
|
||||||
|
|
||||||
|
- 需要在 `BuildSettings/Player Settings.../Publishing Settings` 内, 开启使用 `Custom Main Gradle Template`
|
||||||
|
- 可以直接使用中台提供的 `launcherTemplate.gradle` 文件
|
||||||
|
- 或者在新生成的 `Assets/Plugins/Android/launcherTemplate.gradle` 内添加如下代码:
|
||||||
|
```groove
|
||||||
|
android {
|
||||||
|
...
|
||||||
|
|
||||||
|
lintOptions {
|
||||||
|
abortOnError false
|
||||||
|
checkReleaseBuilds false // <---请添加此行代码
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 请将模版内的 **PACKAGING_OPTIONS** 替换为如下代码
|
||||||
|
packagingOptions {
|
||||||
|
exclude("META-INF/*.kotlin_module")
|
||||||
|
}
|
||||||
|
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Android 构建的最小 Target Version 为 21
|
||||||
|
|
||||||
|
### iOS
|
||||||
|
- 构建相关的升级已经提交至 BuildTools 内
|
||||||
|
- 其他问题持续收集中
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9d4fe0abfc67449296f3992f2b823b78
|
||||||
|
timeCreated: 1671010548
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f0c01eb685db4af99bbfa645d8307495
|
||||||
|
timeCreated: 1671499352
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 205f90aa56f64c759adf90d34e07fadf
|
||||||
|
timeCreated: 1702973536
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Firebase.RemoteConfig;
|
||||||
|
using Guru;
|
||||||
|
using Guru.LitJson;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Guru
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ABTEST 管理器
|
||||||
|
/// </summary>
|
||||||
|
public class ABTestManager : Singleton<ABTestManager>
|
||||||
|
{
|
||||||
|
|
||||||
|
private FirebaseRemoteConfig _remoteConfig;
|
||||||
|
|
||||||
|
private List<ABParamData> _params;
|
||||||
|
|
||||||
|
|
||||||
|
#region 初始化
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化
|
||||||
|
/// </summary>
|
||||||
|
public static void Init()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Instance.Setup();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Debug.LogError(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 安装服务
|
||||||
|
/// </summary>
|
||||||
|
private void Setup()
|
||||||
|
{
|
||||||
|
Debug.Log($"[AB] --- <color=#88ff00>ABTest Init</color>");
|
||||||
|
_params = new List<ABParamData>();
|
||||||
|
|
||||||
|
_remoteConfig = FirebaseRemoteConfig.DefaultInstance;
|
||||||
|
|
||||||
|
string strValue;
|
||||||
|
foreach (var key in _remoteConfig.Keys)
|
||||||
|
{
|
||||||
|
strValue = _remoteConfig.GetValue(key).StringValue;
|
||||||
|
AddParam(strValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------- ABTest -----------
|
||||||
|
// Debug.Log($"<color=orange> --- start parse test string --- </color>");
|
||||||
|
// var testStr = @"{""enabled"":true,""value"":2,""id"":""B"",""guru_ab_23100715"":""B""}";
|
||||||
|
// AddParam(testStr);
|
||||||
|
|
||||||
|
if (_params.Count > 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _params.Count; i++)
|
||||||
|
{
|
||||||
|
// 上报实验AB属性
|
||||||
|
GuruAnalytics.SetUserProperty(_params[i].id, _params[i].group);
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
Debug.Log($"[AB] --- Add AB Param <color=cyan>{_params[i].ToString()}</color>");
|
||||||
|
#else
|
||||||
|
Debug.Log($"[AB] --- Add AB Param {_params[i].ToString()}");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 添加AB参数
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 添加AB参数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
private void AddParam(string value)
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(value) && value.Contains("guru_ab_"))
|
||||||
|
{
|
||||||
|
_params.Add(ABParamData.Parse(value)); // 添加参数
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serializable]
|
||||||
|
internal class ABParamData
|
||||||
|
{
|
||||||
|
private const int PARAM_NAME_LENGTH = 23; // 从开始"ab_" 计算, 往后20个字符
|
||||||
|
|
||||||
|
public string id;
|
||||||
|
public string group;
|
||||||
|
public string value;
|
||||||
|
|
||||||
|
public static ABParamData Parse(string value)
|
||||||
|
{
|
||||||
|
Debug.Log($"--- ABParamData.Parse: {value}");
|
||||||
|
var p = new ABParamData();
|
||||||
|
p.value = value;
|
||||||
|
|
||||||
|
// 发现Guru AB测试标志位
|
||||||
|
var dict = JsonMapper.ToObject<Dictionary<string, object>>(value);
|
||||||
|
if (null != dict)
|
||||||
|
{
|
||||||
|
foreach (var k in dict.Keys)
|
||||||
|
{
|
||||||
|
if (k.StartsWith("guru_ab"))
|
||||||
|
{
|
||||||
|
p.id = GetItemKey(k);
|
||||||
|
p.group = dict[k].ToString();
|
||||||
|
// Debug.Log($"[AB] add property {k}: {dict[k]}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetItemKey(string raw)
|
||||||
|
{
|
||||||
|
var h = raw.Replace("guru_", "");
|
||||||
|
var key = h.Substring(0, Mathf.Min(PARAM_NAME_LENGTH, h.Length)); // 最大长度23
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return $"{id} : {group}";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9da3c10a9dde46499798d47cdd48fd70
|
||||||
|
timeCreated: 1696650618
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
|
||||||
|
# ABTest 中台接口
|
||||||
|
|
||||||
|
- [相关实现技术文档链接](https://docs.google.com/document/d/1AG9PLq-dI0plIati2qgVpuD5QchNO2P8phY1GWr8SeQ/edit?pli=1#heading=h.906ruqltpzqz)
|
||||||
|
|
||||||
|
|
||||||
|
## 云控配置
|
||||||
|
- 需要根据每个测试来配置对应的属性和ID
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 参数 KEY 构成
|
||||||
|
guru_ab_ + 231009 + 01
|
||||||
|
^ ^ ^
|
||||||
|
固定前缀 年月日 实验序号
|
||||||
|
|
||||||
|
// 参数值构成
|
||||||
|
"A" 或 "B"
|
||||||
|
"C" 或 "D"
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
- 属性字段是追加在云控参数体内的
|
||||||
|
```json
|
||||||
|
// 云控的json 参数数据体
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"value": "test",
|
||||||
|
|
||||||
|
"guru_ab_23100901": "A", // 第一个实验的分组
|
||||||
|
"guru_ab_23100902": "C" // 第二个实验的分组
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## 结果验证
|
||||||
|
|
||||||
|
- 对于已经切换了自打点的项目, 需要BI组配合项目组, 抽取每组实验用户的数据, 可形成有效报告
|
||||||
|
- 项目接入后在启动时不会卡顿, 项目不会产生异常和崩溃
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a1e7ab6ebd2348d99444732ad4f6422b
|
||||||
|
timeCreated: 1697167744
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "Guru.Runtime",
|
||||||
|
"rootNamespace": "Guru",
|
||||||
|
"references": [
|
||||||
|
"Adjust",
|
||||||
|
"MaxSdk",
|
||||||
|
"MaxSdk.Scripts",
|
||||||
|
"Amazon",
|
||||||
|
"OpenWrapSDK",
|
||||||
|
"UniWebView-CSharp",
|
||||||
|
"UnityEngine.Purchasing",
|
||||||
|
"UnityEngine.Purchasing.Apple",
|
||||||
|
"UnityEngine.Purchasing.Stores",
|
||||||
|
"UnityEngine.Purchasing.Security",
|
||||||
|
"UnityEngine.Purchasing.SecurityCore",
|
||||||
|
"UnityEngine.Purchasing.SecurityStub",
|
||||||
|
"Google.Play.Review",
|
||||||
|
"Google.Play.Common",
|
||||||
|
"Guru.LitJson"
|
||||||
|
],
|
||||||
|
"includePlatforms": [],
|
||||||
|
"excludePlatforms": [],
|
||||||
|
"allowUnsafeCode": false,
|
||||||
|
"overrideReferences": false,
|
||||||
|
"precompiledReferences": [],
|
||||||
|
"autoReferenced": true,
|
||||||
|
"defineConstraints": [],
|
||||||
|
"versionDefines": [],
|
||||||
|
"noEngineReferences": false
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: e241d247f550f427da05939864204192
|
||||||
|
AssemblyDefinitionImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a66a5db145b064b588d9f4def3d72b12
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 29a493ec2a4443b7a707065c40e88459
|
||||||
|
timeCreated: 1694079394
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
#if UNITY_IOS
|
||||||
|
|
||||||
|
namespace Guru
|
||||||
|
{
|
||||||
|
using UnityEngine;
|
||||||
|
using System;
|
||||||
|
using Unity.Advertisement.IosSupport;
|
||||||
|
public class ATTManager
|
||||||
|
{
|
||||||
|
public const string Version = "1.0.0";
|
||||||
|
|
||||||
|
public const string ATT_STATUS_AUTHORIZED = "authorized";
|
||||||
|
public const string ATT_STATUS_DENIED = "denied";
|
||||||
|
public const string ATT_STATUS_RESTRICTED = "restricted";
|
||||||
|
public const string ATT_STATUS_NOT_DETERMINED = "notDetermined";
|
||||||
|
public const string ATT_STATUS_NOT_APPLICABLE = "notApplicable";
|
||||||
|
public const int ATT_REQUIRED_MIN_OS = 14;
|
||||||
|
|
||||||
|
//---------- 引导类型 ------------
|
||||||
|
public const string GUIDE_TYPE_ADMOB = "admob";
|
||||||
|
public const string GUIDE_TYPE_CUSTOM = "custom";
|
||||||
|
public const string GUIDE_TYPE_MAX = "max";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取状态
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetStatus()
|
||||||
|
{
|
||||||
|
if (!IsATTSupported()) return ATT_STATUS_NOT_APPLICABLE;
|
||||||
|
var status = GetStatusString(ATTrackingStatusBinding.GetAuthorizationTrackingStatus());
|
||||||
|
if(!string.IsNullOrEmpty(status)) return status;
|
||||||
|
return ATT_STATUS_NOT_APPLICABLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 转字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="status"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetStatusString(ATTrackingStatusBinding.AuthorizationTrackingStatus status)
|
||||||
|
{
|
||||||
|
switch (status)
|
||||||
|
{
|
||||||
|
case ATTrackingStatusBinding.AuthorizationTrackingStatus.NOT_DETERMINED:
|
||||||
|
return ATT_STATUS_NOT_DETERMINED;
|
||||||
|
case ATTrackingStatusBinding.AuthorizationTrackingStatus.AUTHORIZED:
|
||||||
|
return ATT_STATUS_AUTHORIZED;
|
||||||
|
case ATTrackingStatusBinding.AuthorizationTrackingStatus.DENIED:
|
||||||
|
return ATT_STATUS_DENIED;
|
||||||
|
case ATTrackingStatusBinding.AuthorizationTrackingStatus.RESTRICTED:
|
||||||
|
return ATT_STATUS_RESTRICTED;
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 状态码转字符串
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public static string GetStatusString(int value)
|
||||||
|
=> GetStatusString((ATTrackingStatusBinding.AuthorizationTrackingStatus)value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否支持ATT
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
private static bool IsATTSupported()
|
||||||
|
{
|
||||||
|
string version = UnityEngine.iOS.Device.systemVersion;
|
||||||
|
|
||||||
|
Debug.Log($"[ATT] --- Get iOS system version: {version}");
|
||||||
|
|
||||||
|
string tmp = version;
|
||||||
|
if (version.Contains(" "))
|
||||||
|
{
|
||||||
|
var a1 = version.Split(' ');
|
||||||
|
tmp = a1[a1.Length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
string num = tmp;
|
||||||
|
if (tmp.Contains("."))
|
||||||
|
{
|
||||||
|
num = tmp.Split('.')[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (int.TryParse(num, out var ver))
|
||||||
|
{
|
||||||
|
if (ver >= ATT_REQUIRED_MIN_OS) return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求系统弹窗
|
||||||
|
/// </summary>
|
||||||
|
public static void RequestATTDailog(Action<string> callback = null)
|
||||||
|
{
|
||||||
|
if (!IsATTSupported())
|
||||||
|
{
|
||||||
|
callback?.Invoke(ATT_STATUS_NOT_APPLICABLE); // 不支持
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ATTrackingStatusBinding.RequestAuthorizationTracking(status =>{
|
||||||
|
callback?.Invoke(GetStatusString(status));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 启动时检查状态
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="callback"></param>
|
||||||
|
public static void CheckStatus(Action<string> callback = null)
|
||||||
|
{
|
||||||
|
if (!IsATTSupported())
|
||||||
|
{
|
||||||
|
callback?.Invoke(ATT_STATUS_NOT_APPLICABLE); // 不支持
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
callback?.Invoke(GetStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c64a8943316547c88c9ace04dd5e3d05
|
||||||
|
timeCreated: 1694079403
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 367cb7699f154c8894d8c3ca85c1fe94
|
||||||
|
timeCreated: 1681280112
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9bb6c50e89674152823d45b10ce535a2
|
||||||
|
timeCreated: 1687238820
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
-keep class com.amazon.device.ads.** { *; }
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 8ce2781a43d0405d8481e54e2a8e5789
|
||||||
|
timeCreated: 1687238837
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f0fcdcfefdeb4214bb75f2cc6a8356f8
|
||||||
|
timeCreated: 1681280138
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f5e96341271f7477280791fe0c9a4015
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
# GuruAds 扩展 Amazon安装说明
|
||||||
|
|
||||||
|
## Version 1.0.0
|
||||||
|
|
||||||
|
## 更新内容:
|
||||||
|
- 添加 Amazon 广告适配兼容
|
||||||
|
|
||||||
|
|
||||||
|
## Amazon 使用说明
|
||||||
|
|
||||||
|
### 介绍
|
||||||
|
- 本扩展基于 GuruCore.Ads.ADService `1.2.0` 开发,
|
||||||
|
> 低于该版本的Guru框架请先升级至[ Guru 1.8.0 ](https://raw.githubusercontent.com/castbox/unity_gurucore/main/gurucore_1.8.0.unitypackage)
|
||||||
|
- 导入本扩展后, 需要先手动安装 APS 插件
|
||||||
|
> unitypackage 可从以下路径解压缩后自行安装:
|
||||||
|
>
|
||||||
|
> Assets/Guru/GuruAds/Editor/Pcakge/unity_aps_1.4.3.zip
|
||||||
|
>
|
||||||
|
- 安装完毕后,请打开 GuruSettings 设置对应的 Amazon 广告配置参数 ,详见 `Amazon Setting`
|
||||||
|
|
||||||
|
### 代码调用
|
||||||
|
|
||||||
|
- 请直接使用 `ADServiceAPS` 来完成所有广告生命周期相关的功能, 它继承了 `IADService`
|
||||||
|
- 实际上 `ADServiceAPS` 继承自 `ADService`。 具备基础功能的同时实现了Amazon的广告请求逻辑。
|
||||||
|
- 所有的广告申请, 加载和播放结果的回调同之前是一致的。
|
||||||
|
```C#
|
||||||
|
|
||||||
|
// 定义广告实例
|
||||||
|
var service = ADService.Instance;
|
||||||
|
|
||||||
|
// 启动广告服务
|
||||||
|
bool isDebugMode = true;
|
||||||
|
service.StartService(() =>
|
||||||
|
{
|
||||||
|
//TODO 执行广告初始化成功后的逻辑
|
||||||
|
}, isDebugMode);
|
||||||
|
|
||||||
|
// 显示 Banner
|
||||||
|
service.ShowBanner();
|
||||||
|
|
||||||
|
// 显示 IV
|
||||||
|
service.ShowInterstitialAD("level_end");
|
||||||
|
|
||||||
|
// 显示 RV
|
||||||
|
service.ShowRewardedAD("add_free_coin", () =>
|
||||||
|
{
|
||||||
|
UserData.Coin += 10;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 6f5bfcb26e384536ab6e134d2cad1c18
|
||||||
|
timeCreated: 1680158911
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 36da1246d638491bb601be91b8f18091
|
||||||
|
timeCreated: 1681280144
|
||||||
|
|
@ -0,0 +1,242 @@
|
||||||
|
/**********************************************
|
||||||
|
* Amazon 广告渠道
|
||||||
|
**********************************************/
|
||||||
|
namespace Guru
|
||||||
|
{
|
||||||
|
using AmazonAds;
|
||||||
|
using UnityEngine;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
internal struct AdSize
|
||||||
|
{
|
||||||
|
public int width;
|
||||||
|
public int height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 广告渠道: Amazon
|
||||||
|
/// </summary>
|
||||||
|
public class AdChanelAmazon: IAdChannel, IAsyncRequestChannel
|
||||||
|
{
|
||||||
|
#region 广告尺寸
|
||||||
|
|
||||||
|
// Banner 尺寸参数
|
||||||
|
private static AdSize BannerSize = new AdSize() { width = 320, height = 50 };
|
||||||
|
// Video 尺寸参数
|
||||||
|
private static AdSize VideoSize = new AdSize() { width = 320, height = 480 };
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SLOT IDS
|
||||||
|
|
||||||
|
// --------------- 获取各种SlotID --------------------
|
||||||
|
private string AmazonBannerSlotID => GuruSettings.Instance.AmazonSetting.BannerSlotID;
|
||||||
|
private string AmazonInterVideoSlotID => GuruSettings.Instance.AmazonSetting.InterSlotID;
|
||||||
|
private string AmazonRewardSlotID => GuruSettings.Instance.AmazonSetting.RewardSlotID;
|
||||||
|
private string AmazonAppID => GuruSettings.Instance.AmazonSetting.AppID;
|
||||||
|
|
||||||
|
// ---------------- Max 广告位ID --------------------
|
||||||
|
private string MaxBannerSlotID => GuruSettings.Instance.ADSetting.GetBannerID();
|
||||||
|
private string MaxIVSlotID => GuruSettings.Instance.ADSetting.GetInterstitialID();
|
||||||
|
private string MaxRVSlotID => GuruSettings.Instance.ADSetting.GetRewardedVideoID();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Async 回调
|
||||||
|
|
||||||
|
public Action<bool, bool> OnBannerRequestOver { get; set; }
|
||||||
|
public Action<bool, bool> OnInterstitialRequestOver { get; set; }
|
||||||
|
public Action<bool, bool> OnRewardRequestOver { get; set; }
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 属性
|
||||||
|
|
||||||
|
public static readonly string ChanelName = "Amazon";
|
||||||
|
public string Name => ChanelName;
|
||||||
|
|
||||||
|
public Action<string> OnRequestOver { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前平台是否可用
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEnabled
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
// return GuruSettings.Instance.AmazonSetting.Enable;
|
||||||
|
return true; // 常驻开启
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 初始化
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 初始化平台
|
||||||
|
/// </summary>
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
Debug.Log($"<color=orange>=== Amazon will not init on Editor ===</color>");
|
||||||
|
#endif
|
||||||
|
if (!IsEnabled)
|
||||||
|
{
|
||||||
|
Debug.Log($"[Ads] --- Amazon is not enabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化Amazon
|
||||||
|
Amazon.Initialize (AmazonAppID);
|
||||||
|
Amazon.SetAdNetworkInfo(new AdNetworkInfo(DTBAdNetwork.MAX));
|
||||||
|
#if UNITY_EDITOR || DEBUG
|
||||||
|
Amazon.EnableTesting (true); // Make sure to take this off when going live.
|
||||||
|
#else
|
||||||
|
Amazon.EnableLogging (false);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if UNITY_IOS
|
||||||
|
Amazon.SetAPSPublisherExtendedIdFeatureEnabled(true);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Banner
|
||||||
|
|
||||||
|
private APSBannerAdRequest bannerAdRequest;
|
||||||
|
private bool _firstLoadBanner = true;
|
||||||
|
public void LoadBannerAD()
|
||||||
|
{
|
||||||
|
if (!IsEnabled) return;
|
||||||
|
|
||||||
|
Debug.Log($"--- Amazon banner start load ---");
|
||||||
|
if (bannerAdRequest != null) bannerAdRequest.DestroyFetchManager();
|
||||||
|
bannerAdRequest = new APSBannerAdRequest(BannerSize.width, BannerSize.height, AmazonBannerSlotID);
|
||||||
|
bannerAdRequest.onSuccess += (adResponse) =>
|
||||||
|
{
|
||||||
|
Debug.Log($"--- Amazon Banner Load Success ---");
|
||||||
|
MaxSdk.SetBannerLocalExtraParameter(MaxBannerSlotID,
|
||||||
|
"amazon_ad_response",
|
||||||
|
adResponse.GetResponse());
|
||||||
|
OnBannerRequestOver?.Invoke(true, _firstLoadBanner);
|
||||||
|
_firstLoadBanner = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
bannerAdRequest.onFailedWithError += (adError) =>
|
||||||
|
{
|
||||||
|
Debug.Log($"--- Amazon Banner Load Fail: [{adError.GetCode()}] {adError.GetMessage()}");
|
||||||
|
MaxSdk.SetBannerLocalExtraParameter(MaxBannerSlotID,
|
||||||
|
"amazon_ad_error",
|
||||||
|
adError.GetAdError());
|
||||||
|
OnBannerRequestOver?.Invoke(false, _firstLoadBanner);
|
||||||
|
};
|
||||||
|
|
||||||
|
bannerAdRequest.LoadAd();
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
OnBannerRequestOver?.Invoke(true, false);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Intersitial
|
||||||
|
|
||||||
|
private bool _isFirstLoadInter = true;
|
||||||
|
private APSVideoAdRequest interstitialAdRequest;
|
||||||
|
|
||||||
|
public void LoadInterstitialAD()
|
||||||
|
{
|
||||||
|
if (!IsEnabled) return;
|
||||||
|
|
||||||
|
// 首次启动注入渠道参数
|
||||||
|
if (_isFirstLoadInter)
|
||||||
|
{
|
||||||
|
Debug.Log($"--- Amazon INTER start load ---");
|
||||||
|
interstitialAdRequest = new APSVideoAdRequest(VideoSize.width, VideoSize.height, AmazonInterVideoSlotID);
|
||||||
|
interstitialAdRequest.onSuccess += (adResponse) =>
|
||||||
|
{
|
||||||
|
Debug.Log($"--- Amazon INTER Load Success ---");
|
||||||
|
MaxSdk.SetInterstitialLocalExtraParameter(MaxIVSlotID,
|
||||||
|
"amazon_ad_response",
|
||||||
|
adResponse.GetResponse());
|
||||||
|
OnInterstitialRequestOver?.Invoke(true, true);
|
||||||
|
_isFirstLoadInter = false;
|
||||||
|
};
|
||||||
|
interstitialAdRequest.onFailedWithError += (adError) =>
|
||||||
|
{
|
||||||
|
Debug.Log($"--- Amazon INTER Load Fail: [{adError.GetCode()}] {adError.GetMessage()}");
|
||||||
|
MaxSdk.SetInterstitialLocalExtraParameter(MaxIVSlotID,
|
||||||
|
"amazon_ad_error",
|
||||||
|
adError.GetAdError());
|
||||||
|
OnInterstitialRequestOver?.Invoke(false, true); // 不成功则一直请求Amazon广告
|
||||||
|
};
|
||||||
|
interstitialAdRequest.LoadAd();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnInterstitialRequestOver?.Invoke(true, false); // 走默认的广告加载逻辑
|
||||||
|
}
|
||||||
|
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
OnInterstitialRequestOver?.Invoke(true, false);
|
||||||
|
_isFirstLoadInter = false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Reward
|
||||||
|
|
||||||
|
private bool _firstLoadRewward = true;
|
||||||
|
private APSVideoAdRequest rewardedVideoAdRequest;
|
||||||
|
|
||||||
|
public void LoadRewardAD()
|
||||||
|
{
|
||||||
|
if (!IsEnabled) return;
|
||||||
|
|
||||||
|
if (_firstLoadRewward)
|
||||||
|
{
|
||||||
|
Debug.Log($"--- Amazon Reward start load ---");
|
||||||
|
rewardedVideoAdRequest = new APSVideoAdRequest(VideoSize.width, VideoSize.height, AmazonRewardSlotID);
|
||||||
|
rewardedVideoAdRequest.onSuccess += (adResponse) =>
|
||||||
|
{
|
||||||
|
Debug.Log($"--- Amazon Reward Load Success ---");
|
||||||
|
MaxSdk.SetRewardedAdLocalExtraParameter(MaxRVSlotID,
|
||||||
|
"amazon_ad_response",
|
||||||
|
adResponse.GetResponse());
|
||||||
|
OnRewardRequestOver?.Invoke(true, true);
|
||||||
|
_firstLoadRewward = false;
|
||||||
|
};
|
||||||
|
rewardedVideoAdRequest.onFailedWithError += (adError) =>
|
||||||
|
{
|
||||||
|
Debug.Log($"--- Amazon Reward Load Fail: [{adError.GetCode()}] {adError.GetMessage()}");
|
||||||
|
MaxSdk.SetRewardedAdLocalExtraParameter(MaxRVSlotID,
|
||||||
|
"amazon_ad_error",
|
||||||
|
adError.GetAdError());
|
||||||
|
OnRewardRequestOver?.Invoke(false, true); // 不成功则一直请求Amazon广告
|
||||||
|
};
|
||||||
|
rewardedVideoAdRequest.LoadAd();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnRewardRequestOver?.Invoke(true, false); // 走默认的广告加载逻辑
|
||||||
|
}
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
OnRewardRequestOver?.Invoke(true, false);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 9c01d5cecc9140c8a4a9e98a983299a2
|
||||||
|
timeCreated: 1681280193
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
namespace Guru
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public partial class GuruSettings
|
||||||
|
{
|
||||||
|
[Header("Amazon 广告配置")]
|
||||||
|
public AmazonSetting AmazonSetting;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Amazon广告配置
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class AmazonSetting
|
||||||
|
{
|
||||||
|
|
||||||
|
[SerializeField] public bool Enable;
|
||||||
|
[SerializeField] private AmazonPlatformSetting Android;
|
||||||
|
[SerializeField] private AmazonPlatformSetting iOS;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取AppID
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public AmazonPlatformSetting GetPlatform()
|
||||||
|
{
|
||||||
|
#if UNITY_IOS
|
||||||
|
return iOS;
|
||||||
|
#else
|
||||||
|
return Android;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public string AppID => GetPlatform().appID;
|
||||||
|
public string BannerSlotID => GetPlatform().bannerSlotID;
|
||||||
|
public string InterSlotID => GetPlatform().interSlotID;
|
||||||
|
public string RewardSlotID => GetPlatform().rewardSlotID;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Amazon平台专属配置
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class AmazonPlatformSetting
|
||||||
|
{
|
||||||
|
// public string name; // 平台名称
|
||||||
|
public string appID; // AppID
|
||||||
|
public string bannerSlotID; // Banner ID
|
||||||
|
public string interSlotID; // Inter ID
|
||||||
|
public string rewardSlotID; // Reward ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 074cbc5efb0c40d4856e20904a9b434d
|
||||||
|
timeCreated: 1681281263
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: a44918376993443edbe32c4ae267ad11
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 23cd1489a0b9410ea9e6e2f410926351
|
||||||
|
timeCreated: 1689144991
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 31dd283a9317e403e9eb07eba271e9c7
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f369f6d7d0c6425d88a3b8ec6ec1b4fc
|
||||||
|
timeCreated: 1689145012
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Guru
|
||||||
|
{
|
||||||
|
public partial class GuruSettings
|
||||||
|
{
|
||||||
|
[Header("[ Moloco ] 测试广告配置")] [Tooltip("此配置只在测试阶段验证广告时使用")]
|
||||||
|
public MolocoSetting MolocoSetting;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moloco 广告配置
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class MolocoSetting
|
||||||
|
{
|
||||||
|
[SerializeField] public bool Enable;
|
||||||
|
[SerializeField] private MolocoPlatformSetting Android;
|
||||||
|
[SerializeField] private MolocoPlatformSetting iOS;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取AppID
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public MolocoPlatformSetting GetPlatform()
|
||||||
|
{
|
||||||
|
#if UNITY_IOS
|
||||||
|
return iOS;
|
||||||
|
#else
|
||||||
|
return Android;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BannerTestUnitID => GetPlatform().bannerTestUnitID;
|
||||||
|
public string InterTestUnitID => GetPlatform().interTestUnitID;
|
||||||
|
public string RewardTestUnitID => GetPlatform().rewardTestUnitID;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moloco 平台专属配置
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class MolocoPlatformSetting
|
||||||
|
{
|
||||||
|
// public string name; // 平台名称
|
||||||
|
public string bannerTestUnitID; // Banner ID
|
||||||
|
public string interTestUnitID; // Inter ID
|
||||||
|
public string rewardTestUnitID; // Reward ID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 73843b3931f04824be84b4adcefdbb94
|
||||||
|
timeCreated: 1689145037
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace Guru
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Moloco 测试接口
|
||||||
|
/// </summary>
|
||||||
|
public class MolocoTestAPI
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
#region 初始化
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Banner
|
||||||
|
|
||||||
|
private static string _testBannerId;
|
||||||
|
private static bool _isLoadingBanner = false;
|
||||||
|
/// <summary>
|
||||||
|
/// 请求测试广告Ba
|
||||||
|
/// </summary>
|
||||||
|
public static void LoadDebugBanner(string bannerId)
|
||||||
|
{
|
||||||
|
if (_isLoadingBanner) return;
|
||||||
|
_isLoadingBanner = true;
|
||||||
|
_testBannerId = bannerId;
|
||||||
|
AddBannerCallBacks();
|
||||||
|
MaxSdk.CreateBanner(_testBannerId, MaxSdkBase.BannerPosition.BottomCenter);
|
||||||
|
ShowToast($"Load Banner: {_testBannerId}");
|
||||||
|
}
|
||||||
|
private static void AddBannerCallBacks()
|
||||||
|
{
|
||||||
|
MaxSdkCallbacks.Banner.OnAdLoadedEvent += OnDebugBannerLoadSuccess;
|
||||||
|
MaxSdkCallbacks.Banner.OnAdLoadFailedEvent += OnDebugBannerLoadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveBannerCallBacks()
|
||||||
|
{
|
||||||
|
MaxSdkCallbacks.Banner.OnAdLoadedEvent -= OnDebugBannerLoadSuccess;
|
||||||
|
MaxSdkCallbacks.Banner.OnAdLoadFailedEvent -= OnDebugBannerLoadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDebugBannerLoadSuccess(string adUnitId, MaxSdkBase.AdInfo adInfo)
|
||||||
|
{
|
||||||
|
if (adUnitId == _testBannerId)
|
||||||
|
{
|
||||||
|
RemoveBannerCallBacks();
|
||||||
|
Debug.Log($"[PM] Load Banner success => Revenue: {adInfo.Revenue}");
|
||||||
|
MaxSdk.ShowBanner(adUnitId);
|
||||||
|
_isLoadingBanner = false;
|
||||||
|
|
||||||
|
ShowToast($"Banner Loaded: {_testBannerId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDebugBannerLoadFailed(string adUnitId, MaxSdkBase.ErrorInfo errorInfo)
|
||||||
|
{
|
||||||
|
if (adUnitId == _testBannerId)
|
||||||
|
{
|
||||||
|
RemoveBannerCallBacks();
|
||||||
|
Debug.Log($"[PM] Load Banner fail => Waterfall:{errorInfo.WaterfallInfo.Name}");
|
||||||
|
_isLoadingBanner = false;
|
||||||
|
|
||||||
|
ShowToast($"Banner Load fail: {_testBannerId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Interstitial
|
||||||
|
|
||||||
|
|
||||||
|
private static bool _isLoadingIV = false;
|
||||||
|
private static string _testIVId;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 请求测试广告IV
|
||||||
|
/// </summary>
|
||||||
|
public static void LoadDebugIV(string unitId)
|
||||||
|
{
|
||||||
|
if (_isLoadingIV) return;
|
||||||
|
_isLoadingIV = true;
|
||||||
|
_testIVId = unitId;
|
||||||
|
AddIvCallBacks();
|
||||||
|
MaxSdk.LoadInterstitial(_testIVId);
|
||||||
|
|
||||||
|
ShowToast($"Load Interstitial: {_testIVId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddIvCallBacks()
|
||||||
|
{
|
||||||
|
MaxSdkCallbacks.Interstitial.OnAdLoadedEvent += OnDebugIVLoadSuccess;
|
||||||
|
MaxSdkCallbacks.Interstitial.OnAdLoadFailedEvent += OnDebugIVLoadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveIvCallBacks()
|
||||||
|
{
|
||||||
|
MaxSdkCallbacks.Interstitial.OnAdLoadedEvent -= OnDebugIVLoadSuccess;
|
||||||
|
MaxSdkCallbacks.Interstitial.OnAdLoadFailedEvent -= OnDebugIVLoadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDebugIVLoadSuccess(string adUnitId, MaxSdkBase.AdInfo adInfo)
|
||||||
|
{
|
||||||
|
if (adUnitId == _testIVId)
|
||||||
|
{
|
||||||
|
RemoveIvCallBacks();
|
||||||
|
_isLoadingIV = false;
|
||||||
|
Debug.Log($"[PM] Load IV success => Revenue: {adInfo.Revenue}");
|
||||||
|
string placement = "pm_test_iv";
|
||||||
|
MaxSdk.ShowInterstitial(adUnitId, placement);
|
||||||
|
ShowToast($"Load IV Success: {_testIVId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDebugIVLoadFailed(string adUnitId, MaxSdkBase.ErrorInfo errorInfo)
|
||||||
|
{
|
||||||
|
if (adUnitId == _testIVId)
|
||||||
|
{
|
||||||
|
RemoveIvCallBacks();
|
||||||
|
_isLoadingIV = false;
|
||||||
|
Debug.Log($"[PM] Load IV fail => Waterfall:{errorInfo.WaterfallInfo.Name}");
|
||||||
|
ShowToast($"Load IV Fail: {_testIVId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Reward
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private static string _testRVId;
|
||||||
|
private static bool _isLoadingRV = false;
|
||||||
|
/// <summary>
|
||||||
|
/// 请求测试广告RV
|
||||||
|
/// </summary>
|
||||||
|
public static void LoadDebugRV(string unitId)
|
||||||
|
{
|
||||||
|
if (_isLoadingRV) return;
|
||||||
|
_isLoadingRV = true;
|
||||||
|
_testRVId = unitId;
|
||||||
|
AddRvCallBacks();
|
||||||
|
MaxSdk.LoadRewardedAd(_testRVId);
|
||||||
|
|
||||||
|
ShowToast($"Load RV: {_testRVId}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddRvCallBacks()
|
||||||
|
{
|
||||||
|
MaxSdkCallbacks.Rewarded.OnAdLoadedEvent += OnDebugRVLoadSuccess;
|
||||||
|
MaxSdkCallbacks.Rewarded.OnAdLoadFailedEvent += OnDebugRVLoadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveRvCallBacks()
|
||||||
|
{
|
||||||
|
MaxSdkCallbacks.Rewarded.OnAdLoadedEvent -= OnDebugRVLoadSuccess;
|
||||||
|
MaxSdkCallbacks.Rewarded.OnAdLoadFailedEvent -= OnDebugRVLoadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDebugRVLoadSuccess(string adUnitId, MaxSdkBase.AdInfo adInfo)
|
||||||
|
{
|
||||||
|
if (adUnitId == _testRVId)
|
||||||
|
{
|
||||||
|
RemoveRvCallBacks();
|
||||||
|
_isLoadingRV = false;
|
||||||
|
Debug.Log($"[PM] Load RV success => Revenue: {adInfo.Revenue}");
|
||||||
|
string placement = "pm_test_rv";
|
||||||
|
MaxSdk.ShowRewardedAd(adUnitId, placement);
|
||||||
|
ShowToast($"Load RV Success: {_testRVId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDebugRVLoadFailed(string adUnitId, MaxSdkBase.ErrorInfo errorInfo)
|
||||||
|
{
|
||||||
|
if (adUnitId == _testRVId)
|
||||||
|
{
|
||||||
|
RemoveRvCallBacks();
|
||||||
|
_isLoadingRV = false;
|
||||||
|
Debug.Log($"[PM] Load RV fail => Waterfall:{errorInfo.WaterfallInfo.Name}");
|
||||||
|
ShowToast($"Load RV Fail: {_testRVId}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Debug
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 显示Toast信息
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="msg"></param>
|
||||||
|
public static void ShowToast(string msg)
|
||||||
|
{
|
||||||
|
#if UNITY_ANDROID
|
||||||
|
// U3D2Android.ShowToast(msg);
|
||||||
|
#else
|
||||||
|
Debug.Log(msg);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 92c8ebc4f05c4729b6ef6a6bae5a34b7
|
||||||
|
timeCreated: 1689147226
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 476080445a424f538e3295b6d0c39c37
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 4f27758688594110b42e5d6cc9368f2b
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
-keep class com.pubmatic.sdk.** { *; }
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 337880599b034517bf8ff594099c5c1d
|
||||||
|
timeCreated: 1687158363
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: c6fc887e2efc448b85609ea99608acdd
|
||||||
|
timeCreated: 1681289567
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,7 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 3a5b29c0662724638bd2fb91e3a98972
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# PubMatic 广告插件
|
||||||
|
|
||||||
|
## Version 1.0.0
|
||||||
|
|
||||||
|
使用方法:
|
||||||
|
- 先解压缩Package内的zip文档
|
||||||
|
- 导入两个unitypackage文件
|
||||||
|
- 将 PubMatic 对应的的 AdChannelPubMatic.cs 文件导入项目
|
||||||
|
- 在 AdService 内注册AdChannelPubMatic. (需要升级 ADService )
|
||||||
|
- 在 PlayerSettings 内添加 `AD_PUBMATIC` 的宏
|
||||||
|
- 在 proguard-user.txt 内添加下面的代码:
|
||||||
|
```yaml
|
||||||
|
# PubMatic
|
||||||
|
-keep class com.pubmatic.** { *; }
|
||||||
|
```
|
||||||
|
|
||||||
|
广告申请和展示时按照正常的 MAX 广告申请接口调用
|
||||||
|
|
||||||
|
测试时, 需要使用专用的Debug接口直接拉起 PubMatic 测试广告
|
||||||
|
```csharp
|
||||||
|
|
||||||
|
Debug.Log($"--- 显示 PubMatic Banner ---");
|
||||||
|
AdChannelPubMatic.RequestDebugBanner();
|
||||||
|
|
||||||
|
Debug.Log($"--- 显示 PubMatic IV ---");
|
||||||
|
AdChannelPubMatic.LoadDebugIV();
|
||||||
|
|
||||||
|
Debug.Log($"--- 显示 PubMatic RV ---");
|
||||||
|
AdChannelPubMatic.RequestDebugRV();
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 40bcecbb98df437bae363dfc4956bed4
|
||||||
|
timeCreated: 1681451207
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 22a8f46f2a1cc4ebd830e5dd9ec2a898
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,370 @@
|
||||||
|
/**********************************************
|
||||||
|
* Pubmatic 广告渠道
|
||||||
|
**********************************************/
|
||||||
|
namespace Guru
|
||||||
|
{
|
||||||
|
using OpenWrapSDK;
|
||||||
|
using System;
|
||||||
|
using OpenWrapSDK.Mediation.AppLovinMAX;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public class AdChannelPubMatic: IAdChannel
|
||||||
|
{
|
||||||
|
#region 属性定义
|
||||||
|
|
||||||
|
public static readonly string ChanelName = "PubMatic";
|
||||||
|
public string Name => ChanelName;
|
||||||
|
|
||||||
|
public Action<string> OnRequestOver { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
// --------------- 获取各种SlotID --------------------
|
||||||
|
private static string PMBannerUnitID => GuruSettings.Instance.PubmaticSetting.BannerUnitID;
|
||||||
|
private static string PMInterUnitID => GuruSettings.Instance.PubmaticSetting.InterUnitID;
|
||||||
|
private static string PMRewardUnitID => GuruSettings.Instance.PubmaticSetting.RewardUnitID;
|
||||||
|
private static string PMStoreUrl => GuruSettings.Instance.PubmaticSetting.StoreUrl;
|
||||||
|
|
||||||
|
public static readonly int BidRequestTimeout = 5; // 请求超时 (秒)
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 当前平台是否可用
|
||||||
|
/// </summary>
|
||||||
|
public bool IsEnabled
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
// return GuruSettings.Instance.PubmaticSetting.Enable;
|
||||||
|
return true; // 常驻开启
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 初始化
|
||||||
|
|
||||||
|
/*
|
||||||
|
* You must set the App Store/Google Play Store storeURL of your app
|
||||||
|
* before it can request an ad using OpenWrap SDK.
|
||||||
|
* The storeURL is the URL where users can download your app from the App Store/Google Play Store.
|
||||||
|
*/
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
#if UNITY_EDITOR
|
||||||
|
Debug.Log($"<color=orange>=== PubMatic will not init on Editor ===</color>");
|
||||||
|
#endif
|
||||||
|
if (!IsEnabled)
|
||||||
|
{
|
||||||
|
Debug.Log($"[Ads] --- PubMatic is not enabled");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var appInfo = new POBApplicationInfo();
|
||||||
|
appInfo.StoreURL = new Uri(PMStoreUrl);
|
||||||
|
POBOpenWrapSDK.SetApplicationInfo(appInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region 基础参数设置
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置Banner参数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="adUnitId"></param>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
private static void SetBannerParams(string adUnitId, string key, object value)
|
||||||
|
{
|
||||||
|
#if UNITY_IOS
|
||||||
|
MaxSdk.SetBannerLocalExtraParameter(adUnitId,
|
||||||
|
key,
|
||||||
|
POBMAXUtil.GetIntPtr(adUnitId, value));
|
||||||
|
#elif UNITY_ANDROID
|
||||||
|
MaxSdk.SetBannerLocalExtraParameter(adUnitId,
|
||||||
|
key,
|
||||||
|
POBMAXUtil.GetAndroidJavaObject(value));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置IV参数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="adUnitId"></param>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
private static void SetIVParams(string adUnitId, string key, object value)
|
||||||
|
{
|
||||||
|
#if UNITY_IOS
|
||||||
|
MaxSdk.SetInterstitialLocalExtraParameter(adUnitId,
|
||||||
|
key,
|
||||||
|
POBMAXUtil.GetIntPtr(adUnitId, value));
|
||||||
|
#elif UNITY_ANDROID
|
||||||
|
MaxSdk.SetInterstitialLocalExtraParameter(adUnitId,
|
||||||
|
key,
|
||||||
|
POBMAXUtil.GetAndroidJavaObject(value));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置RV参数
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="adUnitId"></param>
|
||||||
|
/// <param name="key"></param>
|
||||||
|
/// <param name="value"></param>
|
||||||
|
private static void SetRVParams(string adUnitId, string key, object value)
|
||||||
|
{
|
||||||
|
#if UNITY_IOS
|
||||||
|
MaxSdk.SetRewardedAdLocalExtraParameter(adUnitId,
|
||||||
|
key,
|
||||||
|
POBMAXUtil.GetIntPtr(adUnitId, value));
|
||||||
|
#elif UNITY_ANDROID
|
||||||
|
MaxSdk.SetRewardedAdLocalExtraParameter(adUnitId,
|
||||||
|
key,
|
||||||
|
POBMAXUtil.GetAndroidJavaObject(value));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//---------------- 设置广告超时 -------------------------
|
||||||
|
|
||||||
|
private void SetBannerTimeout(string adUnitId, int timeout = 5)
|
||||||
|
{
|
||||||
|
SetBannerParams(adUnitId, POBMAXConstants.NetworkTimeoutKey, timeout);
|
||||||
|
}
|
||||||
|
private void SetIVTimeout(string adUnitId, int timeout = 5)
|
||||||
|
{
|
||||||
|
SetIVParams(adUnitId, POBMAXConstants.NetworkTimeoutKey, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetRVTimeout(string adUnitId, int timeout = 5)
|
||||||
|
{
|
||||||
|
SetRVParams(adUnitId, POBMAXConstants.NetworkTimeoutKey, timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------ 打开测试广告 ----------------------------
|
||||||
|
|
||||||
|
private void EnableBannerTestAds(string adUnitId)
|
||||||
|
{
|
||||||
|
SetBannerParams(adUnitId, POBMAXConstants.EnableTestModeKey, true);
|
||||||
|
}
|
||||||
|
private void EnableIVTestAds(string adUnitId)
|
||||||
|
{
|
||||||
|
SetIVParams(adUnitId, POBMAXConstants.EnableTestModeKey, true);
|
||||||
|
}
|
||||||
|
private void EnableRVTestAds(string adUnitId)
|
||||||
|
{
|
||||||
|
SetRVParams(adUnitId, POBMAXConstants.EnableTestModeKey, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------- 打开调试模式 ---------------------------
|
||||||
|
private static void EnableBannerDebugMode(string adUnitId)
|
||||||
|
{
|
||||||
|
SetBannerParams(adUnitId, POBMAXConstants.EnableDebugModeKey, true);
|
||||||
|
}
|
||||||
|
private static void EnableIVDebugMode(string adUnitId)
|
||||||
|
{
|
||||||
|
SetIVParams(adUnitId, POBMAXConstants.EnableDebugModeKey, true);
|
||||||
|
}
|
||||||
|
private static void EnableRVDebugMode(string adUnitId)
|
||||||
|
{
|
||||||
|
SetRVParams(adUnitId, POBMAXConstants.EnableDebugModeKey, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Banner
|
||||||
|
|
||||||
|
public void LoadBannerAD()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static bool _isLoadingBanner = false;
|
||||||
|
/// <summary>
|
||||||
|
/// 请求测试广告Ba
|
||||||
|
/// </summary>
|
||||||
|
public static void RequestDebugBanner()
|
||||||
|
{
|
||||||
|
if (_isLoadingBanner) return;
|
||||||
|
_isLoadingBanner = true;
|
||||||
|
EnableBannerDebugMode(PMBannerUnitID);
|
||||||
|
AddBannerCallBacks();
|
||||||
|
MaxSdk.CreateBanner(PMBannerUnitID, MaxSdkBase.BannerPosition.BottomCenter);
|
||||||
|
ShowToast($"Load Banner: {PMBannerUnitID}");
|
||||||
|
}
|
||||||
|
private static void AddBannerCallBacks()
|
||||||
|
{
|
||||||
|
MaxSdkCallbacks.Banner.OnAdLoadedEvent += OnDebugBannerLoadSuccess;
|
||||||
|
MaxSdkCallbacks.Banner.OnAdLoadFailedEvent += OnDebugBannerLoadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveBannerCallBacks()
|
||||||
|
{
|
||||||
|
MaxSdkCallbacks.Banner.OnAdLoadedEvent -= OnDebugBannerLoadSuccess;
|
||||||
|
MaxSdkCallbacks.Banner.OnAdLoadFailedEvent -= OnDebugBannerLoadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDebugBannerLoadSuccess(string adUnitId, MaxSdkBase.AdInfo adInfo)
|
||||||
|
{
|
||||||
|
if (adUnitId == PMBannerUnitID)
|
||||||
|
{
|
||||||
|
RemoveBannerCallBacks();
|
||||||
|
Debug.Log($"[PM] Load Banner success => Revenue: {adInfo.Revenue}");
|
||||||
|
MaxSdk.ShowBanner(adUnitId);
|
||||||
|
_isLoadingBanner = false;
|
||||||
|
|
||||||
|
ShowToast($"Banner Loaded: {PMBannerUnitID}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDebugBannerLoadFailed(string adUnitId, MaxSdkBase.ErrorInfo errorInfo)
|
||||||
|
{
|
||||||
|
if (adUnitId == PMBannerUnitID)
|
||||||
|
{
|
||||||
|
RemoveBannerCallBacks();
|
||||||
|
Debug.Log($"[PM] Load Banner fail => Waterfall:{errorInfo.WaterfallInfo.Name}");
|
||||||
|
_isLoadingBanner = false;
|
||||||
|
|
||||||
|
ShowToast($"Banner Load fail: {PMBannerUnitID}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Interstitial
|
||||||
|
|
||||||
|
public void LoadInterstitialAD()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static bool _isLoadingIV = false;
|
||||||
|
/// <summary>
|
||||||
|
/// 请求测试广告IV
|
||||||
|
/// </summary>
|
||||||
|
public static void LoadDebugIV()
|
||||||
|
{
|
||||||
|
if (_isLoadingIV) return;
|
||||||
|
_isLoadingIV = true;
|
||||||
|
EnableIVDebugMode(PMInterUnitID);
|
||||||
|
AddIvCallBacks();
|
||||||
|
MaxSdk.LoadInterstitial(PMInterUnitID);
|
||||||
|
|
||||||
|
ShowToast($"Load Interstitial: {PMInterUnitID}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddIvCallBacks()
|
||||||
|
{
|
||||||
|
MaxSdkCallbacks.Interstitial.OnAdLoadedEvent += OnDebugIVLoadSuccess;
|
||||||
|
MaxSdkCallbacks.Interstitial.OnAdLoadFailedEvent += OnDebugIVLoadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveIvCallBacks()
|
||||||
|
{
|
||||||
|
MaxSdkCallbacks.Interstitial.OnAdLoadedEvent -= OnDebugIVLoadSuccess;
|
||||||
|
MaxSdkCallbacks.Interstitial.OnAdLoadFailedEvent -= OnDebugIVLoadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDebugIVLoadSuccess(string adUnitId, MaxSdkBase.AdInfo adInfo)
|
||||||
|
{
|
||||||
|
if (adUnitId == PMInterUnitID)
|
||||||
|
{
|
||||||
|
RemoveIvCallBacks();
|
||||||
|
_isLoadingIV = false;
|
||||||
|
Debug.Log($"[PM] Load IV success => Revenue: {adInfo.Revenue}");
|
||||||
|
string placement = "pm_test_iv";
|
||||||
|
MaxSdk.ShowInterstitial(adUnitId, placement);
|
||||||
|
ShowToast($"Load IV Success: {PMInterUnitID}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDebugIVLoadFailed(string adUnitId, MaxSdkBase.ErrorInfo errorInfo)
|
||||||
|
{
|
||||||
|
if (adUnitId == PMInterUnitID)
|
||||||
|
{
|
||||||
|
RemoveIvCallBacks();
|
||||||
|
_isLoadingIV = false;
|
||||||
|
Debug.Log($"[PM] Load IV fail => Waterfall:{errorInfo.WaterfallInfo.Name}");
|
||||||
|
ShowToast($"Load IV Fail: {PMInterUnitID}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Reward
|
||||||
|
|
||||||
|
public void LoadRewardAD()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static bool _isLoadingRV = false;
|
||||||
|
/// <summary>
|
||||||
|
/// 请求测试广告RV
|
||||||
|
/// </summary>
|
||||||
|
public static void RequestDebugRV()
|
||||||
|
{
|
||||||
|
if (_isLoadingRV) return;
|
||||||
|
_isLoadingRV = true;
|
||||||
|
EnableIVDebugMode(PMRewardUnitID);
|
||||||
|
AddRvCallBacks();
|
||||||
|
MaxSdk.LoadRewardedAd(PMRewardUnitID);
|
||||||
|
|
||||||
|
ShowToast($"Load RV: {PMInterUnitID}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AddRvCallBacks()
|
||||||
|
{
|
||||||
|
MaxSdkCallbacks.Rewarded.OnAdLoadedEvent += OnDebugRVLoadSuccess;
|
||||||
|
MaxSdkCallbacks.Rewarded.OnAdLoadFailedEvent += OnDebugRVLoadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void RemoveRvCallBacks()
|
||||||
|
{
|
||||||
|
MaxSdkCallbacks.Rewarded.OnAdLoadedEvent -= OnDebugRVLoadSuccess;
|
||||||
|
MaxSdkCallbacks.Rewarded.OnAdLoadFailedEvent -= OnDebugRVLoadFailed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDebugRVLoadSuccess(string adUnitId, MaxSdkBase.AdInfo adInfo)
|
||||||
|
{
|
||||||
|
if (adUnitId == PMRewardUnitID)
|
||||||
|
{
|
||||||
|
RemoveRvCallBacks();
|
||||||
|
_isLoadingRV = false;
|
||||||
|
Debug.Log($"[PM] Load RV success => Revenue: {adInfo.Revenue}");
|
||||||
|
string placement = "pm_test_rv";
|
||||||
|
MaxSdk.ShowRewardedAd(adUnitId, placement);
|
||||||
|
ShowToast($"Load RV Success: {PMRewardUnitID}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void OnDebugRVLoadFailed(string adUnitId, MaxSdkBase.ErrorInfo errorInfo)
|
||||||
|
{
|
||||||
|
if (adUnitId == PMRewardUnitID)
|
||||||
|
{
|
||||||
|
RemoveRvCallBacks();
|
||||||
|
_isLoadingRV = false;
|
||||||
|
Debug.Log($"[PM] Load RV fail => Waterfall:{errorInfo.WaterfallInfo.Name}");
|
||||||
|
ShowToast($"Load RV Fail: {PMRewardUnitID}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Debug
|
||||||
|
|
||||||
|
private static void ShowToast(string msg)
|
||||||
|
{
|
||||||
|
#if UNITY_ANDROID
|
||||||
|
DeviceUtil.ShowToast(msg);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: d1e329abcf494bd19dfc930b04c0ba5e
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
//================================================
|
||||||
|
// Company Name: Castbox
|
||||||
|
// Project: Ball Sort Puzzle
|
||||||
|
// Author: HYZ
|
||||||
|
// CreateTime: 2023-04-12 21:06:02
|
||||||
|
// Version: 1.0.0
|
||||||
|
// Desc:
|
||||||
|
//================================================
|
||||||
|
|
||||||
|
using UnityEngine.Serialization;
|
||||||
|
|
||||||
|
namespace Guru
|
||||||
|
{
|
||||||
|
using System;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
public partial class GuruSettings
|
||||||
|
{
|
||||||
|
[Header("Pubmatic 广告配置")]
|
||||||
|
public PubmaticSetting PubmaticSetting;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Amazon广告配置
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class PubmaticSetting
|
||||||
|
{
|
||||||
|
[SerializeField] public bool Enable;
|
||||||
|
[SerializeField] private PubmaticPlatformSetting Android;
|
||||||
|
[SerializeField] private PubmaticPlatformSetting iOS;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 获取AppID
|
||||||
|
/// </summary>
|
||||||
|
/// <returns></returns>
|
||||||
|
public PubmaticPlatformSetting GetPlatform()
|
||||||
|
{
|
||||||
|
#if UNITY_IOS
|
||||||
|
return iOS;
|
||||||
|
#else
|
||||||
|
return Android;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
public string BannerUnitID => GetPlatform().bannerUnitID;
|
||||||
|
public string InterUnitID => GetPlatform().interUnitID;
|
||||||
|
public string RewardUnitID => GetPlatform().rewardUnitID;
|
||||||
|
public string StoreUrl => GetPlatform().storeUrl;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Amazon平台专属配置
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class PubmaticPlatformSetting
|
||||||
|
{
|
||||||
|
// public string name; // 平台名称
|
||||||
|
public string storeUrl; // 平台商店地址
|
||||||
|
public string bannerUnitID; // Banner ID
|
||||||
|
public string interUnitID; // Inter ID
|
||||||
|
public string rewardUnitID; // Reward ID
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: f7d6b41f0f5449be8ac0c4d1829bb330
|
||||||
|
MonoImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
serializedVersion: 2
|
||||||
|
defaultReferences: []
|
||||||
|
executionOrder: 0
|
||||||
|
icon: {instanceID: 0}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: cd2ad07dfc12b418db1bcd4b3a316329
|
||||||
|
folderAsset: yes
|
||||||
|
DefaultImporter:
|
||||||
|
externalObjects: {}
|
||||||
|
userData:
|
||||||
|
assetBundleName:
|
||||||
|
assetBundleVariant:
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: 729cc47bf56d436ba4c20380ca1e5e59
|
||||||
|
timeCreated: 1673860499
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
Sample Dependencies.xml:
|
||||||
|
https://github.com/googlesamples/unity-jar-resolver/blob/master/sample/Assets/ExternalDependencyManager/Editor/SampleDependencies.xml
|
||||||
|
-->
|
||||||
|
<dependencies>
|
||||||
|
<androidPackages>
|
||||||
|
<androidPackage spec="androidx.core:core:1.7.0" />
|
||||||
|
<!-- <androidPackage spec="androidx.appcompat:appcompat:1.5.1" />-->
|
||||||
|
<androidPackage spec="androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" />
|
||||||
|
<androidPackage spec="androidx.work:work-runtime:2.7.1" />
|
||||||
|
<androidPackage spec="androidx.work:work-runtime-ktx:2.7.1" />
|
||||||
|
<androidPackage spec="androidx.work:work-rxjava2:2.7.1" />
|
||||||
|
<androidPackage spec="androidx.lifecycle:lifecycle-process:2.4.0" />
|
||||||
|
<androidPackage spec="com.jakewharton.timber:timber:4.7.1" />
|
||||||
|
<androidPackage spec="com.google.code.gson:gson:2.8.5" />
|
||||||
|
<androidPackage spec="androidx.room:room-runtime:2.4.3" />
|
||||||
|
<androidPackage spec="androidx.room:room-rxjava2:2.4.3" />
|
||||||
|
<androidPackage spec="com.squareup.retrofit2:retrofit:2.7.1" />
|
||||||
|
<androidPackage spec="com.squareup.retrofit2:converter-gson:2.7.1" />
|
||||||
|
<androidPackage spec="com.squareup.retrofit2:adapter-rxjava2:2.7.1" />
|
||||||
|
<androidPackage spec="com.squareup.okhttp3:okhttp:4.9.3" />
|
||||||
|
|
||||||
|
<!-- <androidPackage spec="com.mapzen:on-the-road:0.8.1" />-->
|
||||||
|
<!-- <androidPackage spec="com.squareup.retrofit2:retrofit:2.7.1" />-->
|
||||||
|
</androidPackages>
|
||||||
|
<iosPods>
|
||||||
|
<iosPod name="GuruAnalyticsLib" version="0.2.16" bitcodeEnabled="false">
|
||||||
|
<sources>
|
||||||
|
<source>git@github.com:castbox/GuruSpecs.git</source>
|
||||||
|
</sources>
|
||||||
|
</iosPod>
|
||||||
|
<iosPod name="JJException" bitcodeEnabled="false" />
|
||||||
|
</iosPods>
|
||||||
|
</dependencies>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
fileFormatVersion: 2
|
||||||
|
guid: b9cafb1d82bb45598fdd566d9b8f6a80
|
||||||
|
timeCreated: 1674110869
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
-keep class com.guru.** { *; }
|
||||||
|
-keep class guru.core.** { *; }
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue