├── .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 | --------------------------------------------------------------------------------