├── .gitattributes
├── .gitignore
├── ConsoleTest
├── ConsoleTest.csproj
└── Program.cs
├── LICENSE
├── PluginExt
├── PluginBase.cs
├── PluginExt.csproj
└── Properties
│ └── AssemblyInfo.cs
├── PluginTest
├── PluginTest.csproj
├── Program.cs
└── Properties
│ └── AssemblyInfo.cs
├── README.md
├── TestPluginExt
└── TestPluginExt.csproj
└── Utili.sln
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 |
10 | # User-specific files (MonoDevelop/Xamarin Studio)
11 | *.userprefs
12 |
13 | # Build results
14 | [Dd]ebug/
15 | [Dd]ebugPublic/
16 | [Rr]elease/
17 | [Rr]eleases/
18 | x64/
19 | x86/
20 | build/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 |
25 | # Visual Studio 2015 cache/options directory
26 | .vs/
27 |
28 | # MSTest test Results
29 | [Tt]est[Rr]esult*/
30 | [Bb]uild[Ll]og.*
31 |
32 | # NUNIT
33 | *.VisualState.xml
34 | TestResult.xml
35 |
36 | # Build Results of an ATL Project
37 | [Dd]ebugPS/
38 | [Rr]eleasePS/
39 | dlldata.c
40 |
41 | # DNX
42 | project.lock.json
43 | artifacts/
44 |
45 | *_i.c
46 | *_p.c
47 | *_i.h
48 | *.ilk
49 | *.meta
50 | *.obj
51 | *.pch
52 | *.pdb
53 | *.pgc
54 | *.pgd
55 | *.rsp
56 | *.sbr
57 | *.tlb
58 | *.tli
59 | *.tlh
60 | *.tmp
61 | *.tmp_proj
62 | *.log
63 | *.vspscc
64 | *.vssscc
65 | .builds
66 | *.pidb
67 | *.svclog
68 | *.scc
69 |
70 | # Chutzpah Test files
71 | _Chutzpah*
72 |
73 | # Visual C++ cache files
74 | ipch/
75 | *.aps
76 | *.ncb
77 | *.opensdf
78 | *.sdf
79 | *.cachefile
80 |
81 | # Visual Studio profiler
82 | *.psess
83 | *.vsp
84 | *.vspx
85 |
86 | # TFS 2012 Local Workspace
87 | $tf/
88 |
89 | # Guidance Automation Toolkit
90 | *.gpState
91 |
92 | # ReSharper is a .NET coding add-in
93 | _ReSharper*/
94 | *.[Rr]e[Ss]harper
95 | *.DotSettings.user
96 |
97 | # JustCode is a .NET coding add-in
98 | .JustCode
99 |
100 | # TeamCity is a build add-in
101 | _TeamCity*
102 |
103 | # DotCover is a Code Coverage Tool
104 | *.dotCover
105 |
106 | # NCrunch
107 | _NCrunch_*
108 | .*crunch*.local.xml
109 |
110 | # MightyMoose
111 | *.mm.*
112 | AutoTest.Net/
113 |
114 | # Web workbench (sass)
115 | .sass-cache/
116 |
117 | # Installshield output folder
118 | [Ee]xpress/
119 |
120 | # DocProject is a documentation generator add-in
121 | DocProject/buildhelp/
122 | DocProject/Help/*.HxT
123 | DocProject/Help/*.HxC
124 | DocProject/Help/*.hhc
125 | DocProject/Help/*.hhk
126 | DocProject/Help/*.hhp
127 | DocProject/Help/Html2
128 | DocProject/Help/html
129 |
130 | # Click-Once directory
131 | publish/
132 |
133 | # Publish Web Output
134 | *.[Pp]ublish.xml
135 | *.azurePubxml
136 | ## TODO: Comment the next line if you want to checkin your
137 | ## web deploy settings but do note that will include unencrypted
138 | ## passwords
139 | #*.pubxml
140 |
141 | *.publishproj
142 |
143 | # NuGet Packages
144 | *.nupkg
145 | # The packages folder can be ignored because of Package Restore
146 | **/packages/*
147 | # except build/, which is used as an MSBuild target.
148 | !**/packages/build/
149 | # Uncomment if necessary however generally it will be regenerated when needed
150 | #!**/packages/repositories.config
151 |
152 | # Windows Azure Build Output
153 | csx/
154 | *.build.csdef
155 |
156 | # Windows Store app package directory
157 | AppPackages/
158 |
159 | # Visual Studio cache files
160 | # files ending in .cache can be ignored
161 | *.[Cc]ache
162 | # but keep track of directories ending in .cache
163 | !*.[Cc]ache/
164 |
165 | # Others
166 | ClientBin/
167 | [Ss]tyle[Cc]op.*
168 | ~$*
169 | *~
170 | *.dbmdl
171 | *.dbproj.schemaview
172 | *.pfx
173 | *.publishsettings
174 | node_modules/
175 | orleans.codegen.cs
176 |
177 | # RIA/Silverlight projects
178 | Generated_Code/
179 |
180 | # Backup & report files from converting an old project file
181 | # to a newer Visual Studio version. Backup files are not needed,
182 | # because we have git ;-)
183 | _UpgradeReport_Files/
184 | Backup*/
185 | UpgradeLog*.XML
186 | UpgradeLog*.htm
187 |
188 | # SQL Server files
189 | *.mdf
190 | *.ldf
191 |
192 | # Business Intelligence projects
193 | *.rdl.data
194 | *.bim.layout
195 | *.bim_*.settings
196 |
197 | # Microsoft Fakes
198 | FakesAssemblies/
199 |
200 | # Node.js Tools for Visual Studio
201 | .ntvs_analysis.dat
202 |
203 | # Visual Studio 6 build log
204 | *.plg
205 |
206 | # Visual Studio 6 workspace options file
207 | *.opt
208 |
209 | # LightSwitch generated files
210 | GeneratedArtifacts/
211 | _Pvt_Extensions/
212 | ModelManifest.xml
213 |
--------------------------------------------------------------------------------
/ConsoleTest/ConsoleTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {982FFF21-035E-47B7-AA8A-4D4C65531961}
8 | Exe
9 | Properties
10 | ConsoleTest
11 | ConsoleTest
12 | v3.5
13 | 512
14 |
15 |
16 | AnyCPU
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | AnyCPU
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 | {e8987a77-fb1c-4e5f-ace0-c321fb9ac874}
48 | TestPluginExt
49 |
50 |
51 |
52 |
53 |
54 |
55 |
62 |
--------------------------------------------------------------------------------
/ConsoleTest/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using PluginExt;
5 |
6 | namespace PluginTest {
7 | class Test {
8 | static void Main(string[] args) {
9 | File.Delete("TestPlugin.ini");
10 | var t = new TestPlugin();
11 | t.Awake();
12 | }
13 | }
14 | public class TestPlugin : ExPluginBase {
15 | class PluginConfig {
16 | public string[][] ss = new string[][] {
17 | new string[] {"aaaa","bb" },
18 | new string[] {"test","pipi"}
19 | };
20 | public int x = 10;
21 | public float s = (float)Math.PI;
22 | public double doubleValue = Math.PI;
23 | public string path = "pathtest";
24 | //テスト
25 | public string[] sFaceAnime41 = new string[] { "優しさ" , "微笑み" };
26 | public int[] d = new int[] { 1 , 4 , 6 , 7 , 8 };
27 | public Dictionary priority_hoge = new Dictionary();
28 | public ConsoleColor enumColor = ConsoleColor.Gray;
29 | public PluginConfig() {
30 | priority_hoge["test"] = 0.3f;
31 | priority_hoge["pi"] = (float)Math.PI;
32 | }
33 | }
34 | public Dictionary Result { get; } = new Dictionary();
35 |
36 | public bool Assert(bool test,string message) {
37 | if(test)
38 | WriteLine("{0} is success" , message);
39 | else
40 | WriteLine("{0} is failed" , message);
41 | Result.Add(message , test);
42 | return test;
43 | }
44 | public void Awake() {
45 | DebugWriteLine(DataPath);
46 | WriteLine("{0} Plugin Test" , Name);
47 |
48 | var cfg = ReadConfig();
49 | // Initialize
50 | Assert(cfg.x == 10 , "Int Initialize");
51 | Assert(cfg.enumColor == ConsoleColor.Gray , "Enum Initialize");
52 | Assert(cfg.priority_hoge["test"] == 0.3f , "Dictionary Initialize");
53 | Assert(cfg.ss[0][1] == "bb" && cfg.ss[1][1] == "pipi" , "Jagged String Array Initialize");
54 |
55 | cfg.x = 500;
56 | cfg.ss[1] = new string[] { "test" , "test" };
57 | cfg.priority_hoge["test"] = 300f;
58 | cfg.enumColor = ConsoleColor.White;
59 |
60 | SaveConfig(cfg);
61 | cfg = ReadConfig();
62 |
63 | Assert(cfg.x == 500 , "Int Read/Write");
64 | Assert(cfg.enumColor == ConsoleColor.White , "Enum Read/Write");
65 | Assert(cfg.priority_hoge["test"] == 300f , "Dictionary Read/Write");
66 | Assert(cfg.ss[1][1] == "test" , "Jagged String Arrar Read/Write");
67 |
68 | bool x = true;
69 | foreach(var item in Result) {
70 | if(!item.Value) {
71 | x = false;
72 | WriteLine("Failed");
73 | break;
74 | }
75 | }
76 | if(x)
77 | WriteLine("Success");
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 asm__
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/PluginExt/PluginBase.cs:
--------------------------------------------------------------------------------
1 | /* ExPluginBase
2 | * Pluginを楽に作るための抽象クラス
3 | * MIT License
4 | * Copyright © asm__ 2015
5 | */
6 | // テスト用にMonoBehaviourを継承しない
7 | //#define NOUNITY
8 | // 構想中
9 | //#define NOUNITYINJECTOR
10 |
11 | using System;
12 | using System.Collections.Generic;
13 | using System.Diagnostics;
14 | using System.IO;
15 | using System.Runtime.InteropServices;
16 | using System.Security;
17 | using System.Text;
18 |
19 | namespace PluginExt {
20 |
21 | ///
22 | /// 共有コンフィグ
23 | ///
24 | public static class SharedConfig {
25 | public static string DataPath{ get; }
26 | public static bool ChangeConsoleColor;
27 | static SharedConfig() {
28 | #if NOUNITY
29 | string DirName = ".";
30 | #elif NOUNITYINJECTOR
31 | //TODO
32 | string DirName = "Config";
33 | #else
34 | string DirName = @"UnityInjector\Config";
35 | #endif
36 | DataPath = Path.Combine(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName) , DirName);
37 | if(!Directory.Exists(DataPath))
38 | Directory.CreateDirectory(DataPath);
39 | try {
40 | Console.ForegroundColor = ConsoleColor.Gray;
41 | ChangeConsoleColor = true;
42 | } catch{
43 | ChangeConsoleColor = false;
44 | }
45 | }
46 |
47 | #region Save/ReadConfig
48 | public static class Ini {
49 | public class IniFile {
50 | public string File { get; }
51 | public List Sections { get; }
52 | public IniFile(string file) {
53 | File = file;
54 | var sec = new List();
55 | IntPtr ptr = IntPtr.Zero;
56 | try {
57 | int nSize = 1024;
58 | int ns;
59 | do {
60 | nSize *= 2;
61 | if(ptr != IntPtr.Zero)
62 | Marshal.FreeCoTaskMem(ptr);
63 | ptr = Marshal.AllocCoTaskMem(nSize * 2);
64 | ns = SafeNativeMethods.GetPrivateProfileSectionNames(ptr , nSize , File);
65 | } while(ns == nSize - 2);
66 | string[] sections = Marshal.PtrToStringAuto(ptr,ns).TrimEnd('\0').Split('\0');
67 | sec.AddRange(sections);
68 | Sections = sec;
69 | } finally {
70 | if(ptr != IntPtr.Zero)
71 | Marshal.FreeCoTaskMem(ptr);
72 | }
73 | }
74 | public List GetKeyAndSections(string section) {
75 | var parent = section + separator;
76 | var len = parent.Length;
77 | var res = Ini.GetKeys(section , File);
78 | foreach(var s in Sections.FindAll((s) => s.StartsWith(parent) && s.LastIndexOf(separator) <= len))
79 | res.Add(s.Substring(len));
80 | return res;
81 | }
82 | public Dictionary ReadDictionary(string section , string key , Dictionary def) {
83 | var sec = section + separator + key;
84 | var keys = GetKeyAndSections(sec);
85 | if(keys.Count == 0)
86 | return def;
87 | Dictionary res = new Dictionary(keys.Count);
88 | foreach(var item in keys) {
89 | T val = ReadObject(sec , item , default(T));
90 | res.Add(item , val);
91 | }
92 | return res;
93 | }
94 | public T[] ReadArray(string section , string key , T[] def) {
95 | var defList = def == null ? new List() : new List(def.Length);
96 | if(def != null)
97 | defList.AddRange(def);
98 | var resList = ReadList(section , key , defList);
99 | return resList.ToArray();
100 | }
101 | public List ReadList(string section , string key , List def) {
102 | var sec = section + separator + key;
103 | int t;
104 | int len = sec.Length;
105 | var keys = GetKeyAndSections(sec).FindAll((s) => Int32.TryParse(s , out t));
106 | keys.Sort(new NaturalStringComparer());
107 | if(keys.Count == 0)
108 | return def;
109 | var res = new List(keys.Count);
110 | foreach(var item in keys) {
111 | T val = ReadObject(sec , item , default(T));
112 | res.Add(val);
113 | }
114 | return res;
115 | }
116 | public object ReadObject(Type t,string section,string key,object def) {
117 | string _default = String.IsNullOrEmpty(def as string) ? "" : def.ToString();
118 | string s = ReadString(section , key , File , _default);
119 | if(t == typeof(string)) {
120 | return s;
121 | } else if(t.IsEnum) {
122 | try {
123 | return Enum.Parse(t , s);
124 | } catch {
125 | return def;
126 | }
127 | }
128 | Type[] p = new Type[2] { typeof(string) , t.MakeByRefType() };
129 | var methodTryParse = t.GetMethod("TryParse" , p);
130 | if(methodTryParse != null) {
131 | var _a = new object[] { s , def };
132 | if((bool)methodTryParse.Invoke(null , _a))
133 | return _a[1];
134 | return def;
135 | }
136 | object[] invoke_param = new object[] { section , key , def };
137 | if(t.IsArray) {
138 | // 配列型
139 | var generic_ra = typeof(IniFile).GetMethod(nameof(IniFile.ReadArray));
140 | var ra = generic_ra.MakeGenericMethod(t.GetElementType());
141 | return ra.Invoke(this , invoke_param);
142 | } else if(t.IsGenericType) {
143 | //Generic型
144 | Type genericType = t.GetGenericTypeDefinition();
145 | if(genericType == typeof(Dictionary<,>)) {
146 | // Dictionary
147 | var generic = typeof(IniFile).GetMethod(nameof(IniFile.ReadDictionary));
148 | var m = generic.MakeGenericMethod(t.GetGenericArguments()[1]);
149 | return m.Invoke(this , invoke_param);
150 | } else if(genericType == typeof(List<>)) {
151 | // List
152 | var generic = typeof(IniFile).GetMethod(nameof(IniFile.ReadList));
153 | var m = generic.MakeGenericMethod(t.GetGenericArguments()[0]);
154 | return m.Invoke(this , invoke_param);
155 | }
156 | }
157 | throw new NotImplementedException();
158 | }
159 | public T ReadObject(string section , string key,T def) {
160 | return (T) ReadObject(typeof(T) , section , key,def);
161 | }
162 | }
163 | protected static class SafeNativeMethods {
164 | [DllImport("shlwapi.dll" , CharSet = CharSet.Unicode), SuppressUnmanagedCodeSecurity]
165 | public static extern int StrCmpLogicalW(string psz1 , string psz2);
166 | [DllImport("KERNEL32.DLL" , CharSet = CharSet.Auto), SuppressUnmanagedCodeSecurity]
167 | public static extern uint GetPrivateProfileString(string lpAppName , string lpKeyName , string lpDefault , StringBuilder lpReturnedString , uint nSize , string lpFileName);
168 |
169 | [DllImport("KERNEL32.DLL" , CharSet = CharSet.Auto), SuppressUnmanagedCodeSecurity]
170 | public static extern uint GetPrivateProfileInt(string lpAppName , string lpKeyName , int nDefault , string lpFileName);
171 |
172 | [DllImport("kernel32.dll" , CharSet = CharSet.Auto), SuppressUnmanagedCodeSecurity]
173 | public static extern int GetPrivateProfileSection(string lpAppName , IntPtr lpszReturnBuffer , int nSize , string lpFileName);
174 | [DllImport("kernel32.dll" , CharSet = CharSet.Auto), SuppressUnmanagedCodeSecurity]
175 | public static extern int GetPrivateProfileSectionNames(IntPtr lpszReturnBuffer , int nSize , string lpFileName);
176 |
177 | [DllImport("KERNEL32.DLL" , CharSet = CharSet.Auto), SuppressUnmanagedCodeSecurity]
178 | public static extern uint WritePrivateProfileString(string lpAppName , string lpKeyName , string lpString , string lpFileName);
179 | }
180 |
181 | public sealed class NaturalStringComparer : IComparer {
182 | public int Compare(string a , string b) {
183 | return SafeNativeMethods.StrCmpLogicalW(a , b);
184 | }
185 | }
186 | static string separator = "::";
187 |
188 | public static List GetKeys(string section , string filepath) {
189 | IntPtr sb = IntPtr.Zero; int nsize = 1024; int ns;
190 | try {
191 | do {
192 | nsize *= 2;
193 | sb = Marshal.AllocCoTaskMem(nsize * 2);
194 | ns = SafeNativeMethods.GetPrivateProfileSection(section , sb , nsize , filepath);
195 | } while(nsize - 2 == ns);
196 | string sec = Marshal.PtrToStringAuto(sb , ns);
197 |
198 | string[] tmp = sec.TrimEnd('\0').Split('\0');
199 | if(tmp[0] == String.Empty)
200 | return new List(0);
201 | List result = new List(tmp.Length);
202 | foreach(string entry in tmp) {
203 | result.Add(entry.Substring(0 , entry.IndexOf('=')));
204 | }
205 | return result;
206 | } finally {
207 | if(sb != IntPtr.Zero)
208 | Marshal.FreeCoTaskMem(sb);
209 | }
210 | }
211 | #region 読み込み
212 | public static object ReadObject(Type t , string section , string key , string filepath , object def) {
213 | string _default = String.IsNullOrEmpty(def as string) ? "" : def.ToString();
214 | string s = ReadString(section , key , filepath , _default);
215 | if(t == typeof(string)) {
216 | return s;
217 | } else if(t.IsEnum) {
218 | try {
219 | return Enum.Parse(t , s);
220 | } catch {
221 | return def;
222 | }
223 | }
224 | Type[] p = new Type[2] { typeof(string) , t.MakeByRefType() };
225 | var methodTryParse = t.GetMethod("TryParse" , p);
226 | if(methodTryParse != null) {
227 | var _a = new object[] { s , def };
228 | if((bool)methodTryParse.Invoke(null , _a))
229 | return _a[1];
230 | return def;
231 | }
232 | object[] invoke_param = new object[] { section , key , filepath , def };
233 | if(t.IsArray) {
234 | // 配列型
235 | var generic_ra = typeof(Ini).GetMethod(nameof(Ini.ReadArray));
236 | var ra = generic_ra.MakeGenericMethod(t.GetElementType());
237 | return ra.Invoke(null , invoke_param);
238 | }else if(t.IsGenericType) {
239 | //Generic型
240 | Type genericType = t.GetGenericTypeDefinition();
241 | if(genericType == typeof(Dictionary<,>)) {
242 | // Dictionary
243 | var generic = typeof(Ini).GetMethod(nameof(ReadDictionary));
244 | var m = generic.MakeGenericMethod(t.GetGenericArguments()[1]);
245 | return m.Invoke(null , invoke_param);
246 | }else if(genericType == typeof(List<>)) {
247 | // List
248 | var generic = typeof(Ini).GetMethod(nameof(ReadList));
249 | var m = generic.MakeGenericMethod(t.GetGenericArguments()[0]);
250 | return m.Invoke(null , invoke_param);
251 | }
252 | }
253 | throw new NotImplementedException();
254 | }
255 | public static T ReadObject(string section , string key , string filepath , T def) {
256 | return (T)ReadObject(typeof(T) , section , key , filepath , def);
257 | }
258 |
259 | public static string ReadString(string section , string key , string filepath , string def) {
260 | StringBuilder sb = null;
261 | int size = 1024; uint ns = 0;
262 | do {
263 | size *= 2;
264 | sb = new StringBuilder(size);
265 | ns = SafeNativeMethods.GetPrivateProfileString(section , key , def , sb , (uint)size , filepath);
266 | } while(size - 2 == ns);
267 | return sb.ToString();
268 | }
269 | #endregion
270 | #region コレクション型
271 | #region List
272 | public static List ReadList(string section , string key , string filepath , List def) {
273 | int t;
274 | string sec = section + separator + key;
275 | var list = GetKeys(sec , filepath).FindAll((s) => Int32.TryParse(s , out t));
276 | list.Sort(new NaturalStringComparer());
277 | if(list.Count == 0)
278 | return def;
279 |
280 | List result = new List(list.Capacity);
281 | foreach(var item in list) {
282 | result.Add(ReadObject(sec , item , filepath , default(T)));
283 | }
284 | return result;
285 | }
286 |
287 | public static void WriteList(List data,string section,string key,string filepath) {
288 | var sec = section + separator + key;
289 | int i = 0;
290 | foreach(var item in data) {
291 | WriteObject(item , sec , i.ToString() , filepath);
292 | }
293 | }
294 | #endregion
295 | #region Dictionary
296 | public static Dictionary ReadDictionary(string section , string key , string filepath , Dictionary def) {
297 | string sec = section + separator + key;
298 | var list = GetKeys(sec , filepath);
299 | if(list.Count == 0)
300 | return def;
301 | Dictionary result = new Dictionary(list.Capacity);
302 | foreach(var item in list)
303 | result.Add(item , ReadObject(sec , item , filepath , default(V)));
304 | return result;
305 | }
306 | public static void WriteDictionary(Dictionary data , string section , string key , string filepath) {
307 | var sec = section + separator + key;
308 | foreach(var item in data) {
309 | WriteObject(item.Value , sec , item.Key , filepath);
310 | }
311 | }
312 | #endregion
313 | #endregion
314 | #region 配列型
315 | public static T[] ReadArray(string section , string key , string filepath , T[] def) {
316 | List _def = new List(def.Length);
317 | _def.AddRange(def);
318 | List result = ReadList(section , key , filepath , _def);
319 | return result.ToArray();
320 | }
321 | #endregion
322 | public static void WriteObject(Type t , object data , string section , string key , string filepath) {
323 | Type[] p = new Type[2] { typeof(string) , t.MakeByRefType() };
324 | if(t.GetMethod("TryParse" , p) != null || t.IsEnum) {
325 | SafeNativeMethods.WritePrivateProfileString(section , key , data.ToString() , filepath);
326 | } else if(t == typeof(string)) {
327 | SafeNativeMethods.WritePrivateProfileString(section , key , (string)data , filepath);
328 | } else if(t.IsArray) {
329 | Array x = (Array)data;
330 | string sec = section + separator + key;
331 | int i = 0;
332 | foreach(var item in x) {
333 | WriteObject(t.GetElementType() , item , sec , i.ToString() , filepath);
334 | i++;
335 | }
336 | } else if(t.IsGenericType) {
337 | Type genericType = t.GetGenericTypeDefinition();
338 | var invoke_param = new object[] { data , section , key , filepath };
339 | if(genericType == typeof(Dictionary<,>)) {
340 | var generic_write = typeof(Ini).GetMethod(nameof(WriteDictionary));
341 | var write = generic_write.MakeGenericMethod(t.GetGenericArguments()[1]);
342 | write.Invoke(null , invoke_param);
343 | } else if(genericType == typeof(List<>)) {
344 | var generic_write = typeof(Ini).GetMethod(nameof(WriteList));
345 | var write = generic_write.MakeGenericMethod(t.GetGenericArguments()[0]);
346 | write.Invoke(null , invoke_param);
347 | }
348 | } else
349 | throw new NotImplementedException();
350 | }
351 | public static void WriteObject(T data , string section , string key , string filepath) {
352 | WriteObject(typeof(T) , data , section , key , filepath);
353 | }
354 |
355 |
356 | public static T Read(string section , string filepath) {
357 | T ret = (T)Activator.CreateInstance(typeof(T));
358 | IniFile ini = new IniFile(filepath);
359 |
360 | foreach(var n in typeof(T).GetFields()) {
361 | Type t = n.FieldType;
362 | n.SetValue(ret , ini.ReadObject(t , section , n.Name , n.GetValue(ret)));
363 | }
364 |
365 | var postd = typeof(T).GetMethod("OnPostDeserialize");
366 | if(postd != null)
367 | postd.Invoke(ret , null);
368 |
369 | return ret;
370 | }
371 | public static void Write(string section , T data , string filepath) {
372 | var pres = typeof(T).GetMethod("OnPreSerialize");
373 | Type[] p = new Type[2] { typeof(string) , typeof(T).MakeByRefType() };
374 | if(pres != null)
375 | pres.Invoke(data , null);
376 | foreach(var n in typeof(T).GetFields()) {
377 | WriteObject(n.FieldType , n.GetValue(data) , section , n.Name , filepath);
378 | }
379 | }
380 | }
381 | public static T ReadConfig(string section,string filename) where T : new() {
382 | string path = Path.Combine(DataPath , filename);
383 | return Ini.Read(section , path);
384 | }
385 | public static void SaveConfig(string section ,string filename , T data) {
386 | string path = Path.Combine(DataPath , filename);
387 | Ini.Write(section , data , path);
388 | }
389 | #endregion
390 | }
391 | #if NOUNITY
392 | public abstract class ExPluginBase
393 | #elif NOUNITYINJECTOR
394 | public abstract class ExPluginBase : UnityEngine.MonoBehaviour
395 | #else
396 | // abstract class まで自動登録しようとするのは流石にバグだと思うんだよなぁ
397 | // ファイルに使えない文字列突っ込んで回避
398 | [UnityInjector.Attributes.PluginFilter("|NoNeedAutoLoad|")]
399 | public abstract class ExPluginBase : UnityInjector.PluginBase
400 | #endif
401 | {
402 | #if NOUNITYINJECTOR || NOUNITY
403 | ///
404 | /// クラス名の取得
405 | ///
406 | protected string Name { get { return GetType().Name; } }
407 | ///
408 | /// 設定などの保管場所の取得
409 | ///
410 | protected static string DataPath { get; } = SharedConfig.DataPath;
411 | #else
412 | ///
413 | /// 設定などの保管場所の取得
414 | ///
415 | protected static new string DataPath { get; } = SharedConfig.DataPath;
416 | #endif
417 |
418 | #region Config
419 | ///
420 | /// 設定値をiniから読み込む
421 | ///
422 | /// 受け取る型
423 | /// Rootセクション名
424 | ///
425 | public T ReadConfig(string section) where T : new() {
426 | return SharedConfig.ReadConfig("Config" ,Name + ".ini");
427 | }
428 |
429 | ///
430 | /// 設定値をiniから読み込む
431 | ///
432 | /// 受け取る型
433 | ///
434 | public T ReadConfig() where T : new() {
435 | return ReadConfig("Config");
436 | }
437 |
438 | ///
439 | /// 設定値をiniへ書き込む
440 | ///
441 | /// 書き込む型
442 | /// 書き込むデータ
443 | /// Rootセクション名
444 | public void SaveConfig(T data,string section) {
445 | SharedConfig.SaveConfig("Config",Name + ".ini" , data);
446 | }
447 |
448 | ///
449 | /// 設定値をiniへ書き込む
450 | ///
451 | /// 書き込む型
452 | /// 書き込むデータ
453 | public void SaveConfig(T data) {
454 | SaveConfig(data , "Config");
455 | }
456 | #endregion
457 |
458 | #region Console
459 | ///
460 | /// コンソールに文字列出力
461 | /// Console.WriteLineを使うとUnityがトレースログ吐く度に文字色が黒になるので対策
462 | ///
463 | /// フォーマット文字列
464 | /// [option]引数,...
465 | public static void WriteLine(string fmt , params object[] arg) {
466 | #if NOUNITYINJECTOR || NOUNITY
467 | if(SharedConfig.ChangeConsoleColor)
468 | Console.ForegroundColor = ConsoleColor.Gray;
469 | #else
470 | UnityInjector.ConsoleUtil.SafeConsole.ForegroundColor = ConsoleColor.Gray;
471 | #endif
472 | if(arg.Length == 0) {
473 | Console.WriteLine(fmt);
474 | return;
475 | }
476 | Console.WriteLine(fmt , arg);
477 | }
478 |
479 | ///
480 | /// DEBUGビルド時のみコンソールに文字列出力
481 | /// Console.WriteLineを使うとUnityがトレースログ吐く度に文字色が黒になるので対策
482 | ///
483 | /// フォーマット文字列
484 | /// [option]引数,...
485 | [Conditional("DEBUG")]
486 | public static void DebugWriteLine(string fmt , params object[] arg) {
487 | WriteLine(fmt , arg);
488 | }
489 |
490 | #if !NOUNITY
491 | ///
492 | /// CallTree付きログをoutput_log.txtに出力する
493 | ///
494 | /// 書式指定文字列
495 | /// [OPTION]引数,...
496 | public static void LogWithCallTree(string fmt , params object[] arg) {
497 | string message = String.Format(fmt , arg);
498 | UnityEngine.Debug.Log(message);
499 | }
500 | ///
501 | /// DEBUGビルド時のみCallTree付きログをoutput_log.txtに出力する
502 | ///
503 | /// 書式指定文字列
504 | /// [OPTION]引数,...
505 | [Conditional("DEBUG")]
506 | public static void DebugLogWithCallTree(string fmt , params object[] arg) {
507 | string message = String.Format(fmt , arg);
508 | UnityEngine.Debug.Log(message);
509 | }
510 | #endif
511 | #endregion
512 | }
513 | }
514 |
--------------------------------------------------------------------------------
/PluginExt/PluginExt.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {E187A4B0-6A9C-4C2E-A85F-E5F500F661BB}
8 | Library
9 | Properties
10 | PluginExt
11 | PluginExt
12 | v3.5
13 | 512
14 |
15 |
16 | true
17 | full
18 | false
19 | bin\Debug\
20 | DEBUG;TRACE
21 | prompt
22 | 4
23 |
24 |
25 | pdbonly
26 | true
27 | bin\Release\
28 | TRACE
29 | prompt
30 | 4
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | ..\..\..\..\..\Gamez\KISS\CM3D2_KAIZOU\CM3D2x64_Data\Managed\UnityEngine.dll
41 | False
42 |
43 |
44 | ..\..\..\..\..\Gamez\KISS\CM3D2_KAIZOU\CM3D2x64_Data\Managed\UnityInjector.dll
45 | False
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
60 |
--------------------------------------------------------------------------------
/PluginExt/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。
6 | // アセンブリに関連付けられている情報を変更するには、
7 | // これらの属性値を変更してください。
8 | [assembly: AssemblyTitle("PluginExt")]
9 | [assembly: AssemblyProduct("PluginExt")]
10 | [assembly: AssemblyCopyright("Copyright © asm__ 2015")]
11 |
12 | // ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから
13 | // 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、
14 | // その型の ComVisible 属性を true に設定してください。
15 | [assembly: ComVisible(false)]
16 |
17 | // アセンブリのバージョン情報は次の 4 つの値で構成されています:
18 | //
19 | // メジャー バージョン
20 | // マイナー バージョン
21 | // ビルド番号
22 | // Revision
23 | //
24 | // すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を
25 | // 既定値にすることができます:
26 | // [assembly: AssemblyVersion("1.0.*")]
27 | [assembly: AssemblyVersion("0.3.0.*")]
28 | [assembly: AssemblyFileVersion("1.0")]
29 |
--------------------------------------------------------------------------------
/PluginTest/PluginTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {F246C2D6-87D9-4189-BACE-B016A14D22EC}
8 | Library
9 | Properties
10 | PluginTest
11 | PluginTest
12 | v3.5
13 | 512
14 |
15 |
16 | AnyCPU
17 | true
18 | full
19 | false
20 | bin\Debug\
21 | DEBUG;TRACE
22 | prompt
23 | 4
24 |
25 |
26 | AnyCPU
27 | pdbonly
28 | true
29 | bin\Release\
30 | TRACE
31 | prompt
32 | 4
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | ..\..\..\..\..\Gamez\KISS\CM3D2_KAIZOU\CM3D2x64_Data\Managed\UnityEngine.dll
46 |
47 |
48 | ..\..\..\..\..\Gamez\KISS\CM3D2_KAIZOU\CM3D2x64_Data\Managed\UnityInjector.dll
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 | {e187a4b0-6a9c-4c2e-a85f-e5f500f661bb}
61 | PluginExt
62 |
63 |
64 |
65 |
72 |
--------------------------------------------------------------------------------
/PluginTest/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using PluginExt;
4 | using UnityEngine;
5 |
6 | namespace PluginTest {
7 | class Program {
8 | static void Main(string[] args) {
9 | var t = new TestPlugin();
10 | t.Awake();
11 | }
12 | }
13 | public class TestPlugin : ExPluginBase {
14 | class PluginConfig {
15 | public string[][] ss = new string[][] {
16 | new string[] {"aaaa","bb" },
17 | new string[] {"test","pipi"}
18 | };
19 | public int x = 10;
20 | public float s = (float)Math.PI;
21 | public double doubleValue = Math.PI;
22 | public string path = "pathtest";
23 | //テスト
24 | public string[] sFaceAnime41 = new string[] { "優しさ" , "微笑み" };
25 | public int[] d = new int[] { 1 , 4 , 6 , 7 , 8 };
26 | public Dictionary priority_hoge = new Dictionary();
27 | public PluginConfig() {
28 | priority_hoge["test"] = 0.3f;
29 | priority_hoge["pi"] = (float)Math.PI;
30 | }
31 | }
32 | public void Awake() {
33 | DebugWriteLine(DataPath);
34 | WriteLine("{0} Plugin Test",Name);
35 | var cfg = ReadConfig();
36 | WriteLine("cfg.path = {0}" , cfg.path);
37 | foreach(var item in cfg.sFaceAnime41) {
38 | WriteLine(item);
39 | }
40 | SaveConfig(cfg);
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/PluginTest/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // アセンブリに関する一般情報は以下の属性セットをとおして制御されます。
6 | // アセンブリに関連付けられている情報を変更するには、
7 | // これらの属性値を変更してください。
8 | [assembly: AssemblyTitle("PluginTest")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("PluginTest")]
13 | [assembly: AssemblyCopyright("Copyright © 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // ComVisible を false に設定すると、その型はこのアセンブリ内で COM コンポーネントから
18 | // 参照不可能になります。COM からこのアセンブリ内の型にアクセスする場合は、
19 | // その型の ComVisible 属性を true に設定してください。
20 | [assembly: ComVisible(false)]
21 |
22 | // このプロジェクトが COM に公開される場合、次の GUID が typelib の ID になります
23 | [assembly: Guid("f246c2d6-87d9-4189-bace-b016a14d22ec")]
24 |
25 | // アセンブリのバージョン情報は次の 4 つの値で構成されています:
26 | //
27 | // メジャー バージョン
28 | // マイナー バージョン
29 | // ビルド番号
30 | // Revision
31 | //
32 | // すべての値を指定するか、下のように '*' を使ってビルドおよびリビジョン番号を
33 | // 既定値にすることができます:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ExPluginBase
2 |
3 | ざっくり説明するとプラグイン作成の際のよく使う・作る機能をあらかじめ実装しておこう的な
4 |
5 | ## INI シリアライザ
6 | - 設定値をiniから読み込む
7 | - 設定値をiniへ書き込む
8 |
9 | ```csharp
10 | class PluginConfig {
11 | public int x = 10;
12 | public float s = (float)Math.PI;
13 | public double doubleValue = Math.PI;
14 | public string path = "pathtest";
15 | public int[] d = new int[] { 1 , 4 , 6 , 7 , 8 };
16 | }
17 | // iniからの読み取り
18 | var cfg = ReadConfig();
19 | WriteLine("cfg.path = {0}" , cfg.path);
20 | // iniへの書き込み
21 | SaveConfig(cfg);
22 | ```
23 | #### 対応状況
24 | |Type |状況 |
25 | |:-----|----------|
26 | |string|Write/Read|
27 | |(u)int|Write/Read|
28 | |(u)long|Write/Read|
29 | |float/double|Write/Read|
30 | |bool|Write/Read|
31 | |enum|Write/Read|
32 | |T[]|Write/Read \*1 \*2|
33 | |List\|Write/Read \*1 \*2|
34 | |Dictionary\|Write/Read \*2 \*3|
35 | |struct|NotSupported|
36 | |class |NotSupported|
37 |
38 | Tは表内でサポートされている任意の型
39 | #### 既知のバグ
40 | - \*1 ユーザーが編集するなどして **添字が0からはじまらない** 場合や **添字に抜けがある** 場合にデータを壊してしまうバグがある
41 | - \*2 配列やList / Dictionaryにおいて要素を削って保存しても適用されず次回読み込み時に読み込まれてしまう
42 | - \*3 キーに"::"を含む要素は読み込めない
43 |
44 | ## コンソール出力
45 | - WriteLine
46 | - DebugWriteLine
47 | - LogWithCallTree
48 | - DebugLogWithCallTree
49 |
--------------------------------------------------------------------------------
/TestPluginExt/TestPluginExt.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {E8987A77-FB1C-4E5F-ACE0-C321FB9AC874}
8 | Library
9 | Properties
10 | TestPluginExt
11 | TestPluginExt
12 | v3.5
13 | 512
14 |
15 |
16 | true
17 | full
18 | false
19 | bin\Debug\
20 | TRACE;DEBUG;NOUNITY
21 | prompt
22 | 4
23 |
24 |
25 | pdbonly
26 | true
27 | bin\Release\
28 | TRACE;NOUNITY
29 | prompt
30 | 4
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | PluginBase.cs
46 |
47 |
48 |
49 |
56 |
--------------------------------------------------------------------------------
/Utili.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.23107.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginExt", "PluginExt\PluginExt.csproj", "{E187A4B0-6A9C-4C2E-A85F-E5F500F661BB}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginTest", "PluginTest\PluginTest.csproj", "{F246C2D6-87D9-4189-BACE-B016A14D22EC}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestPluginExt", "TestPluginExt\TestPluginExt.csproj", "{E8987A77-FB1C-4E5F-ACE0-C321FB9AC874}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleTest", "ConsoleTest\ConsoleTest.csproj", "{982FFF21-035E-47B7-AA8A-4D4C65531961}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
20 | {E187A4B0-6A9C-4C2E-A85F-E5F500F661BB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {E187A4B0-6A9C-4C2E-A85F-E5F500F661BB}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {E187A4B0-6A9C-4C2E-A85F-E5F500F661BB}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {E187A4B0-6A9C-4C2E-A85F-E5F500F661BB}.Release|Any CPU.Build.0 = Release|Any CPU
24 | {F246C2D6-87D9-4189-BACE-B016A14D22EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
25 | {F246C2D6-87D9-4189-BACE-B016A14D22EC}.Debug|Any CPU.Build.0 = Debug|Any CPU
26 | {F246C2D6-87D9-4189-BACE-B016A14D22EC}.Release|Any CPU.ActiveCfg = Release|Any CPU
27 | {F246C2D6-87D9-4189-BACE-B016A14D22EC}.Release|Any CPU.Build.0 = Release|Any CPU
28 | {E8987A77-FB1C-4E5F-ACE0-C321FB9AC874}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {E8987A77-FB1C-4E5F-ACE0-C321FB9AC874}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {E8987A77-FB1C-4E5F-ACE0-C321FB9AC874}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | {E8987A77-FB1C-4E5F-ACE0-C321FB9AC874}.Release|Any CPU.Build.0 = Release|Any CPU
32 | {982FFF21-035E-47B7-AA8A-4D4C65531961}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
33 | {982FFF21-035E-47B7-AA8A-4D4C65531961}.Debug|Any CPU.Build.0 = Debug|Any CPU
34 | {982FFF21-035E-47B7-AA8A-4D4C65531961}.Release|Any CPU.ActiveCfg = Release|Any CPU
35 | {982FFF21-035E-47B7-AA8A-4D4C65531961}.Release|Any CPU.Build.0 = Release|Any CPU
36 | EndGlobalSection
37 | GlobalSection(SolutionProperties) = preSolution
38 | HideSolutionNode = FALSE
39 | EndGlobalSection
40 | EndGlobal
41 |
--------------------------------------------------------------------------------