提交 a2260805 编写于 作者: T tanghai

1.更新运行指南

2.删除unity editor中的导表工具
上级 412b8f37
......@@ -10,6 +10,10 @@ b. 去net core 官网下载安装 .net5
##### 5.用vs2019打开ET/Client-Server.sln 编译(一定要编译,右键VS解决方案,全部编译)
##### 6.导表工具,编译完成后命令行进入Tools/ExcelExporter/Bin目录,执行dotnet ExcelExporter.dll
##### 7.导出协议工具,编译完成后进入Tools/Proto2CS/Bin目录,执行dotnet Proto2CS.dll
# 测试状态同步demo, 帧同步demo已经删除,需要的话请看ET4.0
##### 1. 打开Unity->tools菜单->命令行配置,重启server
......
fileFormatVersion: 2
guid: 8e91244514194e95aac3947b3ff064f4
timeCreated: 1505439981
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using MongoDB.Bson;
using NPOI.SS.UserModel;
using NPOI.XSSF.UserModel;
using UnityEditor;
using UnityEngine;
namespace ET
{
public struct CellInfo
{
public string Type;
public string Name;
public string Desc;
}
public class ExcelMD5Info
{
public Dictionary<string, string> fileMD5 = new Dictionary<string, string>();
public string Get(string fileName)
{
string md5 = "";
this.fileMD5.TryGetValue(fileName, out md5);
return md5;
}
public void Add(string fileName, string md5)
{
this.fileMD5[fileName] = md5;
}
}
public class ExcelExporterEditor: EditorWindow
{
[MenuItem("Tools/导出配置")]
private static void ShowWindow()
{
GetWindow(typeof (ExcelExporterEditor));
}
private const string ExcelPath = "../Excel";
private const string ServerConfigPath = "../Config/";
private bool isClient;
private ExcelMD5Info md5Info;
// Update is called once per frame
private void OnGUI()
{
try
{
const string clientPath = "./Assets/Bundles/Config";
if (GUILayout.Button("导出客户端配置"))
{
this.isClient = true;
ExportAll(clientPath);
ExportAllClass(@"./Assets/Model/Config", "using MongoDB.Bson.Serialization.Attributes;\n\nnamespace ET\n{\n");
Log.Info($"导出客户端配置完成!");
}
if (GUILayout.Button("导出服务端配置"))
{
this.isClient = false;
ExportAll(ServerConfigPath);
ExportAllClass(@"../Server/Model/Config", "using MongoDB.Bson.Serialization.Attributes;\n\nnamespace ET\n{\n");
Log.Info($"导出服务端配置完成!");
}
}
catch (Exception e)
{
Log.Error(e);
}
}
private void ExportAllClass(string exportDir, string csHead)
{
foreach (string filePath in Directory.GetFiles(ExcelPath))
{
if (Path.GetExtension(filePath) != ".xlsx")
{
continue;
}
if (Path.GetFileName(filePath).StartsWith("~"))
{
continue;
}
ExportClass(filePath, exportDir, csHead);
Log.Info($"生成{Path.GetFileName(filePath)}类");
}
AssetDatabase.Refresh();
}
private void ExportClass(string fileName, string exportDir, string csHead)
{
XSSFWorkbook xssfWorkbook;
using (FileStream file = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
xssfWorkbook = new XSSFWorkbook(file);
}
string protoName = Path.GetFileNameWithoutExtension(fileName);
string exportPath = Path.Combine(exportDir, $"{protoName}.cs");
using (FileStream txt = new FileStream(exportPath, FileMode.Create))
using (StreamWriter sw = new StreamWriter(txt))
{
StringBuilder sb = new StringBuilder();
ISheet sheet = xssfWorkbook.GetSheetAt(0);
sb.Append(csHead);
sb.Append($"\t[Config]\n");
sb.Append($"\tpublic partial class {protoName}Category : ACategory<{protoName}>\n");
sb.Append("\t{\n");
sb.Append($"\t\tpublic static {protoName}Category Instance;\n");
sb.Append($"\t\tpublic {protoName}Category()\n");
sb.Append("\t\t{\n");
sb.Append($"\t\t\tInstance = this;\n");
sb.Append("\t\t}\n");
sb.Append("\t}\n\n");
sb.Append($"\tpublic partial class {protoName}: IConfig\n");
sb.Append("\t{\n");
sb.Append("\t\t[BsonId]\n");
sb.Append("\t\tpublic long Id { get; set; }\n");
int cellCount = sheet.GetRow(3).LastCellNum;
for (int i = 2; i < cellCount; i++)
{
string fieldDesc = GetCellString(sheet, 2, i);
if (fieldDesc.StartsWith("#"))
{
continue;
}
// s开头表示这个字段是服务端专用
if (fieldDesc.StartsWith("s") && this.isClient)
{
continue;
}
string fieldName = GetCellString(sheet, 3, i);
if (fieldName == "Id" || fieldName == "_id")
{
continue;
}
string fieldType = GetCellString(sheet, 4, i);
if (fieldType == "" || fieldName == "")
{
continue;
}
sb.Append($"\t\tpublic {fieldType} {fieldName};\n");
}
sb.Append("\t}\n");
sb.Append("}\n");
sw.Write(sb.ToString());
}
}
private void ExportAll(string exportDir)
{
string md5File = Path.Combine(ExcelPath, "md5.txt");
if (!File.Exists(md5File))
{
this.md5Info = new ExcelMD5Info();
}
else
{
this.md5Info = MongoHelper.FromJson<ExcelMD5Info>(File.ReadAllText(md5File));
}
foreach (string filePath in Directory.GetFiles(ExcelPath))
{
if (Path.GetExtension(filePath) != ".xlsx")
{
continue;
}
if (Path.GetFileName(filePath).StartsWith("~"))
{
continue;
}
string fileName = Path.GetFileName(filePath);
string oldMD5 = this.md5Info.Get(fileName);
string md5 = MD5Helper.FileMD5(filePath);
this.md5Info.Add(fileName, md5);
// if (md5 == oldMD5)
// {
// continue;
// }
Export(filePath, exportDir);
}
File.WriteAllText(md5File, this.md5Info.ToJson());
Log.Info("所有表导表完成");
AssetDatabase.Refresh();
}
private void Export(string fileName, string exportDir)
{
XSSFWorkbook xssfWorkbook;
using (FileStream file = new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
xssfWorkbook = new XSSFWorkbook(file);
}
string protoName = Path.GetFileNameWithoutExtension(fileName);
Log.Info($"{protoName}导表开始");
string exportPath = Path.Combine(exportDir, $"{protoName}.txt");
using (FileStream txt = new FileStream(exportPath, FileMode.Create))
using (StreamWriter sw = new StreamWriter(txt))
{
sw.WriteLine("{");
for (int i = 0; i < xssfWorkbook.NumberOfSheets; ++i)
{
ISheet sheet = xssfWorkbook.GetSheetAt(i);
ExportSheet(sheet, sw);
}
sw.WriteLine("}");
}
Log.Info($"{protoName}导表完成");
}
private void ExportSheet(ISheet sheet, StreamWriter sw)
{
int cellCount = sheet.GetRow(3).LastCellNum;
CellInfo[] cellInfos = new CellInfo[cellCount];
for (int i = 2; i < cellCount; i++)
{
string fieldDesc = GetCellString(sheet, 2, i);
string fieldName = GetCellString(sheet, 3, i);
string fieldType = GetCellString(sheet, 4, i);
cellInfos[i] = new CellInfo() { Name = fieldName, Type = fieldType, Desc = fieldDesc };
}
for (int i = 5; i <= sheet.LastRowNum; ++i)
{
if (GetCellString(sheet, i, 2) == "")
{
continue;
}
StringBuilder sb = new StringBuilder();
IRow row = sheet.GetRow(i);
for (int j = 2; j < cellCount; ++j)
{
string desc = cellInfos[j].Desc.ToLower();
if (desc.StartsWith("#"))
{
continue;
}
// s开头表示这个字段是服务端专用
if (desc.StartsWith("s") && this.isClient)
{
continue;
}
// c开头表示这个字段是客户端专用
if (desc.StartsWith("c") && !this.isClient)
{
continue;
}
string fieldValue = GetCellString(row, j);
if (fieldValue == "")
{
continue;
}
if (j > 2)
{
sb.Append(",");
}
string fieldName = cellInfos[j].Name;
if (fieldName == "Id" || fieldName == "_id")
{
fieldName = "_id";
sb.Append("{\"" + fieldValue + "\":{{");
}
string fieldType = cellInfos[j].Type;
sb.Append($"\"{fieldName}\":{Convert(fieldType, fieldValue)}");
}
sb.Append("}},");
sw.WriteLine(sb.ToString());
}
}
private static string Convert(string type, string value)
{
switch (type)
{
case "int[]":
case "int32[]":
case "long[]":
return $"[{value}]";
case "string[]":
return $"[{value}]";
case "int":
case "int32":
case "int64":
case "long":
case "float":
case "double":
return value;
case "string":
return $"\"{value}\"";
default:
throw new Exception($"不支持此类型: {type}");
}
}
private static string GetCellString(ISheet sheet, int i, int j)
{
IRow _irow = sheet.GetRow(i);
if(_irow != null)
{
return GetCellString(_irow, j);
}
return "";
}
private static string GetCellString(IRow row, int i)
{
ICell _icell = row.GetCell(i);
if (_icell != null)
{
return GetCellString(_icell); ;
}
return "";
}
private static string GetCellString(ICell cell)
{
if (cell != null)
{
if(cell.CellType == CellType.Numeric || (cell.CellType == CellType.Formula && cell.CachedFormulaResultType == CellType.Numeric))
{
return cell.NumericCellValue.ToString();
}
else if (cell.CellType == CellType.String || (cell.CellType == CellType.Formula && cell.CachedFormulaResultType == CellType.String))
{
return cell.StringCellValue.ToString();
}
else if (cell.CellType == CellType.Boolean || (cell.CellType == CellType.Formula && cell.CachedFormulaResultType == CellType.Boolean))
{
return cell.BooleanCellValue.ToString();
}
else
{
return cell.ToString();
}
}
return "";
}
}
}
\ No newline at end of file
fileFormatVersion: 2
guid: 74e20be85f3f4863aa8418d99b14fbbe
timeCreated: 1505439974
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using UnityEditor;
namespace ET
{
[Flags]
public enum HeadFlag
{
None = 0,
Bson = 1,
Proto = 2,
}
public static class InnerProto2CS
{
private const string protoPath = "../Proto/";
private const string serverMessagePath = "../Server/Model/Module/Message/";
private static readonly char[] splitChars = { ' ', '\t' };
private static readonly List<OpcodeInfo> msgOpcode = new List<OpcodeInfo>();
public static void Proto2CS()
{
msgOpcode.Clear();
Proto2CS("ETModel", "InnerMessage.proto", serverMessagePath, "InnerOpcode", 1000);
GenerateOpcode("ETModel", "InnerOpcode", serverMessagePath);
AssetDatabase.Refresh();
}
public static void Proto2CS(string ns, string protoName, string outputPath, string opcodeClassName, int startOpcode)
{
msgOpcode.Clear();
string proto = Path.Combine(protoPath, protoName);
string csPath = Path.Combine(outputPath, Path.GetFileNameWithoutExtension(proto) + ".cs");
string s = File.ReadAllText(proto);
StringBuilder sb = new StringBuilder();
sb.Append("\n");
sb.Append("using System.Collections.Generic;\n");
sb.Append($"namespace {ns}\n");
sb.Append("{\n");
bool isMsgStart = false;
string parentClass = "";
foreach (string line in s.Split('\n'))
{
string newline = line.Trim();
if (newline == "")
{
continue;
}
if (newline.StartsWith("//"))
{
sb.Append($"{newline}\n");
}
if (newline.StartsWith("message"))
{
parentClass = "";
isMsgStart = true;
string msgName = newline.Split(splitChars, StringSplitOptions.RemoveEmptyEntries)[1];
string[] ss = newline.Split(new []{"//"}, StringSplitOptions.RemoveEmptyEntries);
if (ss.Length == 2)
{
parentClass = ss[1].Trim();
}
msgOpcode.Add(new OpcodeInfo() { Name = msgName, Opcode = ++startOpcode});
sb.Append($"\t[Message({opcodeClassName}.{msgName})]\n");
sb.Append($"\tpublic partial class {msgName}");
if (parentClass == "IActorMessage" || parentClass == "IActorRequest" || parentClass == "IActorResponse")
{
sb.Append($": {parentClass}\n");
}
else if (parentClass != "")
{
sb.Append($": {parentClass}\n");
}
else
{
sb.Append("\n");
}
continue;
}
if (isMsgStart)
{
if (newline == "{")
{
sb.Append("\t{\n");
continue;
}
if (newline == "}")
{
isMsgStart = false;
sb.Append("\t}\n\n");
continue;
}
if (newline.Trim().StartsWith("//"))
{
sb.AppendLine(newline);
continue;
}
if (newline.Trim() != "" && newline != "}")
{
if (newline.StartsWith("repeated"))
{
Repeated(sb, ns, newline);
}
else
{
Members(sb, newline, true);
}
}
}
}
sb.Append("}\n");
File.WriteAllText(csPath, sb.ToString());
}
private static void GenerateOpcode(string ns, string outputFileName, string outputPath)
{
StringBuilder sb = new StringBuilder();
sb.AppendLine($"namespace {ns}");
sb.AppendLine("{");
sb.AppendLine($"\tpublic static partial class {outputFileName}");
sb.AppendLine("\t{");
foreach (OpcodeInfo info in msgOpcode)
{
sb.AppendLine($"\t\t public const ushort {info.Name} = {info.Opcode};");
}
sb.AppendLine("\t}");
sb.AppendLine("}");
string csPath = Path.Combine(outputPath, outputFileName + ".cs");
File.WriteAllText(csPath, sb.ToString());
}
private static void Repeated(StringBuilder sb, string ns, string newline)
{
try
{
int index = newline.IndexOf(";");
newline = newline.Remove(index);
string[] ss = newline.Split(splitChars, StringSplitOptions.RemoveEmptyEntries);
string type = ss[1];
type = ConvertType(type);
string name = ss[2];
sb.Append($"\t\tpublic List<{type}> {name} = new List<{type}>();\n\n");
}
catch (Exception e)
{
Log.Error($"{newline}\n {e}");
}
}
private static string ConvertType(string type)
{
string typeCs = "";
switch (type)
{
case "int16":
typeCs = "short";
break;
case "int32":
typeCs = "int";
break;
case "bytes":
typeCs = "byte[]";
break;
case "uint32":
typeCs = "uint";
break;
case "long":
typeCs = "long";
break;
case "int64":
typeCs = "long";
break;
case "uint64":
typeCs = "ulong";
break;
case "uint16":
typeCs = "ushort";
break;
default:
typeCs = type;
break;
}
return typeCs;
}
private static void Members(StringBuilder sb, string newline, bool isRequired)
{
try
{
int index = newline.IndexOf(";");
newline = newline.Remove(index);
string[] ss = newline.Split(splitChars, StringSplitOptions.RemoveEmptyEntries);
string type = ss[0];
string name = ss[1];
string typeCs = ConvertType(type);
sb.Append($"\t\tpublic {typeCs} {name} {{ get; set; }}\n\n");
}
catch (Exception e)
{
Log.Error($"{newline}\n {e}");
}
}
}
}
fileFormatVersion: 2
guid: 0590521d9ee5b744eb2f86b044b2e2a7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
using System.Diagnostics;
using UnityEditor;
namespace ET
{
internal class OpcodeInfo
{
public string Name;
public int Opcode;
}
public class Proto2CSEditor: EditorWindow
{
[MenuItem("Tools/Proto2CS")]
public static void AllProto2CS()
{
Process process = ProcessHelper.Run("dotnet", "Proto2CS.dll", "../Proto/", false);
Log.Info(process.StandardOutput.ReadToEnd());
AssetDatabase.Refresh();
}
}
}
fileFormatVersion: 2
guid: 4eb91415eb00caf45ac2d0c0a6be283f
timeCreated: 1514895524
licenseType: Free
MonoImporter:
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 3cb778891aae4c84894d177b0178bcad
folderAsset: yes
timeCreated: 1506045262
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: c3fa93194f02ebe49a7670d67b2f8b8e
folderAsset: yes
timeCreated: 1505439286
licenseType: Free
DefaultImporter:
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: ddd506206ad91a24499d39bbb41402c9
timeCreated: 1499162000
licenseType: Free
PluginImporter:
serializedVersion: 2
iconMap: {}
executionOrder: {}
isPreloaded: 0
isOverridable: 0
platformData:
data:
first:
'': Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux: 1
Exclude Linux64: 1
Exclude LinuxUniversal: 1
Exclude OSXIntel: 1
Exclude OSXIntel64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
data:
first:
Any:
second:
enabled: 0
settings: {}
data:
first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
data:
first:
Facebook: Win
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Standalone: Linux
second:
enabled: 0
settings:
CPU: x86
data:
first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: x86_64
data:
first:
Standalone: LinuxUniversal
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: OSXIntel
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Standalone: OSXIntel64
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: Win
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: 23aa2f3505ec6644f9821d9428b28430
timeCreated: 1499220036
licenseType: Free
PluginImporter:
serializedVersion: 2
iconMap: {}
executionOrder: {}
isPreloaded: 0
isOverridable: 0
platformData:
data:
first:
'': Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux: 1
Exclude Linux64: 1
Exclude LinuxUniversal: 1
Exclude OSXIntel: 1
Exclude OSXIntel64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
data:
first:
Any:
second:
enabled: 0
settings: {}
data:
first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
data:
first:
Facebook: Win
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Standalone: Linux
second:
enabled: 0
settings:
CPU: x86
data:
first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: x86_64
data:
first:
Standalone: LinuxUniversal
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: OSXIntel
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Standalone: OSXIntel64
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: Win
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: bec6b1709aaee2d40b287de2251fd62d
timeCreated: 1499220037
licenseType: Free
PluginImporter:
serializedVersion: 2
iconMap: {}
executionOrder: {}
isPreloaded: 0
isOverridable: 0
platformData:
data:
first:
'': Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux: 1
Exclude Linux64: 1
Exclude LinuxUniversal: 1
Exclude OSXIntel: 1
Exclude OSXIntel64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
data:
first:
Any:
second:
enabled: 0
settings: {}
data:
first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
data:
first:
Facebook: Win
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Standalone: Linux
second:
enabled: 0
settings:
CPU: x86
data:
first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: x86_64
data:
first:
Standalone: LinuxUniversal
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: OSXIntel
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Standalone: OSXIntel64
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: Win
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: AnyCPU
data:
first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
fileFormatVersion: 2
guid: c753a6199062e744c9324a5b053bb4e4
timeCreated: 1499161999
licenseType: Free
PluginImporter:
serializedVersion: 2
iconMap: {}
executionOrder: {}
isPreloaded: 0
isOverridable: 0
platformData:
data:
first:
'': Any
second:
enabled: 0
settings:
Exclude Editor: 0
Exclude Linux: 1
Exclude Linux64: 1
Exclude LinuxUniversal: 1
Exclude OSXIntel: 1
Exclude OSXIntel64: 1
Exclude OSXUniversal: 1
Exclude Win: 1
Exclude Win64: 1
data:
first:
Any:
second:
enabled: 0
settings: {}
data:
first:
Editor: Editor
second:
enabled: 1
settings:
CPU: AnyCPU
DefaultValueInitialized: true
OS: AnyOS
data:
first:
Facebook: Win
second:
enabled: 0
settings:
CPU: None
data:
first:
Facebook: Win64
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: Linux
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: Linux64
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: LinuxUniversal
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: OSXIntel
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: OSXIntel64
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: OSXUniversal
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: Win
second:
enabled: 0
settings:
CPU: None
data:
first:
Standalone: Win64
second:
enabled: 0
settings:
CPU: None
data:
first:
Windows Store Apps: WindowsStoreApps
second:
enabled: 0
settings:
CPU: AnyCPU
userData:
assetBundleName:
assetBundleVariant:
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册