├── .gitattributes
├── .gitignore
├── MonkeModManager.sln
├── MonkeModManager
├── FormMain.Designer.cs
├── FormMain.cs
├── FormMain.resx
├── FormSelectPlatform.Designer.cs
├── FormSelectPlatform.cs
├── FormSelectPlatform.resx
├── Internals
│ ├── ReleaseInfo.cs
│ ├── SimpleJson.cs
│ └── Unzip.cs
├── MonkeModManager.csproj
├── Program.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── app.config
├── app.manifest
├── mods.json
├── monke.ico
├── monke.png
├── monke_transparent.ico
├── monke_transparent.png
└── update.txt
├── README.md
├── mods.json
└── update.txt
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.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 | *.sln.docstates
8 |
9 | # Build results
10 |
11 | [Dd]ebug/
12 | [Rr]elease/
13 | x64/
14 | build/
15 | [Bb]in/
16 | [Oo]bj/
17 |
18 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets
19 | !packages/*/build/
20 |
21 | # MSTest test Results
22 | [Tt]est[Rr]esult*/
23 | [Bb]uild[Ll]og.*
24 |
25 | *_i.c
26 | *_p.c
27 | *.ilk
28 | *.meta
29 | *.obj
30 | *.pch
31 | *.pdb
32 | *.pgc
33 | *.pgd
34 | *.rsp
35 | *.sbr
36 | *.tlb
37 | *.tli
38 | *.tlh
39 | *.tmp
40 | *.tmp_proj
41 | *.log
42 | *.vspscc
43 | *.vssscc
44 | .builds
45 | *.pidb
46 | *.log
47 | *.scc
48 |
49 | # Visual C++ cache files
50 | ipch/
51 | *.aps
52 | *.ncb
53 | *.opensdf
54 | *.sdf
55 | *.cachefile
56 |
57 | # Visual Studio profiler
58 | *.psess
59 | *.vsp
60 | *.vspx
61 |
62 | # Guidance Automation Toolkit
63 | *.gpState
64 |
65 | # ReSharper is a .NET coding add-in
66 | _ReSharper*/
67 | *.[Rr]e[Ss]harper
68 |
69 | # TeamCity is a build add-in
70 | _TeamCity*
71 |
72 | # DotCover is a Code Coverage Tool
73 | *.dotCover
74 |
75 | # NCrunch
76 | *.ncrunch*
77 | .*crunch*.local.xml
78 |
79 | # Installshield output folder
80 | [Ee]xpress/
81 |
82 | # DocProject is a documentation generator add-in
83 | DocProject/buildhelp/
84 | DocProject/Help/*.HxT
85 | DocProject/Help/*.HxC
86 | DocProject/Help/*.hhc
87 | DocProject/Help/*.hhk
88 | DocProject/Help/*.hhp
89 | DocProject/Help/Html2
90 | DocProject/Help/html
91 |
92 | # Click-Once directory
93 | publish/
94 |
95 | # Publish Web Output
96 | *.Publish.xml
97 |
98 | # NuGet Packages Directory
99 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
100 | #packages/
101 |
102 | # Windows Azure Build Output
103 | csx
104 | *.build.csdef
105 |
106 | # Windows Store app package directory
107 | AppPackages/
108 |
109 | # Others
110 | sql/
111 | *.Cache
112 | ClientBin/
113 | [Ss]tyle[Cc]op.*
114 | ~$*
115 | *~
116 | *.dbmdl
117 | *.[Pp]ublish.xml
118 | *.pfx
119 | *.publishsettings
120 |
121 | # RIA/Silverlight projects
122 | Generated_Code/
123 |
124 | # Backup & report files from converting an old project file to a newer
125 | # Visual Studio version. Backup files are not needed, because we have git ;-)
126 | _UpgradeReport_Files/
127 | Backup*/
128 | UpgradeLog*.XML
129 | UpgradeLog*.htm
130 |
131 | # SQL Server files
132 | App_Data/*.mdf
133 | App_Data/*.ldf
134 |
135 |
136 | #LightSwitch generated files
137 | GeneratedArtifacts/
138 | _Pvt_Extensions/
139 | ModelManifest.xml
140 |
141 | # =========================
142 | # Windows detritus
143 | # =========================
144 |
145 | # Windows image file caches
146 | Thumbs.db
147 | ehthumbs.db
148 |
149 | # Folder config file
150 | Desktop.ini
151 |
152 | # Recycle Bin used on file shares
153 | $RECYCLE.BIN/
154 |
155 | # Mac desktop service store files
156 | .DS_Store
157 | /MonkeModManager/token.txt
158 |
--------------------------------------------------------------------------------
/MonkeModManager.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31005.135
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MonkeModManager", "MonkeModManager\MonkeModManager.csproj", "{D30E6357-6B15-4EDC-8DD3-8A6D040D83B2}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {D30E6357-6B15-4EDC-8DD3-8A6D040D83B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {D30E6357-6B15-4EDC-8DD3-8A6D040D83B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {D30E6357-6B15-4EDC-8DD3-8A6D040D83B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {D30E6357-6B15-4EDC-8DD3-8A6D040D83B2}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {F2095A4B-F91F-41B5-B658-E18B3319AFFC}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/MonkeModManager/FormMain.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace MonkeModManager
2 | {
3 | partial class FormMain
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.components = new System.ComponentModel.Container();
32 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(FormMain));
33 | this.textBoxDirectory = new System.Windows.Forms.TextBox();
34 | this.buttonFolderBrowser = new System.Windows.Forms.Button();
35 | this.label1 = new System.Windows.Forms.Label();
36 | this.buttonInstall = new System.Windows.Forms.Button();
37 | this.labelStatus = new System.Windows.Forms.Label();
38 | this.tabControlMain = new System.Windows.Forms.TabControl();
39 | this.Plugins = new System.Windows.Forms.TabPage();
40 | this.listViewMods = new System.Windows.Forms.ListView();
41 | this.columnHeaderName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
42 | this.columnHeaderAuthor = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
43 | this.contextMenuStripMain = new System.Windows.Forms.ContextMenuStrip(this.components);
44 | this.viewInfoToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
45 | this.Utilities = new System.Windows.Forms.TabPage();
46 | this.pictureBox1 = new System.Windows.Forms.PictureBox();
47 | this.buttonOpenWiki = new System.Windows.Forms.Button();
48 | this.buttonDiscordLink = new System.Windows.Forms.Button();
49 | this.groupBox1 = new System.Windows.Forms.GroupBox();
50 | this.buttonBepInEx = new System.Windows.Forms.Button();
51 | this.buttonOpenConfig = new System.Windows.Forms.Button();
52 | this.buttonOpenGameFolder = new System.Windows.Forms.Button();
53 | this.labelOpen = new System.Windows.Forms.Label();
54 | this.buttonRestoreCosmetics = new System.Windows.Forms.Button();
55 | this.buttonRestoreMods = new System.Windows.Forms.Button();
56 | this.buttonBackupCosmetics = new System.Windows.Forms.Button();
57 | this.buttonBackupMods = new System.Windows.Forms.Button();
58 | this.buttonUninstallAll = new System.Windows.Forms.Button();
59 | this.buttonModInfo = new System.Windows.Forms.Button();
60 | this.labelVersion = new System.Windows.Forms.Label();
61 | this.tabControlMain.SuspendLayout();
62 | this.Plugins.SuspendLayout();
63 | this.contextMenuStripMain.SuspendLayout();
64 | this.Utilities.SuspendLayout();
65 | ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
66 | this.groupBox1.SuspendLayout();
67 | this.SuspendLayout();
68 | //
69 | // textBoxDirectory
70 | //
71 | this.textBoxDirectory.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left)
72 | | System.Windows.Forms.AnchorStyles.Right)));
73 | this.textBoxDirectory.Enabled = false;
74 | this.textBoxDirectory.Location = new System.Drawing.Point(10, 25);
75 | this.textBoxDirectory.Name = "textBoxDirectory";
76 | this.textBoxDirectory.Size = new System.Drawing.Size(508, 22);
77 | this.textBoxDirectory.TabIndex = 0;
78 | //
79 | // buttonFolderBrowser
80 | //
81 | this.buttonFolderBrowser.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
82 | this.buttonFolderBrowser.Location = new System.Drawing.Point(524, 25);
83 | this.buttonFolderBrowser.Name = "buttonFolderBrowser";
84 | this.buttonFolderBrowser.Size = new System.Drawing.Size(26, 23);
85 | this.buttonFolderBrowser.TabIndex = 1;
86 | this.buttonFolderBrowser.Text = "..";
87 | this.buttonFolderBrowser.UseVisualStyleBackColor = true;
88 | this.buttonFolderBrowser.Click += new System.EventHandler(this.buttonFolderBrowser_Click);
89 | //
90 | // label1
91 | //
92 | this.label1.AutoSize = true;
93 | this.label1.Location = new System.Drawing.Point(9, 9);
94 | this.label1.Name = "label1";
95 | this.label1.Size = new System.Drawing.Size(127, 13);
96 | this.label1.TabIndex = 2;
97 | this.label1.Text = "Gorilla Tag Folder Path:";
98 | //
99 | // buttonInstall
100 | //
101 | this.buttonInstall.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
102 | this.buttonInstall.Enabled = false;
103 | this.buttonInstall.Location = new System.Drawing.Point(440, 341);
104 | this.buttonInstall.Name = "buttonInstall";
105 | this.buttonInstall.Size = new System.Drawing.Size(112, 23);
106 | this.buttonInstall.TabIndex = 4;
107 | this.buttonInstall.Text = "Install / Update";
108 | this.buttonInstall.UseVisualStyleBackColor = true;
109 | this.buttonInstall.Click += new System.EventHandler(this.buttonInstall_Click);
110 | //
111 | // labelStatus
112 | //
113 | this.labelStatus.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)));
114 | this.labelStatus.AutoSize = true;
115 | this.labelStatus.Location = new System.Drawing.Point(7, 346);
116 | this.labelStatus.Name = "labelStatus";
117 | this.labelStatus.Size = new System.Drawing.Size(66, 13);
118 | this.labelStatus.TabIndex = 5;
119 | this.labelStatus.Text = "Status: Null";
120 | //
121 | // tabControlMain
122 | //
123 | this.tabControlMain.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
124 | | System.Windows.Forms.AnchorStyles.Left)
125 | | System.Windows.Forms.AnchorStyles.Right)));
126 | this.tabControlMain.Controls.Add(this.Plugins);
127 | this.tabControlMain.Controls.Add(this.Utilities);
128 | this.tabControlMain.Enabled = false;
129 | this.tabControlMain.Location = new System.Drawing.Point(10, 53);
130 | this.tabControlMain.Name = "tabControlMain";
131 | this.tabControlMain.SelectedIndex = 0;
132 | this.tabControlMain.Size = new System.Drawing.Size(544, 282);
133 | this.tabControlMain.TabIndex = 8;
134 | //
135 | // Plugins
136 | //
137 | this.Plugins.Controls.Add(this.listViewMods);
138 | this.Plugins.Location = new System.Drawing.Point(4, 22);
139 | this.Plugins.Name = "Plugins";
140 | this.Plugins.Padding = new System.Windows.Forms.Padding(3);
141 | this.Plugins.Size = new System.Drawing.Size(536, 256);
142 | this.Plugins.TabIndex = 0;
143 | this.Plugins.Text = "Plugins";
144 | this.Plugins.UseVisualStyleBackColor = true;
145 | //
146 | // listViewMods
147 | //
148 | this.listViewMods.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
149 | | System.Windows.Forms.AnchorStyles.Left)
150 | | System.Windows.Forms.AnchorStyles.Right)));
151 | this.listViewMods.CheckBoxes = true;
152 | this.listViewMods.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
153 | this.columnHeaderName,
154 | this.columnHeaderAuthor});
155 | this.listViewMods.ContextMenuStrip = this.contextMenuStripMain;
156 | this.listViewMods.FullRowSelect = true;
157 | this.listViewMods.HideSelection = false;
158 | this.listViewMods.Location = new System.Drawing.Point(6, 6);
159 | this.listViewMods.Name = "listViewMods";
160 | this.listViewMods.Size = new System.Drawing.Size(524, 244);
161 | this.listViewMods.TabIndex = 0;
162 | this.listViewMods.UseCompatibleStateImageBehavior = false;
163 | this.listViewMods.View = System.Windows.Forms.View.Details;
164 | this.listViewMods.ItemChecked += new System.Windows.Forms.ItemCheckedEventHandler(this.listViewMods_ItemChecked);
165 | this.listViewMods.ItemSelectionChanged += new System.Windows.Forms.ListViewItemSelectionChangedEventHandler(this.listViewMods_ItemSelectionChanged);
166 | this.listViewMods.DoubleClick += new System.EventHandler(this.listViewMods_DoubleClick);
167 | //
168 | // columnHeaderName
169 | //
170 | this.columnHeaderName.Text = "Name";
171 | this.columnHeaderName.Width = 321;
172 | //
173 | // columnHeaderAuthor
174 | //
175 | this.columnHeaderAuthor.Text = "Author";
176 | this.columnHeaderAuthor.Width = 162;
177 | //
178 | // contextMenuStripMain
179 | //
180 | this.contextMenuStripMain.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
181 | this.viewInfoToolStripMenuItem});
182 | this.contextMenuStripMain.Name = "contextMenuStripMain";
183 | this.contextMenuStripMain.Size = new System.Drawing.Size(124, 26);
184 | //
185 | // viewInfoToolStripMenuItem
186 | //
187 | this.viewInfoToolStripMenuItem.Name = "viewInfoToolStripMenuItem";
188 | this.viewInfoToolStripMenuItem.Size = new System.Drawing.Size(123, 22);
189 | this.viewInfoToolStripMenuItem.Text = "View Info";
190 | this.viewInfoToolStripMenuItem.Click += new System.EventHandler(this.viewInfoToolStripMenuItem_Click);
191 | //
192 | // Utilities
193 | //
194 | this.Utilities.Controls.Add(this.labelVersion);
195 | this.Utilities.Controls.Add(this.pictureBox1);
196 | this.Utilities.Controls.Add(this.buttonOpenWiki);
197 | this.Utilities.Controls.Add(this.buttonDiscordLink);
198 | this.Utilities.Controls.Add(this.groupBox1);
199 | this.Utilities.Controls.Add(this.buttonRestoreCosmetics);
200 | this.Utilities.Controls.Add(this.buttonRestoreMods);
201 | this.Utilities.Controls.Add(this.buttonBackupCosmetics);
202 | this.Utilities.Controls.Add(this.buttonBackupMods);
203 | this.Utilities.Controls.Add(this.buttonUninstallAll);
204 | this.Utilities.Location = new System.Drawing.Point(4, 22);
205 | this.Utilities.Name = "Utilities";
206 | this.Utilities.Size = new System.Drawing.Size(536, 256);
207 | this.Utilities.TabIndex = 1;
208 | this.Utilities.Text = "Utilities";
209 | this.Utilities.UseVisualStyleBackColor = true;
210 | //
211 | // pictureBox1
212 | //
213 | this.pictureBox1.Image = ((System.Drawing.Image)(resources.GetObject("pictureBox1.Image")));
214 | this.pictureBox1.Location = new System.Drawing.Point(170, 43);
215 | this.pictureBox1.Name = "pictureBox1";
216 | this.pictureBox1.Size = new System.Drawing.Size(186, 163);
217 | this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage;
218 | this.pictureBox1.TabIndex = 10;
219 | this.pictureBox1.TabStop = false;
220 | //
221 | // buttonOpenWiki
222 | //
223 | this.buttonOpenWiki.Location = new System.Drawing.Point(379, 183);
224 | this.buttonOpenWiki.Name = "buttonOpenWiki";
225 | this.buttonOpenWiki.Size = new System.Drawing.Size(134, 23);
226 | this.buttonOpenWiki.TabIndex = 9;
227 | this.buttonOpenWiki.Text = "Check out the guides!";
228 | this.buttonOpenWiki.UseVisualStyleBackColor = true;
229 | this.buttonOpenWiki.Click += new System.EventHandler(this.buttonOpenWiki_Click);
230 | //
231 | // buttonDiscordLink
232 | //
233 | this.buttonDiscordLink.Location = new System.Drawing.Point(379, 153);
234 | this.buttonDiscordLink.Name = "buttonDiscordLink";
235 | this.buttonDiscordLink.Size = new System.Drawing.Size(134, 23);
236 | this.buttonDiscordLink.TabIndex = 8;
237 | this.buttonDiscordLink.Text = "Join the Discord!";
238 | this.buttonDiscordLink.UseVisualStyleBackColor = true;
239 | this.buttonDiscordLink.Click += new System.EventHandler(this.buttonDiscordLink_Click);
240 | //
241 | // groupBox1
242 | //
243 | this.groupBox1.Controls.Add(this.buttonBepInEx);
244 | this.groupBox1.Controls.Add(this.buttonOpenConfig);
245 | this.groupBox1.Controls.Add(this.buttonOpenGameFolder);
246 | this.groupBox1.Controls.Add(this.labelOpen);
247 | this.groupBox1.Location = new System.Drawing.Point(373, 16);
248 | this.groupBox1.Name = "groupBox1";
249 | this.groupBox1.Size = new System.Drawing.Size(146, 130);
250 | this.groupBox1.TabIndex = 7;
251 | this.groupBox1.TabStop = false;
252 | //
253 | // buttonBepInEx
254 | //
255 | this.buttonBepInEx.Location = new System.Drawing.Point(6, 96);
256 | this.buttonBepInEx.Name = "buttonBepInEx";
257 | this.buttonBepInEx.Size = new System.Drawing.Size(134, 23);
258 | this.buttonBepInEx.TabIndex = 5;
259 | this.buttonBepInEx.Text = "BepInEx Folder";
260 | this.buttonBepInEx.UseVisualStyleBackColor = true;
261 | this.buttonBepInEx.Click += new System.EventHandler(this.buttonOpenBepInExFolder_Click);
262 | //
263 | // buttonOpenConfig
264 | //
265 | this.buttonOpenConfig.Location = new System.Drawing.Point(6, 67);
266 | this.buttonOpenConfig.Name = "buttonOpenConfig";
267 | this.buttonOpenConfig.Size = new System.Drawing.Size(134, 23);
268 | this.buttonOpenConfig.TabIndex = 5;
269 | this.buttonOpenConfig.Text = "Config Folder";
270 | this.buttonOpenConfig.UseVisualStyleBackColor = true;
271 | this.buttonOpenConfig.Click += new System.EventHandler(this.buttonOpenConfigFolder_Click);
272 | //
273 | // buttonOpenGameFolder
274 | //
275 | this.buttonOpenGameFolder.Location = new System.Drawing.Point(6, 38);
276 | this.buttonOpenGameFolder.Name = "buttonOpenGameFolder";
277 | this.buttonOpenGameFolder.Size = new System.Drawing.Size(134, 23);
278 | this.buttonOpenGameFolder.TabIndex = 5;
279 | this.buttonOpenGameFolder.Text = "Game Folder";
280 | this.buttonOpenGameFolder.UseVisualStyleBackColor = true;
281 | this.buttonOpenGameFolder.Click += new System.EventHandler(this.buttonOpenGameFolder_Click);
282 | //
283 | // labelOpen
284 | //
285 | this.labelOpen.AutoSize = true;
286 | this.labelOpen.Location = new System.Drawing.Point(23, 15);
287 | this.labelOpen.Name = "labelOpen";
288 | this.labelOpen.Size = new System.Drawing.Size(99, 13);
289 | this.labelOpen.TabIndex = 6;
290 | this.labelOpen.Text = "Important Folders";
291 | //
292 | // buttonRestoreCosmetics
293 | //
294 | this.buttonRestoreCosmetics.Location = new System.Drawing.Point(14, 173);
295 | this.buttonRestoreCosmetics.Name = "buttonRestoreCosmetics";
296 | this.buttonRestoreCosmetics.Size = new System.Drawing.Size(132, 37);
297 | this.buttonRestoreCosmetics.TabIndex = 4;
298 | this.buttonRestoreCosmetics.Text = "Restore Cosmetics from Backup";
299 | this.buttonRestoreCosmetics.UseVisualStyleBackColor = true;
300 | this.buttonRestoreCosmetics.Click += new System.EventHandler(this.buttonRestoreCosmetics_Click);
301 | //
302 | // buttonRestoreMods
303 | //
304 | this.buttonRestoreMods.Location = new System.Drawing.Point(14, 130);
305 | this.buttonRestoreMods.Name = "buttonRestoreMods";
306 | this.buttonRestoreMods.Size = new System.Drawing.Size(132, 37);
307 | this.buttonRestoreMods.TabIndex = 3;
308 | this.buttonRestoreMods.Text = "Restore Mods from Backup";
309 | this.buttonRestoreMods.UseVisualStyleBackColor = true;
310 | this.buttonRestoreMods.Click += new System.EventHandler(this.buttonRestoreMods_Click);
311 | //
312 | // buttonBackupCosmetics
313 | //
314 | this.buttonBackupCosmetics.Location = new System.Drawing.Point(14, 101);
315 | this.buttonBackupCosmetics.Name = "buttonBackupCosmetics";
316 | this.buttonBackupCosmetics.Size = new System.Drawing.Size(132, 23);
317 | this.buttonBackupCosmetics.TabIndex = 2;
318 | this.buttonBackupCosmetics.Text = "Backup Cosmetics";
319 | this.buttonBackupCosmetics.UseVisualStyleBackColor = true;
320 | this.buttonBackupCosmetics.Click += new System.EventHandler(this.buttonBackupCosmetics_Click);
321 | //
322 | // buttonBackupMods
323 | //
324 | this.buttonBackupMods.Location = new System.Drawing.Point(14, 72);
325 | this.buttonBackupMods.Name = "buttonBackupMods";
326 | this.buttonBackupMods.Size = new System.Drawing.Size(132, 23);
327 | this.buttonBackupMods.TabIndex = 1;
328 | this.buttonBackupMods.Text = "Backup Mods Folder";
329 | this.buttonBackupMods.UseVisualStyleBackColor = true;
330 | this.buttonBackupMods.Click += new System.EventHandler(this.buttonBackupMods_Click);
331 | //
332 | // buttonUninstallAll
333 | //
334 | this.buttonUninstallAll.Location = new System.Drawing.Point(14, 43);
335 | this.buttonUninstallAll.Name = "buttonUninstallAll";
336 | this.buttonUninstallAll.Size = new System.Drawing.Size(132, 23);
337 | this.buttonUninstallAll.TabIndex = 0;
338 | this.buttonUninstallAll.Text = "Uninstall All Mods";
339 | this.buttonUninstallAll.UseVisualStyleBackColor = true;
340 | this.buttonUninstallAll.Click += new System.EventHandler(this.buttonUninstallAll_Click);
341 | //
342 | // buttonModInfo
343 | //
344 | this.buttonModInfo.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
345 | this.buttonModInfo.Enabled = false;
346 | this.buttonModInfo.Location = new System.Drawing.Point(322, 341);
347 | this.buttonModInfo.Name = "buttonModInfo";
348 | this.buttonModInfo.Size = new System.Drawing.Size(112, 23);
349 | this.buttonModInfo.TabIndex = 9;
350 | this.buttonModInfo.Text = "View Mod Info";
351 | this.buttonModInfo.UseVisualStyleBackColor = true;
352 | this.buttonModInfo.Click += new System.EventHandler(this.buttonModInfo_Click);
353 | //
354 | // labelVersion
355 | //
356 | this.labelVersion.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)));
357 | this.labelVersion.AutoSize = true;
358 | this.labelVersion.Location = new System.Drawing.Point(188, 209);
359 | this.labelVersion.Name = "labelVersion";
360 | this.labelVersion.Size = new System.Drawing.Size(119, 13);
361 | this.labelVersion.TabIndex = 11;
362 | this.labelVersion.Text = "Monke Mod Manager";
363 | this.labelVersion.TextAlign = System.Drawing.ContentAlignment.BottomCenter;
364 | this.labelVersion.UseMnemonic = false;
365 | //
366 | // FormMain
367 | //
368 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
369 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
370 | this.ClientSize = new System.Drawing.Size(566, 376);
371 | this.Controls.Add(this.buttonModInfo);
372 | this.Controls.Add(this.tabControlMain);
373 | this.Controls.Add(this.labelStatus);
374 | this.Controls.Add(this.buttonInstall);
375 | this.Controls.Add(this.label1);
376 | this.Controls.Add(this.buttonFolderBrowser);
377 | this.Controls.Add(this.textBoxDirectory);
378 | this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
379 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
380 | this.Name = "FormMain";
381 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
382 | this.Text = "Monke Mod Manager";
383 | this.Load += new System.EventHandler(this.FormMain_Load);
384 | this.tabControlMain.ResumeLayout(false);
385 | this.Plugins.ResumeLayout(false);
386 | this.contextMenuStripMain.ResumeLayout(false);
387 | this.Utilities.ResumeLayout(false);
388 | this.Utilities.PerformLayout();
389 | ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
390 | this.groupBox1.ResumeLayout(false);
391 | this.groupBox1.PerformLayout();
392 | this.ResumeLayout(false);
393 | this.PerformLayout();
394 |
395 | }
396 |
397 | #endregion
398 |
399 | private System.Windows.Forms.TextBox textBoxDirectory;
400 | private System.Windows.Forms.Button buttonFolderBrowser;
401 | private System.Windows.Forms.Label label1;
402 | private System.Windows.Forms.Button buttonInstall;
403 | private System.Windows.Forms.Label labelStatus;
404 | private System.Windows.Forms.TabControl tabControlMain;
405 | private System.Windows.Forms.TabPage Plugins;
406 | private System.Windows.Forms.ListView listViewMods;
407 | private System.Windows.Forms.ColumnHeader columnHeaderName;
408 | private System.Windows.Forms.ColumnHeader columnHeaderAuthor;
409 | private System.Windows.Forms.ContextMenuStrip contextMenuStripMain;
410 | private System.Windows.Forms.ToolStripMenuItem viewInfoToolStripMenuItem;
411 | private System.Windows.Forms.Button buttonModInfo;
412 | private System.Windows.Forms.TabPage Utilities;
413 | private System.Windows.Forms.Button buttonUninstallAll;
414 | private System.Windows.Forms.Button buttonBackupMods;
415 | private System.Windows.Forms.Button buttonBackupCosmetics;
416 | private System.Windows.Forms.Button buttonRestoreMods;
417 | private System.Windows.Forms.Button buttonRestoreCosmetics;
418 | private System.Windows.Forms.Label labelOpen;
419 | private System.Windows.Forms.GroupBox groupBox1;
420 | private System.Windows.Forms.Button buttonBepInEx;
421 | private System.Windows.Forms.Button buttonOpenConfig;
422 | private System.Windows.Forms.Button buttonOpenGameFolder;
423 | private System.Windows.Forms.Button buttonOpenWiki;
424 | private System.Windows.Forms.Button buttonDiscordLink;
425 | private System.Windows.Forms.PictureBox pictureBox1;
426 | private System.Windows.Forms.Label labelVersion;
427 | }
428 | }
429 |
430 |
--------------------------------------------------------------------------------
/MonkeModManager/FormMain.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Windows.Forms;
6 | using System.Net;
7 | using System.IO;
8 | using System.IO.Compression;
9 | using System.Threading;
10 | using MonkeModManager.Internals;
11 | using System.Diagnostics;
12 | using System.Runtime.InteropServices;
13 | using MonkeModManager.Internals.SimpleJSON;
14 | using System.Text.RegularExpressions;
15 |
16 | namespace MonkeModManager
17 | {
18 | public partial class FormMain : Form
19 | {
20 |
21 | private const string BaseEndpoint = "https://api.github.com/repos/";
22 | private const Int16 CurrentVersion = 4;
23 | private List releases;
24 | Dictionary groups = new Dictionary();
25 | private string InstallDirectory = @"";
26 | public bool isSteam = true;
27 | public bool platformDetected = false;
28 |
29 | public FormMain()
30 | {
31 | InitializeComponent();
32 | }
33 |
34 | private void FormMain_Load(object sender, EventArgs e)
35 | {
36 | LocationHandler();
37 | ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
38 | releases = new List();
39 | var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString();
40 | labelVersion.Text = "Monke Mod Manager v" + version.Substring(0, version.Length - 2);
41 | new Thread(() =>
42 | {
43 | LoadRequiredPlugins();
44 | }).Start();
45 | }
46 |
47 | #region ReleaseHandling
48 |
49 | private void LoadReleases()
50 | {
51 | #if !DEBUG
52 | var decoded = JSON.Parse(DownloadSite("https://raw.githubusercontent.com/DeadlyKitten/MonkeModManager/master/mods.json"));
53 | #else
54 | var decoded = JSON.Parse(File.ReadAllText("C:/Users/Steven/Desktop/testmods.json"));
55 | #endif
56 | var allMods = decoded["mods"].AsArray;
57 | var allGroups = decoded["groups"].AsArray;
58 |
59 | for (int i = 0; i < allMods.Count; i++)
60 | {
61 | JSONNode current = allMods[i];
62 | ReleaseInfo release = new ReleaseInfo(current["name"], current["author"], current["gitPath"], current["releaseId"], current["tag"], current["group"], current["installPath"], current["dependencies"].AsArray);
63 | UpdateReleaseInfo(ref release);
64 | releases.Add(release);
65 | }
66 |
67 |
68 | allGroups.Linq.OrderBy(x => x.Value["rank"]);
69 | for (int i = 0; i < allGroups.Count; i++)
70 | {
71 | JSONNode current = allGroups[i];
72 | if (releases.Any(x => x.Group == current["name"]))
73 | {
74 | groups.Add(current["name"], groups.Count());
75 | }
76 | }
77 | groups.Add("Uncategorized", groups.Count());
78 |
79 | foreach (ReleaseInfo release in releases)
80 | {
81 | foreach (string dep in release.Dependencies)
82 | {
83 | releases.Where(x => x.Name == dep).FirstOrDefault()?.Dependents.Add(release.Name);
84 | }
85 | }
86 | //WriteReleasesToDisk();
87 | }
88 |
89 | private void LoadRequiredPlugins()
90 | {
91 | CheckVersion();
92 | UpdateStatus("Getting latest version info...");
93 | LoadReleases();
94 | this.Invoke((MethodInvoker)(() =>
95 | {//Invoke so we can call from current thread
96 | //Update checkbox's text
97 | Dictionary includedGroups = new Dictionary();
98 |
99 | for (int i = 0; i < groups.Count(); i++)
100 | {
101 | var key = groups.First(x => x.Value == i).Key;
102 | var value = listViewMods.Groups.Add(new ListViewGroup(key, HorizontalAlignment.Left));
103 | groups[key] = value;
104 | }
105 |
106 | foreach (ReleaseInfo release in releases)
107 | {
108 | ListViewItem item = new ListViewItem();
109 | item.Text = release.Name;
110 | if (!String.IsNullOrEmpty(release.Version)) item.Text = $"{release.Name} - {release.Version}";
111 | if (!String.IsNullOrEmpty(release.Tag)) { item.Text = string.Format("{0} - ({1})",release.Name, release.Tag); };
112 | item.SubItems.Add(release.Author);
113 | item.Tag = release;
114 | if (release.Install)
115 | {
116 | listViewMods.Items.Add(item);
117 | }
118 | CheckDefaultMod(release, item);
119 |
120 | if (release.Group == null || !groups.ContainsKey(release.Group))
121 | {
122 | item.Group = listViewMods.Groups[groups["Uncategorized"]];
123 | }
124 | else if (groups.ContainsKey(release.Group))
125 | {
126 | int index = groups[release.Group];
127 | item.Group = listViewMods.Groups[index];
128 | }
129 | else
130 | {
131 | //int index = listViewMods.Groups.Add(new ListViewGroup(release.Group, HorizontalAlignment.Left));
132 | //item.Group = listViewMods.Groups[index];
133 | }
134 | }
135 |
136 | tabControlMain.Enabled = true;
137 | buttonInstall.Enabled = true;
138 |
139 | }));
140 |
141 | UpdateStatus("Release info updated!");
142 |
143 | }
144 |
145 | private void UpdateReleaseInfo(ref ReleaseInfo release)
146 | {
147 | Thread.Sleep(100); //So we don't get rate limited by github
148 |
149 | string releaseFormatted = BaseEndpoint + release.GitPath + "/releases";
150 | var rootNode = JSON.Parse(DownloadSite(releaseFormatted))[0];
151 |
152 | release.Version = rootNode["tag_name"];
153 |
154 | var assetsNode = rootNode["assets"];
155 | var downloadReleaseNode = assetsNode[release.ReleaseId];
156 | release.Link = downloadReleaseNode["browser_download_url"];
157 |
158 | var uploaderNode = downloadReleaseNode["uploader"];
159 | if (release.Author.Equals(String.Empty)) release.Author = uploaderNode["login"];
160 | }
161 |
162 | #endregion // ReleaseHandling
163 |
164 | #region Installation
165 |
166 | private void Install()
167 | {
168 | ChangeInstallButtonState(false);
169 | UpdateStatus("Starting install sequence...");
170 | foreach (ReleaseInfo release in releases)
171 | {
172 | if (release.Install)
173 | {
174 | UpdateStatus(string.Format("Downloading...{0}", release.Name));
175 | byte[] file = DownloadFile(release.Link);
176 | UpdateStatus(string.Format("Installing...{0}", release.Name));
177 | string fileName = Path.GetFileName(release.Link);
178 | if (Path.GetExtension(fileName).Equals(".dll"))
179 | {
180 | string dir;
181 | if (release.InstallLocation == null)
182 | {
183 | dir = Path.Combine(InstallDirectory, @"BepInEx\plugins", Regex.Replace(release.Name, @"\s+", string.Empty));
184 | if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
185 | }
186 | else
187 | {
188 | dir = Path.Combine(InstallDirectory, release.InstallLocation);
189 | }
190 | File.WriteAllBytes(Path.Combine(dir, fileName), file);
191 |
192 | var dllFile = Path.Combine(InstallDirectory, @"BepInEx\plugins", fileName);
193 | if (File.Exists(dllFile))
194 | {
195 | File.Delete(dllFile);
196 | }
197 | }
198 | else
199 | {
200 | UnzipFile(file, (release.InstallLocation != null) ? Path.Combine(InstallDirectory, release.InstallLocation) : InstallDirectory);
201 | }
202 | UpdateStatus(string.Format("Installed {0}!", release.Name));
203 | }
204 | }
205 | UpdateStatus("Install complete!");
206 | ChangeInstallButtonState(true);
207 | }
208 |
209 | #endregion // Installation
210 |
211 | #region UIEvents
212 |
213 | private void buttonInstall_Click(object sender, EventArgs e)
214 | {
215 | new Thread(() =>
216 | {
217 | Install();
218 | }).Start();
219 | }
220 |
221 | private void buttonFolderBrowser_Click(object sender, EventArgs e)
222 | {
223 | using (var fileDialog = new OpenFileDialog())
224 | {
225 | fileDialog.FileName = "Gorilla Tag.exe";
226 | fileDialog.Filter = "Exe Files (.exe)|*.exe|All Files (*.*)|*.*";
227 | fileDialog.FilterIndex = 1;
228 | if (fileDialog.ShowDialog() == DialogResult.OK)
229 | {
230 | string path = fileDialog.FileName;
231 | if (Path.GetFileName(path).Equals("Gorilla Tag.exe"))
232 | {
233 | InstallDirectory = Path.GetDirectoryName(path);
234 | textBoxDirectory.Text = InstallDirectory;
235 | }
236 | else
237 | {
238 | MessageBox.Show("That's not Gorilla Tag.exe! please try again!", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
239 | }
240 |
241 | }
242 |
243 | }
244 | }
245 |
246 | private void listViewMods_ItemChecked(object sender, ItemCheckedEventArgs e)
247 | {
248 | ReleaseInfo release = (ReleaseInfo)e.Item.Tag;
249 |
250 | if (release.Dependencies.Count > 0)
251 | {
252 | foreach (ListViewItem item in listViewMods.Items)
253 | {
254 | var plugin = (ReleaseInfo)item.Tag;
255 |
256 | if (plugin.Name == release.Name) continue;
257 |
258 | // if this depends on plugin
259 | if (release.Dependencies.Contains(plugin.Name))
260 | {
261 | if (e.Item.Checked)
262 | {
263 | item.Checked = true;
264 | item.ForeColor = System.Drawing.Color.DimGray;
265 | }
266 | else
267 | {
268 | release.Install = false;
269 | if (releases.Count(x => plugin.Dependents.Contains(x.Name) && x.Install) <= 1)
270 | {
271 | item.Checked = false;
272 | item.ForeColor = System.Drawing.Color.Black;
273 | }
274 | }
275 | }
276 | }
277 | }
278 |
279 | // don't allow user to uncheck if a dependent is checked
280 | if (release.Dependents.Count > 0)
281 | {
282 | if (releases.Count(x => release.Dependents.Contains(x.Name) && x.Install) > 0)
283 | {
284 | e.Item.Checked = true;
285 | }
286 | }
287 |
288 | if (release.Name.Contains("BepInEx")) { e.Item.Checked = true; };
289 | release.Install = e.Item.Checked;
290 | }
291 |
292 | private void listViewMods_DoubleClick(object sender, EventArgs e)
293 | {
294 | OpenLinkFromRelease();
295 | }
296 |
297 | private void buttonModInfo_Click(object sender, EventArgs e)
298 | {
299 | OpenLinkFromRelease();
300 | }
301 |
302 | private void viewInfoToolStripMenuItem_Click(object sender, EventArgs e)
303 | {
304 | OpenLinkFromRelease();
305 | }
306 |
307 | private void listViewMods_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
308 | {
309 | if (listViewMods.SelectedItems.Count > 0)
310 | {
311 | buttonModInfo.Enabled = true;
312 | }
313 | else
314 | {
315 | buttonModInfo.Enabled = false;
316 | }
317 | }
318 |
319 | private void buttonUninstallAll_Click(object sender, EventArgs e)
320 | {
321 | var confirmResult = MessageBox.Show(
322 | "You are about to delete all your mods (including hats and materials). This cannot be undone!\n\nAre you sure you wish to continue?",
323 | "Confirm Delete",
324 | MessageBoxButtons.YesNo);
325 |
326 | if (confirmResult == DialogResult.Yes)
327 | {
328 | UpdateStatus("Uninstalling all mods");
329 |
330 | var pluginsPath = Path.Combine(InstallDirectory, @"BepInEx\plugins");
331 |
332 | try
333 | {
334 | foreach (var d in Directory.GetDirectories(pluginsPath))
335 | {
336 | Directory.Delete(d, true);
337 | }
338 |
339 | foreach (var f in Directory.GetFiles(pluginsPath))
340 | {
341 | File.Delete(f);
342 | }
343 | }
344 | catch (Exception ex)
345 | {
346 | MessageBox.Show("Something went wrong!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
347 | UpdateStatus("Failed to uninstall mods.");
348 | return;
349 | }
350 |
351 | UpdateStatus("All mods uninstalled successfully!");
352 | }
353 | }
354 |
355 | private void buttonBackupMods_Click(object sender, EventArgs e)
356 | {
357 | var pluginsPath = Path.Combine(InstallDirectory, @"BepInEx\plugins");
358 |
359 | SaveFileDialog saveFileDialog = new SaveFileDialog
360 | {
361 | InitialDirectory = InstallDirectory,
362 | FileName = $"Mod Backup",
363 | Filter = "ZIP Folder (.zip)|*.zip",
364 | Title = "Save Mod Backup"
365 | };
366 |
367 | if (saveFileDialog.ShowDialog() == DialogResult.OK && saveFileDialog.FileName != "")
368 | {
369 | UpdateStatus("Backing up mods...");
370 | try
371 | {
372 | if (File.Exists(saveFileDialog.FileName)) File.Delete(saveFileDialog.FileName);
373 | ZipFile.CreateFromDirectory(pluginsPath, saveFileDialog.FileName);
374 | }
375 | catch (Exception ex)
376 | {
377 | MessageBox.Show("Something went wrong!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
378 | UpdateStatus("Failed to back up mods.");
379 | return;
380 | }
381 | UpdateStatus("Successfully backed up mods!");
382 | }
383 |
384 |
385 | }
386 |
387 | private void buttonBackupCosmetics_Click(object sender, EventArgs e)
388 | {
389 | var pluginsPath = Path.Combine(InstallDirectory, @"BepInEx\plugins");
390 |
391 | SaveFileDialog saveFileDialog = new SaveFileDialog
392 | {
393 | InitialDirectory = InstallDirectory,
394 | FileName = $"Cosmetics Backup",
395 | Filter = "ZIP Folder (.zip)|*.zip",
396 | Title = "Save Cosmetics Backup"
397 | };
398 |
399 | if (saveFileDialog.ShowDialog() == DialogResult.OK && saveFileDialog.FileName != "")
400 | {
401 | UpdateStatus("Backing up cosmetics...");
402 | if (File.Exists(saveFileDialog.FileName)) File.Delete(saveFileDialog.FileName);
403 | try
404 | {
405 | ZipFile.CreateFromDirectory(Path.Combine(pluginsPath, @"GorillaCosmetics\Hats"), saveFileDialog.FileName, CompressionLevel.Optimal, true);
406 | using (ZipArchive archive = ZipFile.Open(saveFileDialog.FileName, ZipArchiveMode.Update))
407 | {
408 | foreach (var f in Directory.GetFiles(Path.Combine(pluginsPath, @"GorillaCosmetics\Materials")))
409 | {
410 | archive.CreateEntryFromFile(f, $"{Path.Combine("Materials", Path.GetFileName(f))}");
411 | }
412 | }
413 | }
414 | catch (Exception ex)
415 | {
416 | MessageBox.Show("Something went wrong!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
417 | UpdateStatus("Failed to restore cosmetics.");
418 | return;
419 | }
420 | UpdateStatus("Backed up cosmetics!");
421 | }
422 | }
423 |
424 | private void buttonRestoreMods_Click(object sender, EventArgs e)
425 | {
426 | using (var fileDialog = new OpenFileDialog())
427 | {
428 | fileDialog.InitialDirectory = InstallDirectory;
429 | fileDialog.FileName = "Mod Backup.zip";
430 | fileDialog.Filter = "ZIP Folder (.zip)|*.zip";
431 | fileDialog.FilterIndex = 1;
432 | if (fileDialog.ShowDialog() == DialogResult.OK)
433 | {
434 | if (!Path.GetExtension(fileDialog.FileName).Equals(".zip", StringComparison.InvariantCultureIgnoreCase))
435 | {
436 | MessageBox.Show("Invalid file!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
437 | UpdateStatus("Failed to restore mods.");
438 | return;
439 | }
440 | var pluginsPath = Path.Combine(InstallDirectory, @"BepInEx\plugins");
441 | try
442 | {
443 | UpdateStatus("Restoring mods...");
444 | using (var archive = ZipFile.OpenRead(fileDialog.FileName))
445 | {
446 | foreach (var entry in archive.Entries)
447 | {
448 | var directory = Path.Combine(InstallDirectory, @"BepInEx\plugins", Path.GetDirectoryName(entry.FullName));
449 | if (!Directory.Exists(directory))
450 | {
451 | Directory.CreateDirectory(directory);
452 | }
453 |
454 | entry.ExtractToFile(Path.Combine(pluginsPath, entry.FullName), true);
455 | }
456 | }
457 | UpdateStatus("Successfully restored mods!");
458 | }
459 | catch (Exception ex)
460 | {
461 | MessageBox.Show("Something went wrong!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
462 | UpdateStatus("Failed to restore mods.");
463 | }
464 | }
465 | }
466 | }
467 |
468 | private void buttonRestoreCosmetics_Click(object sender, EventArgs e)
469 | {
470 | using (var fileDialog = new OpenFileDialog())
471 | {
472 | fileDialog.InitialDirectory = InstallDirectory;
473 | fileDialog.FileName = "Cosmetics Backup.zip";
474 | fileDialog.Filter = "ZIP Folder (.zip)|*.zip";
475 | fileDialog.FilterIndex = 1;
476 | if (fileDialog.ShowDialog() == DialogResult.OK)
477 | {
478 | if (!Path.GetExtension(fileDialog.FileName).Equals(".zip", StringComparison.InvariantCultureIgnoreCase))
479 | {
480 | MessageBox.Show("Invalid file!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
481 | UpdateStatus("Failed to restore co0smetics.");
482 | return;
483 | }
484 | var cosmeticsPath = Path.Combine(InstallDirectory, @"BepInEx\plugins\GorillaCosmetics");
485 | try
486 | {
487 | UpdateStatus("Restoring cosmetics...");
488 | using (var archive = ZipFile.OpenRead(fileDialog.FileName))
489 | {
490 | foreach (var entry in archive.Entries)
491 | {
492 | var directory = Path.Combine(InstallDirectory, @"BepInEx\plugins\GorillaCosmetics", Path.GetDirectoryName(entry.FullName));
493 | if (!Directory.Exists(directory))
494 | {
495 | Directory.CreateDirectory(directory);
496 | }
497 |
498 | entry.ExtractToFile(Path.Combine(cosmeticsPath, entry.FullName), true);
499 | }
500 | }
501 | UpdateStatus("Successfully restored cosmetics!");
502 | }
503 | catch
504 | {
505 | MessageBox.Show("Something went wrong!", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
506 | UpdateStatus("Failed to restore cosmetics.");
507 | }
508 | }
509 | }
510 | }
511 |
512 | #region Folders
513 |
514 | private void buttonOpenGameFolder_Click(object sender, EventArgs e)
515 | {
516 | if (Directory.Exists(InstallDirectory))
517 | Process.Start(InstallDirectory);
518 | }
519 |
520 | private void buttonOpenConfigFolder_Click(object sender, EventArgs e)
521 | {
522 | var configDirectory = Path.Combine(InstallDirectory, @"BepInEx\config");
523 | if (Directory.Exists(configDirectory))
524 | Process.Start(configDirectory);
525 | }
526 |
527 | private void buttonOpenBepInExFolder_Click(object sender, EventArgs e)
528 | {
529 | var BepInExDirectory = Path.Combine(InstallDirectory, "BepInEx");
530 | if (Directory.Exists(BepInExDirectory))
531 | Process.Start(BepInExDirectory);
532 | }
533 |
534 | #endregion // Folders
535 |
536 | private void buttonOpenWiki_Click(object sender, EventArgs e)
537 | {
538 | Process.Start("https://gorillatagmodding.burrito.software/");
539 | }
540 |
541 | private void buttonDiscordLink_Click(object sender, EventArgs e)
542 | {
543 | Process.Start("https://discord.gg/ux4ZbBC6JQ");
544 | }
545 |
546 | #endregion // UIEvents
547 |
548 | #region Helpers
549 |
550 | private CookieContainer PermCookie;
551 | private string DownloadSite(string URL)
552 | {
553 | try
554 | {
555 | if (PermCookie == null) { PermCookie = new CookieContainer(); }
556 | HttpWebRequest RQuest = (HttpWebRequest)HttpWebRequest.Create(URL);
557 | RQuest.Method = "GET";
558 | RQuest.KeepAlive = true;
559 | RQuest.CookieContainer = PermCookie;
560 | RQuest.ContentType = "application/x-www-form-urlencoded";
561 | RQuest.Referer = "";
562 | RQuest.UserAgent = "Monke-Mod-Manager";
563 | RQuest.Proxy = null;
564 | #if DEBUG
565 | RQuest.Headers.Add("Authorization", $"Token {File.ReadAllText("../../token.txt")}");
566 | #endif
567 | HttpWebResponse Response = (HttpWebResponse)RQuest.GetResponse();
568 | StreamReader Sr = new StreamReader(Response.GetResponseStream());
569 | string Code = Sr.ReadToEnd();
570 | Sr.Close();
571 | return Code;
572 | }
573 | catch (Exception ex)
574 | {
575 | if (ex.Message.Contains("403"))
576 | {
577 | MessageBox.Show("Failed to update version info, GitHub has rate limited you, please check back in 15 - 30 minutes", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
578 | }
579 | else
580 | {
581 | MessageBox.Show("Failed to update version info, please check your internet connection", this.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
582 | }
583 | Process.GetCurrentProcess().Kill();
584 | return null;
585 | }
586 | }
587 |
588 | private void UnzipFile(byte[] data, string directory)
589 | {
590 | using (MemoryStream ms = new MemoryStream(data))
591 | {
592 | using (var unzip = new Unzip(ms))
593 | {
594 | unzip.ExtractToDirectory(directory);
595 | }
596 | }
597 | }
598 |
599 | private byte[] DownloadFile(string url)
600 | {
601 | WebClient client = new WebClient();
602 | client.Proxy = null;
603 | return client.DownloadData(url);
604 | }
605 |
606 | private void UpdateStatus(string status)
607 | {
608 | string formattedText = string.Format("Status: {0}", status);
609 | this.Invoke((MethodInvoker)(() =>
610 | { //Invoke so we can call from any thread
611 | labelStatus.Text = formattedText;
612 | }));
613 | }
614 |
615 | private void NotFoundHandler()
616 | {
617 | bool found = false;
618 | while (found == false)
619 | {
620 | using (var fileDialog = new OpenFileDialog())
621 | {
622 | fileDialog.FileName = "Gorilla Tag.exe";
623 | fileDialog.Filter = "Exe Files (.exe)|*.exe|All Files (*.*)|*.*";
624 | fileDialog.FilterIndex = 1;
625 | if (fileDialog.ShowDialog() == DialogResult.OK)
626 | {
627 | string path = fileDialog.FileName;
628 | if (Path.GetFileName(path).Equals("Gorilla Tag.exe"))
629 | {
630 | InstallDirectory = Path.GetDirectoryName(path);
631 | textBoxDirectory.Text = InstallDirectory;
632 | found = true;
633 | }
634 | else
635 | {
636 | MessageBox.Show("That's not Gorilla Tag.exe! please try again!", "Error!", MessageBoxButtons.OK, MessageBoxIcon.Error);
637 | }
638 | }
639 | else
640 | {
641 | Process.GetCurrentProcess().Kill();
642 | }
643 | }
644 | }
645 | }
646 |
647 | private void CheckVersion()
648 | {
649 | UpdateStatus("Checking for updates...");
650 | Int16 version = Convert.ToInt16(DownloadSite("https://raw.githubusercontent.com/DeadlyKitten/MonkeModManager/master/update.txt"));
651 | if (version > CurrentVersion)
652 | {
653 | this.Invoke((MethodInvoker)(() =>
654 | {
655 | MessageBox.Show("Your version of the mod installer is outdated! Please download the new one!", "Update available!", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
656 | Process.Start("https://github.com/DeadlyKitten/MonkeModManager/releases/latest");
657 | Process.GetCurrentProcess().Kill();
658 | Environment.Exit(0);
659 | }));
660 | }
661 | }
662 |
663 | private void ChangeInstallButtonState(bool enabled)
664 | {
665 | this.Invoke((MethodInvoker)(() =>
666 | {
667 | buttonInstall.Enabled = enabled;
668 | }));
669 | }
670 |
671 | private void OpenLinkFromRelease()
672 | {
673 | if (listViewMods.SelectedItems.Count > 0)
674 | {
675 | ReleaseInfo release = (ReleaseInfo)listViewMods.SelectedItems[0].Tag;
676 | UpdateStatus($"Opening GitHub page for {release.Name}");
677 | Process.Start(string.Format("https://github.com/{0}", release.GitPath));
678 | }
679 |
680 | }
681 |
682 | #endregion // Helpers
683 |
684 | #region Registry
685 |
686 | private void LocationHandler()
687 | {
688 | string steam = GetSteamLocation();
689 | if (steam != null)
690 | {
691 | if (Directory.Exists(steam))
692 | {
693 | if (File.Exists(steam + @"\Gorilla Tag.exe"))
694 | {
695 | textBoxDirectory.Text = steam;
696 | InstallDirectory = steam;
697 | platformDetected = true;
698 | return;
699 | }
700 | }
701 | }
702 | ShowErrorFindingDirectoryMessage();
703 | }
704 | private void ShowErrorFindingDirectoryMessage()
705 | {
706 | MessageBox.Show("We couldn't seem to find your Gorilla Tag installation, please press \"OK\" and point us to it", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
707 | NotFoundHandler();
708 | this.TopMost = true;
709 | }
710 | private string GetSteamLocation()
711 | {
712 | string path = RegistryWOW6432.GetRegKey64(RegHive.HKEY_LOCAL_MACHINE, @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 1533390", @"InstallLocation");
713 | if (path != null)
714 | {
715 | path = path + @"\";
716 | }
717 | return path;
718 | }
719 | private void CheckDefaultMod(ReleaseInfo release, ListViewItem item)
720 | {
721 | if (release.Name.Contains("BepInEx"))
722 | {
723 | item.Checked = true;
724 | item.ForeColor = System.Drawing.Color.DimGray;
725 | }
726 | else
727 | {
728 | release.Install = false;
729 | }
730 | }
731 | #endregion // Registry
732 |
733 | #region RegHelper
734 | enum RegSAM
735 | {
736 | QueryValue = 0x0001,
737 | SetValue = 0x0002,
738 | CreateSubKey = 0x0004,
739 | EnumerateSubKeys = 0x0008,
740 | Notify = 0x0010,
741 | CreateLink = 0x0020,
742 | WOW64_32Key = 0x0200,
743 | WOW64_64Key = 0x0100,
744 | WOW64_Res = 0x0300,
745 | Read = 0x00020019,
746 | Write = 0x00020006,
747 | Execute = 0x00020019,
748 | AllAccess = 0x000f003f
749 | }
750 |
751 | static class RegHive
752 | {
753 | public static UIntPtr HKEY_LOCAL_MACHINE = new UIntPtr(0x80000002u);
754 | public static UIntPtr HKEY_CURRENT_USER = new UIntPtr(0x80000001u);
755 | }
756 |
757 | static class RegistryWOW6432
758 | {
759 | [DllImport("Advapi32.dll")]
760 | static extern uint RegOpenKeyEx(UIntPtr hKey, string lpSubKey, uint ulOptions, int samDesired, out int phkResult);
761 |
762 | [DllImport("Advapi32.dll")]
763 | static extern uint RegCloseKey(int hKey);
764 |
765 | [DllImport("advapi32.dll", EntryPoint = "RegQueryValueEx")]
766 | public static extern int RegQueryValueEx(int hKey, string lpValueName, int lpReserved, ref uint lpType, System.Text.StringBuilder lpData, ref uint lpcbData);
767 |
768 | static public string GetRegKey64(UIntPtr inHive, String inKeyName, string inPropertyName)
769 | {
770 | return GetRegKey64(inHive, inKeyName, RegSAM.WOW64_64Key, inPropertyName);
771 | }
772 |
773 | static public string GetRegKey32(UIntPtr inHive, String inKeyName, string inPropertyName)
774 | {
775 | return GetRegKey64(inHive, inKeyName, RegSAM.WOW64_32Key, inPropertyName);
776 | }
777 |
778 | static public string GetRegKey64(UIntPtr inHive, String inKeyName, RegSAM in32or64key, string inPropertyName)
779 | {
780 | //UIntPtr HKEY_LOCAL_MACHINE = (UIntPtr)0x80000002;
781 | int hkey = 0;
782 |
783 | try
784 | {
785 | uint lResult = RegOpenKeyEx(RegHive.HKEY_LOCAL_MACHINE, inKeyName, 0, (int)RegSAM.QueryValue | (int)in32or64key, out hkey);
786 | if (0 != lResult) return null;
787 | uint lpType = 0;
788 | uint lpcbData = 1024;
789 | StringBuilder AgeBuffer = new StringBuilder(1024);
790 | RegQueryValueEx(hkey, inPropertyName, 0, ref lpType, AgeBuffer, ref lpcbData);
791 | string Age = AgeBuffer.ToString();
792 | return Age;
793 | }
794 | finally
795 | {
796 | if (0 != hkey) RegCloseKey(hkey);
797 | }
798 | }
799 | }
800 |
801 | #endregion // RegHelper
802 |
803 | }
804 |
805 | }
806 |
--------------------------------------------------------------------------------
/MonkeModManager/FormSelectPlatform.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace MonkeModManager
2 | {
3 | partial class FormSelectPlatform
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.radioButtonSteam = new System.Windows.Forms.RadioButton();
32 | this.radioButtonOculus = new System.Windows.Forms.RadioButton();
33 | this.buttonConfirm = new System.Windows.Forms.Button();
34 | this.SuspendLayout();
35 | //
36 | // radioButtonSteam
37 | //
38 | this.radioButtonSteam.AutoSize = true;
39 | this.radioButtonSteam.Location = new System.Drawing.Point(53, 12);
40 | this.radioButtonSteam.Name = "radioButtonSteam";
41 | this.radioButtonSteam.Size = new System.Drawing.Size(187, 17);
42 | this.radioButtonSteam.TabIndex = 0;
43 | this.radioButtonSteam.TabStop = true;
44 | this.radioButtonSteam.Text = "I purchased the game on Steam";
45 | this.radioButtonSteam.UseVisualStyleBackColor = true;
46 | //
47 | // radioButtonOculus
48 | //
49 | this.radioButtonOculus.AutoSize = true;
50 | this.radioButtonOculus.Location = new System.Drawing.Point(25, 35);
51 | this.radioButtonOculus.Name = "radioButtonOculus";
52 | this.radioButtonOculus.Size = new System.Drawing.Size(242, 17);
53 | this.radioButtonOculus.TabIndex = 1;
54 | this.radioButtonOculus.TabStop = true;
55 | this.radioButtonOculus.Text = "I purchased the game on the Oculus Store";
56 | this.radioButtonOculus.UseVisualStyleBackColor = true;
57 | //
58 | // buttonConfirm
59 | //
60 | this.buttonConfirm.Location = new System.Drawing.Point(109, 60);
61 | this.buttonConfirm.Name = "buttonConfirm";
62 | this.buttonConfirm.Size = new System.Drawing.Size(75, 23);
63 | this.buttonConfirm.TabIndex = 2;
64 | this.buttonConfirm.Text = "Confirm";
65 | this.buttonConfirm.UseVisualStyleBackColor = true;
66 | this.buttonConfirm.Click += new System.EventHandler(this.buttonConfirm_Click);
67 | //
68 | // FormSelectPlatform
69 | //
70 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
71 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
72 | this.ClientSize = new System.Drawing.Size(292, 95);
73 | this.Controls.Add(this.buttonConfirm);
74 | this.Controls.Add(this.radioButtonOculus);
75 | this.Controls.Add(this.radioButtonSteam);
76 | this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
77 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog;
78 | this.MaximizeBox = false;
79 | this.MinimizeBox = false;
80 | this.Name = "FormSelectPlatform";
81 | this.ShowIcon = false;
82 | this.ShowInTaskbar = false;
83 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
84 | this.Text = "Please select your platform";
85 | this.TopMost = true;
86 | this.ResumeLayout(false);
87 | this.PerformLayout();
88 |
89 | }
90 |
91 | #endregion
92 |
93 | private System.Windows.Forms.RadioButton radioButtonSteam;
94 | private System.Windows.Forms.RadioButton radioButtonOculus;
95 | private System.Windows.Forms.Button buttonConfirm;
96 | }
97 | }
--------------------------------------------------------------------------------
/MonkeModManager/FormSelectPlatform.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Forms;
3 |
4 | namespace MonkeModManager
5 | {
6 | public partial class FormSelectPlatform : Form
7 | {
8 | new readonly FormMain Parent;
9 | public FormSelectPlatform(FormMain parent)
10 | {
11 | InitializeComponent();
12 | Parent = parent;
13 | }
14 |
15 | private void buttonConfirm_Click(object sender, EventArgs e)
16 | {
17 | Parent.platformDetected = true;
18 | if (radioButtonOculus.Checked)
19 | {
20 | Parent.isSteam = false;
21 | }
22 | this.Close();
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/MonkeModManager/FormSelectPlatform.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/MonkeModManager/Internals/ReleaseInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using MonkeModManager.Internals.SimpleJSON;
6 |
7 | namespace MonkeModManager.Internals
8 | {
9 | public class ReleaseInfo
10 | {
11 | public string Version;
12 | public string Link;
13 | public string Name;
14 | public string Author;
15 | public string GitPath;
16 | public string Tag;
17 | public string Group;
18 | public string InstallLocation;
19 | public int ReleaseId;
20 | public bool Install = true;
21 | public List Dependencies = new List();
22 | public List Dependents = new List();
23 | public ReleaseInfo(string _name, string _author, string _gitPath, int _releaseId, string _tag, string _group, string _installLocation, JSONArray dependencies)
24 | {
25 | Name = _name;
26 | Author = _author;
27 | GitPath = _gitPath;
28 | ReleaseId = _releaseId;
29 | Tag = _tag;
30 | InstallLocation = _installLocation;
31 | Group = _group;
32 |
33 | if (dependencies == null) return;
34 | for (int i = 0; i < dependencies.Count; i++)
35 | {
36 | Dependencies.Add(dependencies[i]);
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/MonkeModManager/Internals/SimpleJson.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace MonkeModManager.Internals
8 | {
9 | /* * * * *
10 | * A simple JSON Parser / builder
11 | * ------------------------------
12 | *
13 | * It mainly has been written as a simple JSON parser. It can build a JSON string
14 | * from the node-tree, or generate a node tree from any valid JSON string.
15 | *
16 | * If you want to use compression when saving to file / stream / B64 you have to include
17 | * SharpZipLib ( http://www.icsharpcode.net/opensource/sharpziplib/ ) in your project and
18 | * define "USE_SharpZipLib" at the top of the file
19 | *
20 | * Written by Bunny83
21 | * 2012-06-09
22 | *
23 | * [2012-06-09 First Version]
24 | * - provides strongly typed node classes and lists / dictionaries
25 | * - provides easy access to class members / array items / data values
26 | * - the parser now properly identifies types. So generating JSON with this framework should work.
27 | * - only double quotes (") are used for quoting strings.
28 | * - provides "casting" properties to easily convert to / from those types:
29 | * int / float / double / bool
30 | * - provides a common interface for each node so no explicit casting is required.
31 | * - the parser tries to avoid errors, but if malformed JSON is parsed the result is more or less undefined
32 | * - It can serialize/deserialize a node tree into/from an experimental compact binary format. It might
33 | * be handy if you want to store things in a file and don't want it to be easily modifiable
34 | *
35 | * [2012-12-17 Update]
36 | * - Added internal JSONLazyCreator class which simplifies the construction of a JSON tree
37 | * Now you can simple reference any item that doesn't exist yet and it will return a JSONLazyCreator
38 | * The class determines the required type by it's further use, creates the type and removes itself.
39 | * - Added binary serialization / deserialization.
40 | * - Added support for BZip2 zipped binary format. Requires the SharpZipLib ( http://www.icsharpcode.net/opensource/sharpziplib/ )
41 | * The usage of the SharpZipLib library can be disabled by removing or commenting out the USE_SharpZipLib define at the top
42 | * - The serializer uses different types when it comes to store the values. Since my data values
43 | * are all of type string, the serializer will "try" which format fits best. The order is: int, float, double, bool, string.
44 | * It's not the most efficient way but for a moderate amount of data it should work on all platforms.
45 | *
46 | * [2017-03-08 Update]
47 | * - Optimised parsing by using a StringBuilder for token. This prevents performance issues when large
48 | * string data fields are contained in the json data.
49 | * - Finally refactored the badly named JSONClass into JSONObject.
50 | * - Replaced the old JSONData class by distict typed classes ( JSONString, JSONNumber, JSONBool, JSONNull ) this
51 | * allows to propertly convert the node tree back to json without type information loss. The actual value
52 | * parsing now happens at parsing time and not when you actually access one of the casting properties.
53 | *
54 | * [2017-04-11 Update]
55 | * - Fixed parsing bug where empty string values have been ignored.
56 | * - Optimised "ToString" by using a StringBuilder internally. This should heavily improve performance for large files
57 | * - Changed the overload of "ToString(string aIndent)" to "ToString(int aIndent)"
58 | *
59 | * [2017-11-29 Update]
60 | * - Removed the IEnumerator implementations on JSONArray & JSONObject and replaced it with a common
61 | * struct Enumerator in JSONNode that should avoid garbage generation. The enumerator always works
62 | * on KeyValuePair, even for JSONArray.
63 | * - Added two wrapper Enumerators that allows for easy key or value enumeration. A JSONNode now has
64 | * a "Keys" and a "Values" enumerable property. Those are also struct enumerators / enumerables
65 | * - A KeyValuePair can now be implicitly converted into a JSONNode. This allows
66 | * a foreach loop over a JSONNode to directly access the values only. Since KeyValuePair as well as
67 | * all the Enumerators are structs, no garbage is allocated.
68 | * - To add Linq support another "LinqEnumerator" is available through the "Linq" property. This
69 | * enumerator does implement the generic IEnumerable interface so most Linq extensions can be used
70 | * on this enumerable object. This one does allocate memory as it's a wrapper class.
71 | * - The Escape method now escapes all control characters (# < 32) in strings as uncode characters
72 | * (\uXXXX) and if the static bool JSONNode.forceASCII is set to true it will also escape all
73 | * characters # > 127. This might be useful if you require an ASCII output. Though keep in mind
74 | * when your strings contain many non-ascii characters the strings become much longer (x6) and are
75 | * no longer human readable.
76 | * - The node types JSONObject and JSONArray now have an "Inline" boolean switch which will default to
77 | * false. It can be used to serialize this element inline even you serialize with an indented format
78 | * This is useful for arrays containing numbers so it doesn't place every number on a new line
79 | * - Extracted the binary serialization code into a seperate extension file. All classes are now declared
80 | * as "partial" so an extension file can even add a new virtual or abstract method / interface to
81 | * JSONNode and override it in the concrete type classes. It's of course a hacky approach which is
82 | * generally not recommended, but i wanted to keep everything tightly packed.
83 | * - Added a static CreateOrGet method to the JSONNull class. Since this class is immutable it could
84 | * be reused without major problems. If you have a lot null fields in your data it will help reduce
85 | * the memory / garbage overhead. I also added a static setting (reuseSameInstance) to JSONNull
86 | * (default is true) which will change the behaviour of "CreateOrGet". If you set this to false
87 | * CreateOrGet will not reuse the cached instance but instead create a new JSONNull instance each time.
88 | * I made the JSONNull constructor private so if you need to create an instance manually use
89 | * JSONNull.CreateOrGet()
90 | *
91 | * [2018-01-09 Update]
92 | * - Changed all double.TryParse and double.ToString uses to use the invariant culture to avoid problems
93 | * on systems with a culture that uses a comma as decimal point.
94 | *
95 | * [2018-01-26 Update]
96 | * - Added AsLong. Note that a JSONNumber is stored as double and can't represent all long values. However
97 | * storing it as string would work.
98 | * - Added static setting "JSONNode.longAsString" which controls the default type that is used by the
99 | * LazyCreator when using AsLong
100 | *
101 | * [2018-04-25 Update]
102 | * - Added support for parsing single values (JSONBool, JSONString, JSONNumber, JSONNull) as top level value.
103 | *
104 | * The MIT License (MIT)
105 | *
106 | * Copyright (c) 2012-2017 Markus Göbel (Bunny83)
107 | *
108 | * Permission is hereby granted, free of charge, to any person obtaining a copy
109 | * of this software and associated documentation files (the "Software"), to deal
110 | * in the Software without restriction, including without limitation the rights
111 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
112 | * copies of the Software, and to permit persons to whom the Software is
113 | * furnished to do so, subject to the following conditions:
114 | *
115 | * The above copyright notice and this permission notice shall be included in all
116 | * copies or substantial portions of the Software.
117 | *
118 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
119 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
120 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
121 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
122 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
123 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
124 | * SOFTWARE.
125 | *
126 | * * * * */
127 | using System;
128 | using System.Collections;
129 | using System.Collections.Generic;
130 | using System.Globalization;
131 | using System.Linq;
132 | using System.Text;
133 |
134 | namespace SimpleJSON
135 | {
136 | public enum JSONNodeType
137 | {
138 | Array = 1,
139 | Object = 2,
140 | String = 3,
141 | Number = 4,
142 | NullValue = 5,
143 | Boolean = 6,
144 | None = 7,
145 | Custom = 0xFF,
146 | }
147 | public enum JSONTextMode
148 | {
149 | Compact,
150 | Indent
151 | }
152 |
153 | public abstract partial class JSONNode
154 | {
155 | #region Enumerators
156 | public struct Enumerator
157 | {
158 | private enum Type { None, Array, Object }
159 | private Type type;
160 | private Dictionary.Enumerator m_Object;
161 | private List.Enumerator m_Array;
162 | public bool IsValid { get { return type != Type.None; } }
163 | public Enumerator(List.Enumerator aArrayEnum)
164 | {
165 | type = Type.Array;
166 | m_Object = default(Dictionary.Enumerator);
167 | m_Array = aArrayEnum;
168 | }
169 | public Enumerator(Dictionary.Enumerator aDictEnum)
170 | {
171 | type = Type.Object;
172 | m_Object = aDictEnum;
173 | m_Array = default(List.Enumerator);
174 | }
175 | public KeyValuePair Current
176 | {
177 | get
178 | {
179 | if (type == Type.Array)
180 | return new KeyValuePair(string.Empty, m_Array.Current);
181 | else if (type == Type.Object)
182 | return m_Object.Current;
183 | return new KeyValuePair(string.Empty, null);
184 | }
185 | }
186 | public bool MoveNext()
187 | {
188 | if (type == Type.Array)
189 | return m_Array.MoveNext();
190 | else if (type == Type.Object)
191 | return m_Object.MoveNext();
192 | return false;
193 | }
194 | }
195 | public struct ValueEnumerator
196 | {
197 | private Enumerator m_Enumerator;
198 | public ValueEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { }
199 | public ValueEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { }
200 | public ValueEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; }
201 | public JSONNode Current { get { return m_Enumerator.Current.Value; } }
202 | public bool MoveNext() { return m_Enumerator.MoveNext(); }
203 | public ValueEnumerator GetEnumerator() { return this; }
204 | }
205 | public struct KeyEnumerator
206 | {
207 | private Enumerator m_Enumerator;
208 | public KeyEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { }
209 | public KeyEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { }
210 | public KeyEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; }
211 | public JSONNode Current { get { return m_Enumerator.Current.Key; } }
212 | public bool MoveNext() { return m_Enumerator.MoveNext(); }
213 | public KeyEnumerator GetEnumerator() { return this; }
214 | }
215 |
216 | public class LinqEnumerator : IEnumerator>, IEnumerable>
217 | {
218 | private JSONNode m_Node;
219 | private Enumerator m_Enumerator;
220 | internal LinqEnumerator(JSONNode aNode)
221 | {
222 | m_Node = aNode;
223 | if (m_Node != null)
224 | m_Enumerator = m_Node.GetEnumerator();
225 | }
226 | public KeyValuePair Current { get { return m_Enumerator.Current; } }
227 | object IEnumerator.Current { get { return m_Enumerator.Current; } }
228 | public bool MoveNext() { return m_Enumerator.MoveNext(); }
229 |
230 | public void Dispose()
231 | {
232 | m_Node = null;
233 | m_Enumerator = new Enumerator();
234 | }
235 |
236 | public IEnumerator> GetEnumerator()
237 | {
238 | return new LinqEnumerator(m_Node);
239 | }
240 |
241 | public void Reset()
242 | {
243 | if (m_Node != null)
244 | m_Enumerator = m_Node.GetEnumerator();
245 | }
246 |
247 | IEnumerator IEnumerable.GetEnumerator()
248 | {
249 | return new LinqEnumerator(m_Node);
250 | }
251 | }
252 |
253 | #endregion Enumerators
254 |
255 | #region common interface
256 |
257 | public static bool forceASCII = false; // Use Unicode by default
258 | public static bool longAsString = false; // lazy creator creates a JSONString instead of JSONNumber
259 |
260 | public abstract JSONNodeType Tag { get; }
261 |
262 | public virtual JSONNode this[int aIndex] { get { return null; } set { } }
263 |
264 | public virtual JSONNode this[string aKey] { get { return null; } set { } }
265 |
266 | public virtual string Value { get { return ""; } set { } }
267 |
268 | public virtual int Count { get { return 0; } }
269 |
270 | public virtual bool IsNumber { get { return false; } }
271 | public virtual bool IsString { get { return false; } }
272 | public virtual bool IsBoolean { get { return false; } }
273 | public virtual bool IsNull { get { return false; } }
274 | public virtual bool IsArray { get { return false; } }
275 | public virtual bool IsObject { get { return false; } }
276 |
277 | public virtual bool Inline { get { return false; } set { } }
278 |
279 | public virtual void Add(string aKey, JSONNode aItem)
280 | {
281 | }
282 | public virtual void Add(JSONNode aItem)
283 | {
284 | Add("", aItem);
285 | }
286 |
287 | public virtual JSONNode Remove(string aKey)
288 | {
289 | return null;
290 | }
291 |
292 | public virtual JSONNode Remove(int aIndex)
293 | {
294 | return null;
295 | }
296 |
297 | public virtual JSONNode Remove(JSONNode aNode)
298 | {
299 | return aNode;
300 | }
301 |
302 | public virtual IEnumerable Children
303 | {
304 | get
305 | {
306 | yield break;
307 | }
308 | }
309 |
310 | public IEnumerable DeepChildren
311 | {
312 | get
313 | {
314 | foreach (var C in Children)
315 | foreach (var D in C.DeepChildren)
316 | yield return D;
317 | }
318 | }
319 |
320 | public override string ToString()
321 | {
322 | StringBuilder sb = new StringBuilder();
323 | WriteToStringBuilder(sb, 0, 0, JSONTextMode.Compact);
324 | return sb.ToString();
325 | }
326 |
327 | public virtual string ToString(int aIndent)
328 | {
329 | StringBuilder sb = new StringBuilder();
330 | WriteToStringBuilder(sb, 0, aIndent, JSONTextMode.Indent);
331 | return sb.ToString();
332 | }
333 | internal abstract void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode);
334 |
335 | public abstract Enumerator GetEnumerator();
336 | public IEnumerable> Linq { get { return new LinqEnumerator(this); } }
337 | public KeyEnumerator Keys { get { return new KeyEnumerator(GetEnumerator()); } }
338 | public ValueEnumerator Values { get { return new ValueEnumerator(GetEnumerator()); } }
339 |
340 | #endregion common interface
341 |
342 | #region typecasting properties
343 |
344 |
345 | public virtual double AsDouble
346 | {
347 | get
348 | {
349 | double v = 0.0;
350 | if (double.TryParse(Value, NumberStyles.Float, CultureInfo.InvariantCulture, out v))
351 | return v;
352 | return 0.0;
353 | }
354 | set
355 | {
356 | Value = value.ToString(CultureInfo.InvariantCulture);
357 | }
358 | }
359 |
360 | public virtual int AsInt
361 | {
362 | get { return (int)AsDouble; }
363 | set { AsDouble = value; }
364 | }
365 |
366 | public virtual float AsFloat
367 | {
368 | get { return (float)AsDouble; }
369 | set { AsDouble = value; }
370 | }
371 |
372 | public virtual bool AsBool
373 | {
374 | get
375 | {
376 | bool v = false;
377 | if (bool.TryParse(Value, out v))
378 | return v;
379 | return !string.IsNullOrEmpty(Value);
380 | }
381 | set
382 | {
383 | Value = (value) ? "true" : "false";
384 | }
385 | }
386 |
387 | public virtual long AsLong
388 | {
389 | get
390 | {
391 | long val = 0;
392 | if (long.TryParse(Value, out val))
393 | return val;
394 | return 0L;
395 | }
396 | set
397 | {
398 | Value = value.ToString();
399 | }
400 | }
401 |
402 | public virtual JSONArray AsArray
403 | {
404 | get
405 | {
406 | return this as JSONArray;
407 | }
408 | }
409 |
410 | public virtual JSONObject AsObject
411 | {
412 | get
413 | {
414 | return this as JSONObject;
415 | }
416 | }
417 |
418 |
419 | #endregion typecasting properties
420 |
421 | #region operators
422 |
423 | public static implicit operator JSONNode(string s)
424 | {
425 | return new JSONString(s);
426 | }
427 | public static implicit operator string(JSONNode d)
428 | {
429 | return (d == null) ? null : d.Value;
430 | }
431 |
432 | public static implicit operator JSONNode(double n)
433 | {
434 | return new JSONNumber(n);
435 | }
436 | public static implicit operator double(JSONNode d)
437 | {
438 | return (d == null) ? 0 : d.AsDouble;
439 | }
440 |
441 | public static implicit operator JSONNode(float n)
442 | {
443 | return new JSONNumber(n);
444 | }
445 | public static implicit operator float(JSONNode d)
446 | {
447 | return (d == null) ? 0 : d.AsFloat;
448 | }
449 |
450 | public static implicit operator JSONNode(int n)
451 | {
452 | return new JSONNumber(n);
453 | }
454 | public static implicit operator int(JSONNode d)
455 | {
456 | return (d == null) ? 0 : d.AsInt;
457 | }
458 |
459 | public static implicit operator JSONNode(long n)
460 | {
461 | if (longAsString)
462 | return new JSONString(n.ToString());
463 | return new JSONNumber(n);
464 | }
465 | public static implicit operator long(JSONNode d)
466 | {
467 | return (d == null) ? 0L : d.AsLong;
468 | }
469 |
470 | public static implicit operator JSONNode(bool b)
471 | {
472 | return new JSONBool(b);
473 | }
474 | public static implicit operator bool(JSONNode d)
475 | {
476 | return (d == null) ? false : d.AsBool;
477 | }
478 |
479 | public static implicit operator JSONNode(KeyValuePair aKeyValue)
480 | {
481 | return aKeyValue.Value;
482 | }
483 |
484 | public static bool operator ==(JSONNode a, object b)
485 | {
486 | if (ReferenceEquals(a, b))
487 | return true;
488 | bool aIsNull = a is JSONNull || ReferenceEquals(a, null) || a is JSONLazyCreator;
489 | bool bIsNull = b is JSONNull || ReferenceEquals(b, null) || b is JSONLazyCreator;
490 | if (aIsNull && bIsNull)
491 | return true;
492 | return !aIsNull && a.Equals(b);
493 | }
494 |
495 | public static bool operator !=(JSONNode a, object b)
496 | {
497 | return !(a == b);
498 | }
499 |
500 | public override bool Equals(object obj)
501 | {
502 | return ReferenceEquals(this, obj);
503 | }
504 |
505 | public override int GetHashCode()
506 | {
507 | return base.GetHashCode();
508 | }
509 |
510 | #endregion operators
511 |
512 | [ThreadStatic]
513 | private static StringBuilder m_EscapeBuilder;
514 | internal static StringBuilder EscapeBuilder
515 | {
516 | get
517 | {
518 | if (m_EscapeBuilder == null)
519 | m_EscapeBuilder = new StringBuilder();
520 | return m_EscapeBuilder;
521 | }
522 | }
523 | internal static string Escape(string aText)
524 | {
525 | var sb = EscapeBuilder;
526 | sb.Length = 0;
527 | if (sb.Capacity < aText.Length + aText.Length / 10)
528 | sb.Capacity = aText.Length + aText.Length / 10;
529 | foreach (char c in aText)
530 | {
531 | switch (c)
532 | {
533 | case '\\':
534 | sb.Append("\\\\");
535 | break;
536 | case '\"':
537 | sb.Append("\\\"");
538 | break;
539 | case '\n':
540 | sb.Append("\\n");
541 | break;
542 | case '\r':
543 | sb.Append("\\r");
544 | break;
545 | case '\t':
546 | sb.Append("\\t");
547 | break;
548 | case '\b':
549 | sb.Append("\\b");
550 | break;
551 | case '\f':
552 | sb.Append("\\f");
553 | break;
554 | default:
555 | if (c < ' ' || (forceASCII && c > 127))
556 | {
557 | ushort val = c;
558 | sb.Append("\\u").Append(val.ToString("X4"));
559 | }
560 | else
561 | sb.Append(c);
562 | break;
563 | }
564 | }
565 | string result = sb.ToString();
566 | sb.Length = 0;
567 | return result;
568 | }
569 |
570 | private static JSONNode ParseElement(string token, bool quoted)
571 | {
572 | if (quoted)
573 | return token;
574 | string tmp = token.ToLower();
575 | if (tmp == "false" || tmp == "true")
576 | return tmp == "true";
577 | if (tmp == "null")
578 | return JSONNull.CreateOrGet();
579 | double val;
580 | if (double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out val))
581 | return val;
582 | else
583 | return token;
584 | }
585 |
586 | public static JSONNode Parse(string aJSON)
587 | {
588 | Stack stack = new Stack();
589 | JSONNode ctx = null;
590 | int i = 0;
591 | StringBuilder Token = new StringBuilder();
592 | string TokenName = "";
593 | bool QuoteMode = false;
594 | bool TokenIsQuoted = false;
595 | while (i < aJSON.Length)
596 | {
597 | switch (aJSON[i])
598 | {
599 | case '{':
600 | if (QuoteMode)
601 | {
602 | Token.Append(aJSON[i]);
603 | break;
604 | }
605 | stack.Push(new JSONObject());
606 | if (ctx != null)
607 | {
608 | ctx.Add(TokenName, stack.Peek());
609 | }
610 | TokenName = "";
611 | Token.Length = 0;
612 | ctx = stack.Peek();
613 | break;
614 |
615 | case '[':
616 | if (QuoteMode)
617 | {
618 | Token.Append(aJSON[i]);
619 | break;
620 | }
621 |
622 | stack.Push(new JSONArray());
623 | if (ctx != null)
624 | {
625 | ctx.Add(TokenName, stack.Peek());
626 | }
627 | TokenName = "";
628 | Token.Length = 0;
629 | ctx = stack.Peek();
630 | break;
631 |
632 | case '}':
633 | case ']':
634 | if (QuoteMode)
635 | {
636 |
637 | Token.Append(aJSON[i]);
638 | break;
639 | }
640 | if (stack.Count == 0)
641 | throw new Exception("JSON Parse: Too many closing brackets");
642 |
643 | stack.Pop();
644 | if (Token.Length > 0 || TokenIsQuoted)
645 | ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted));
646 | TokenIsQuoted = false;
647 | TokenName = "";
648 | Token.Length = 0;
649 | if (stack.Count > 0)
650 | ctx = stack.Peek();
651 | break;
652 |
653 | case ':':
654 | if (QuoteMode)
655 | {
656 | Token.Append(aJSON[i]);
657 | break;
658 | }
659 | TokenName = Token.ToString();
660 | Token.Length = 0;
661 | TokenIsQuoted = false;
662 | break;
663 |
664 | case '"':
665 | QuoteMode ^= true;
666 | TokenIsQuoted |= QuoteMode;
667 | break;
668 |
669 | case ',':
670 | if (QuoteMode)
671 | {
672 | Token.Append(aJSON[i]);
673 | break;
674 | }
675 | if (Token.Length > 0 || TokenIsQuoted)
676 | ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted));
677 | TokenIsQuoted = false;
678 | TokenName = "";
679 | Token.Length = 0;
680 | TokenIsQuoted = false;
681 | break;
682 |
683 | case '\r':
684 | case '\n':
685 | break;
686 |
687 | case ' ':
688 | case '\t':
689 | if (QuoteMode)
690 | Token.Append(aJSON[i]);
691 | break;
692 |
693 | case '\\':
694 | ++i;
695 | if (QuoteMode)
696 | {
697 | char C = aJSON[i];
698 | switch (C)
699 | {
700 | case 't':
701 | Token.Append('\t');
702 | break;
703 | case 'r':
704 | Token.Append('\r');
705 | break;
706 | case 'n':
707 | Token.Append('\n');
708 | break;
709 | case 'b':
710 | Token.Append('\b');
711 | break;
712 | case 'f':
713 | Token.Append('\f');
714 | break;
715 | case 'u':
716 | {
717 | string s = aJSON.Substring(i + 1, 4);
718 | Token.Append((char)int.Parse(
719 | s,
720 | System.Globalization.NumberStyles.AllowHexSpecifier));
721 | i += 4;
722 | break;
723 | }
724 | default:
725 | Token.Append(C);
726 | break;
727 | }
728 | }
729 | break;
730 |
731 | default:
732 | Token.Append(aJSON[i]);
733 | break;
734 | }
735 | ++i;
736 | }
737 | if (QuoteMode)
738 | {
739 | throw new Exception("JSON Parse: Quotation marks seems to be messed up.");
740 | }
741 | if (ctx == null)
742 | return ParseElement(Token.ToString(), TokenIsQuoted);
743 | return ctx;
744 | }
745 |
746 | }
747 | // End of JSONNode
748 |
749 | public partial class JSONArray : JSONNode
750 | {
751 | private List m_List = new List();
752 | private bool inline = false;
753 | public override bool Inline
754 | {
755 | get { return inline; }
756 | set { inline = value; }
757 | }
758 |
759 | public override JSONNodeType Tag { get { return JSONNodeType.Array; } }
760 | public override bool IsArray { get { return true; } }
761 | public override Enumerator GetEnumerator() { return new Enumerator(m_List.GetEnumerator()); }
762 |
763 | public override JSONNode this[int aIndex]
764 | {
765 | get
766 | {
767 | if (aIndex < 0 || aIndex >= m_List.Count)
768 | return new JSONLazyCreator(this);
769 | return m_List[aIndex];
770 | }
771 | set
772 | {
773 | if (value == null)
774 | value = JSONNull.CreateOrGet();
775 | if (aIndex < 0 || aIndex >= m_List.Count)
776 | m_List.Add(value);
777 | else
778 | m_List[aIndex] = value;
779 | }
780 | }
781 |
782 | public override JSONNode this[string aKey]
783 | {
784 | get { return new JSONLazyCreator(this); }
785 | set
786 | {
787 | if (value == null)
788 | value = JSONNull.CreateOrGet();
789 | m_List.Add(value);
790 | }
791 | }
792 |
793 | public override int Count
794 | {
795 | get { return m_List.Count; }
796 | }
797 |
798 | public override void Add(string aKey, JSONNode aItem)
799 | {
800 | if (aItem == null)
801 | aItem = JSONNull.CreateOrGet();
802 | m_List.Add(aItem);
803 | }
804 |
805 | public override JSONNode Remove(int aIndex)
806 | {
807 | if (aIndex < 0 || aIndex >= m_List.Count)
808 | return null;
809 | JSONNode tmp = m_List[aIndex];
810 | m_List.RemoveAt(aIndex);
811 | return tmp;
812 | }
813 |
814 | public override JSONNode Remove(JSONNode aNode)
815 | {
816 | m_List.Remove(aNode);
817 | return aNode;
818 | }
819 |
820 | public override IEnumerable Children
821 | {
822 | get
823 | {
824 | foreach (JSONNode N in m_List)
825 | yield return N;
826 | }
827 | }
828 |
829 |
830 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
831 | {
832 | aSB.Append('[');
833 | int count = m_List.Count;
834 | if (inline)
835 | aMode = JSONTextMode.Compact;
836 | for (int i = 0; i < count; i++)
837 | {
838 | if (i > 0)
839 | aSB.Append(',');
840 | if (aMode == JSONTextMode.Indent)
841 | aSB.AppendLine();
842 |
843 | if (aMode == JSONTextMode.Indent)
844 | aSB.Append(' ', aIndent + aIndentInc);
845 | m_List[i].WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode);
846 | }
847 | if (aMode == JSONTextMode.Indent)
848 | aSB.AppendLine().Append(' ', aIndent);
849 | aSB.Append(']');
850 | }
851 | }
852 | // End of JSONArray
853 |
854 | public partial class JSONObject : JSONNode
855 | {
856 | private Dictionary m_Dict = new Dictionary();
857 |
858 | private bool inline = false;
859 | public override bool Inline
860 | {
861 | get { return inline; }
862 | set { inline = value; }
863 | }
864 |
865 | public override JSONNodeType Tag { get { return JSONNodeType.Object; } }
866 | public override bool IsObject { get { return true; } }
867 |
868 | public override Enumerator GetEnumerator() { return new Enumerator(m_Dict.GetEnumerator()); }
869 |
870 |
871 | public override JSONNode this[string aKey]
872 | {
873 | get
874 | {
875 | if (m_Dict.ContainsKey(aKey))
876 | return m_Dict[aKey];
877 | else
878 | return new JSONLazyCreator(this, aKey);
879 | }
880 | set
881 | {
882 | if (value == null)
883 | value = JSONNull.CreateOrGet();
884 | if (m_Dict.ContainsKey(aKey))
885 | m_Dict[aKey] = value;
886 | else
887 | m_Dict.Add(aKey, value);
888 | }
889 | }
890 |
891 | public override JSONNode this[int aIndex]
892 | {
893 | get
894 | {
895 | if (aIndex < 0 || aIndex >= m_Dict.Count)
896 | return null;
897 | return m_Dict.ElementAt(aIndex).Value;
898 | }
899 | set
900 | {
901 | if (value == null)
902 | value = JSONNull.CreateOrGet();
903 | if (aIndex < 0 || aIndex >= m_Dict.Count)
904 | return;
905 | string key = m_Dict.ElementAt(aIndex).Key;
906 | m_Dict[key] = value;
907 | }
908 | }
909 |
910 | public override int Count
911 | {
912 | get { return m_Dict.Count; }
913 | }
914 |
915 | public override void Add(string aKey, JSONNode aItem)
916 | {
917 | if (aItem == null)
918 | aItem = JSONNull.CreateOrGet();
919 |
920 | if (!string.IsNullOrEmpty(aKey))
921 | {
922 | if (m_Dict.ContainsKey(aKey))
923 | m_Dict[aKey] = aItem;
924 | else
925 | m_Dict.Add(aKey, aItem);
926 | }
927 | else
928 | m_Dict.Add(Guid.NewGuid().ToString(), aItem);
929 | }
930 |
931 | public override JSONNode Remove(string aKey)
932 | {
933 | if (!m_Dict.ContainsKey(aKey))
934 | return null;
935 | JSONNode tmp = m_Dict[aKey];
936 | m_Dict.Remove(aKey);
937 | return tmp;
938 | }
939 |
940 | public override JSONNode Remove(int aIndex)
941 | {
942 | if (aIndex < 0 || aIndex >= m_Dict.Count)
943 | return null;
944 | var item = m_Dict.ElementAt(aIndex);
945 | m_Dict.Remove(item.Key);
946 | return item.Value;
947 | }
948 |
949 | public override JSONNode Remove(JSONNode aNode)
950 | {
951 | try
952 | {
953 | var item = m_Dict.Where(k => k.Value == aNode).First();
954 | m_Dict.Remove(item.Key);
955 | return aNode;
956 | }
957 | catch
958 | {
959 | return null;
960 | }
961 | }
962 |
963 | public override IEnumerable Children
964 | {
965 | get
966 | {
967 | foreach (KeyValuePair N in m_Dict)
968 | yield return N.Value;
969 | }
970 | }
971 |
972 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
973 | {
974 | aSB.Append('{');
975 | bool first = true;
976 | if (inline)
977 | aMode = JSONTextMode.Compact;
978 | foreach (var k in m_Dict)
979 | {
980 | if (!first)
981 | aSB.Append(',');
982 | first = false;
983 | if (aMode == JSONTextMode.Indent)
984 | aSB.AppendLine();
985 | if (aMode == JSONTextMode.Indent)
986 | aSB.Append(' ', aIndent + aIndentInc);
987 | aSB.Append('\"').Append(Escape(k.Key)).Append('\"');
988 | if (aMode == JSONTextMode.Compact)
989 | aSB.Append(':');
990 | else
991 | aSB.Append(" : ");
992 | k.Value.WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode);
993 | }
994 | if (aMode == JSONTextMode.Indent)
995 | aSB.AppendLine().Append(' ', aIndent);
996 | aSB.Append('}');
997 | }
998 |
999 | }
1000 | // End of JSONObject
1001 |
1002 | public partial class JSONString : JSONNode
1003 | {
1004 | private string m_Data;
1005 |
1006 | public override JSONNodeType Tag { get { return JSONNodeType.String; } }
1007 | public override bool IsString { get { return true; } }
1008 |
1009 | public override Enumerator GetEnumerator() { return new Enumerator(); }
1010 |
1011 |
1012 | public override string Value
1013 | {
1014 | get { return m_Data; }
1015 | set
1016 | {
1017 | m_Data = value;
1018 | }
1019 | }
1020 |
1021 | public JSONString(string aData)
1022 | {
1023 | m_Data = aData;
1024 | }
1025 |
1026 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
1027 | {
1028 | aSB.Append('\"').Append(Escape(m_Data)).Append('\"');
1029 | }
1030 | public override bool Equals(object obj)
1031 | {
1032 | if (base.Equals(obj))
1033 | return true;
1034 | string s = obj as string;
1035 | if (s != null)
1036 | return m_Data == s;
1037 | JSONString s2 = obj as JSONString;
1038 | if (s2 != null)
1039 | return m_Data == s2.m_Data;
1040 | return false;
1041 | }
1042 | public override int GetHashCode()
1043 | {
1044 | return m_Data.GetHashCode();
1045 | }
1046 | }
1047 | // End of JSONString
1048 |
1049 | public partial class JSONNumber : JSONNode
1050 | {
1051 | private double m_Data;
1052 |
1053 | public override JSONNodeType Tag { get { return JSONNodeType.Number; } }
1054 | public override bool IsNumber { get { return true; } }
1055 | public override Enumerator GetEnumerator() { return new Enumerator(); }
1056 |
1057 | public override string Value
1058 | {
1059 | get { return m_Data.ToString(CultureInfo.InvariantCulture); }
1060 | set
1061 | {
1062 | double v;
1063 | if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out v))
1064 | m_Data = v;
1065 | }
1066 | }
1067 |
1068 | public override double AsDouble
1069 | {
1070 | get { return m_Data; }
1071 | set { m_Data = value; }
1072 | }
1073 | public override long AsLong
1074 | {
1075 | get { return (long)m_Data; }
1076 | set { m_Data = value; }
1077 | }
1078 |
1079 | public JSONNumber(double aData)
1080 | {
1081 | m_Data = aData;
1082 | }
1083 |
1084 | public JSONNumber(string aData)
1085 | {
1086 | Value = aData;
1087 | }
1088 |
1089 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
1090 | {
1091 | aSB.Append(Value);
1092 | }
1093 | private static bool IsNumeric(object value)
1094 | {
1095 | return value is int || value is uint
1096 | || value is float || value is double
1097 | || value is decimal
1098 | || value is long || value is ulong
1099 | || value is short || value is ushort
1100 | || value is sbyte || value is byte;
1101 | }
1102 | public override bool Equals(object obj)
1103 | {
1104 | if (obj == null)
1105 | return false;
1106 | if (base.Equals(obj))
1107 | return true;
1108 | JSONNumber s2 = obj as JSONNumber;
1109 | if (s2 != null)
1110 | return m_Data == s2.m_Data;
1111 | if (IsNumeric(obj))
1112 | return Convert.ToDouble(obj) == m_Data;
1113 | return false;
1114 | }
1115 | public override int GetHashCode()
1116 | {
1117 | return m_Data.GetHashCode();
1118 | }
1119 | }
1120 | // End of JSONNumber
1121 |
1122 | public partial class JSONBool : JSONNode
1123 | {
1124 | private bool m_Data;
1125 |
1126 | public override JSONNodeType Tag { get { return JSONNodeType.Boolean; } }
1127 | public override bool IsBoolean { get { return true; } }
1128 | public override Enumerator GetEnumerator() { return new Enumerator(); }
1129 |
1130 | public override string Value
1131 | {
1132 | get { return m_Data.ToString(); }
1133 | set
1134 | {
1135 | bool v;
1136 | if (bool.TryParse(value, out v))
1137 | m_Data = v;
1138 | }
1139 | }
1140 | public override bool AsBool
1141 | {
1142 | get { return m_Data; }
1143 | set { m_Data = value; }
1144 | }
1145 |
1146 | public JSONBool(bool aData)
1147 | {
1148 | m_Data = aData;
1149 | }
1150 |
1151 | public JSONBool(string aData)
1152 | {
1153 | Value = aData;
1154 | }
1155 |
1156 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
1157 | {
1158 | aSB.Append((m_Data) ? "true" : "false");
1159 | }
1160 | public override bool Equals(object obj)
1161 | {
1162 | if (obj == null)
1163 | return false;
1164 | if (obj is bool)
1165 | return m_Data == (bool)obj;
1166 | return false;
1167 | }
1168 | public override int GetHashCode()
1169 | {
1170 | return m_Data.GetHashCode();
1171 | }
1172 | }
1173 | // End of JSONBool
1174 |
1175 | public partial class JSONNull : JSONNode
1176 | {
1177 | static JSONNull m_StaticInstance = new JSONNull();
1178 | public static bool reuseSameInstance = true;
1179 | public static JSONNull CreateOrGet()
1180 | {
1181 | if (reuseSameInstance)
1182 | return m_StaticInstance;
1183 | return new JSONNull();
1184 | }
1185 | private JSONNull() { }
1186 |
1187 | public override JSONNodeType Tag { get { return JSONNodeType.NullValue; } }
1188 | public override bool IsNull { get { return true; } }
1189 | public override Enumerator GetEnumerator() { return new Enumerator(); }
1190 |
1191 | public override string Value
1192 | {
1193 | get { return "null"; }
1194 | set { }
1195 | }
1196 | public override bool AsBool
1197 | {
1198 | get { return false; }
1199 | set { }
1200 | }
1201 |
1202 | public override bool Equals(object obj)
1203 | {
1204 | if (object.ReferenceEquals(this, obj))
1205 | return true;
1206 | return (obj is JSONNull);
1207 | }
1208 | public override int GetHashCode()
1209 | {
1210 | return 0;
1211 | }
1212 |
1213 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
1214 | {
1215 | aSB.Append("null");
1216 | }
1217 | }
1218 | // End of JSONNull
1219 |
1220 | internal partial class JSONLazyCreator : JSONNode
1221 | {
1222 | private JSONNode m_Node = null;
1223 | private string m_Key = null;
1224 | public override JSONNodeType Tag { get { return JSONNodeType.None; } }
1225 | public override Enumerator GetEnumerator() { return new Enumerator(); }
1226 |
1227 | public JSONLazyCreator(JSONNode aNode)
1228 | {
1229 | m_Node = aNode;
1230 | m_Key = null;
1231 | }
1232 |
1233 | public JSONLazyCreator(JSONNode aNode, string aKey)
1234 | {
1235 | m_Node = aNode;
1236 | m_Key = aKey;
1237 | }
1238 |
1239 | private T Set(T aVal) where T : JSONNode
1240 | {
1241 | if (m_Key == null)
1242 | m_Node.Add(aVal);
1243 | else
1244 | m_Node.Add(m_Key, aVal);
1245 | m_Node = null; // Be GC friendly.
1246 | return aVal;
1247 | }
1248 |
1249 | public override JSONNode this[int aIndex]
1250 | {
1251 | get { return new JSONLazyCreator(this); }
1252 | set { Set(new JSONArray()).Add(value); }
1253 | }
1254 |
1255 | public override JSONNode this[string aKey]
1256 | {
1257 | get { return new JSONLazyCreator(this, aKey); }
1258 | set { Set(new JSONObject()).Add(aKey, value); }
1259 | }
1260 |
1261 | public override void Add(JSONNode aItem)
1262 | {
1263 | Set(new JSONArray()).Add(aItem);
1264 | }
1265 |
1266 | public override void Add(string aKey, JSONNode aItem)
1267 | {
1268 | Set(new JSONObject()).Add(aKey, aItem);
1269 | }
1270 |
1271 | public static bool operator ==(JSONLazyCreator a, object b)
1272 | {
1273 | if (b == null)
1274 | return true;
1275 | return System.Object.ReferenceEquals(a, b);
1276 | }
1277 |
1278 | public static bool operator !=(JSONLazyCreator a, object b)
1279 | {
1280 | return !(a == b);
1281 | }
1282 |
1283 | public override bool Equals(object obj)
1284 | {
1285 | if (obj == null)
1286 | return true;
1287 | return System.Object.ReferenceEquals(this, obj);
1288 | }
1289 |
1290 | public override int GetHashCode()
1291 | {
1292 | return 0;
1293 | }
1294 |
1295 | public override int AsInt
1296 | {
1297 | get { Set(new JSONNumber(0)); return 0; }
1298 | set { Set(new JSONNumber(value)); }
1299 | }
1300 |
1301 | public override float AsFloat
1302 | {
1303 | get { Set(new JSONNumber(0.0f)); return 0.0f; }
1304 | set { Set(new JSONNumber(value)); }
1305 | }
1306 |
1307 | public override double AsDouble
1308 | {
1309 | get { Set(new JSONNumber(0.0)); return 0.0; }
1310 | set { Set(new JSONNumber(value)); }
1311 | }
1312 |
1313 | public override long AsLong
1314 | {
1315 | get
1316 | {
1317 | if (longAsString)
1318 | Set(new JSONString("0"));
1319 | else
1320 | Set(new JSONNumber(0.0));
1321 | return 0L;
1322 | }
1323 | set
1324 | {
1325 | if (longAsString)
1326 | Set(new JSONString(value.ToString()));
1327 | else
1328 | Set(new JSONNumber(value));
1329 | }
1330 | }
1331 |
1332 | public override bool AsBool
1333 | {
1334 | get { Set(new JSONBool(false)); return false; }
1335 | set { Set(new JSONBool(value)); }
1336 | }
1337 |
1338 | public override JSONArray AsArray
1339 | {
1340 | get { return Set(new JSONArray()); }
1341 | }
1342 |
1343 | public override JSONObject AsObject
1344 | {
1345 | get { return Set(new JSONObject()); }
1346 | }
1347 | internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode)
1348 | {
1349 | aSB.Append("null");
1350 | }
1351 | }
1352 | // End of JSONLazyCreator
1353 |
1354 | public static class JSON
1355 | {
1356 | public static JSONNode Parse(string aJSON)
1357 | {
1358 | return JSONNode.Parse(aJSON);
1359 | }
1360 | }
1361 | }
1362 |
1363 | }
1364 |
--------------------------------------------------------------------------------
/MonkeModManager/Internals/Unzip.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.ComponentModel;
4 | using System.IO;
5 | using System.IO.Compression;
6 | using System.Linq;
7 | using System.Text;
8 |
9 | namespace MonkeModManager.Internals
10 | {
11 | ///
12 | /// Unzip helper class.
13 | ///
14 | internal class Unzip : IDisposable
15 | {
16 | ///
17 | /// Zip archive entry.
18 | ///
19 | public class Entry
20 | {
21 | ///
22 | /// Gets or sets the name of a file or a directory.
23 | ///
24 | public string Name { get; set; }
25 |
26 | ///
27 | /// Gets or sets the comment.
28 | ///
29 | public string Comment { get; set; }
30 |
31 | ///
32 | /// Gets or sets the CRC32.
33 | ///
34 | public uint Crc32 { get; set; }
35 |
36 | ///
37 | /// Gets or sets the compressed size of the file.
38 | ///
39 | public int CompressedSize { get; set; }
40 |
41 | ///
42 | /// Gets or sets the original size of the file.
43 | ///
44 | public int OriginalSize { get; set; }
45 |
46 | ///
47 | /// Gets or sets a value indicating whether this is deflated.
48 | ///
49 | public bool Deflated { get; set; }
50 |
51 | ///
52 | /// Gets a value indicating whether this is a directory.
53 | ///
54 | public bool IsDirectory { get { return Name.EndsWith("/"); } }
55 |
56 | ///
57 | /// Gets or sets the timestamp.
58 | ///
59 | public DateTime Timestamp { get; set; }
60 |
61 | ///
62 | /// Gets a value indicating whether this is a file.
63 | ///
64 | public bool IsFile { get { return !IsDirectory; } }
65 |
66 | [EditorBrowsable(EditorBrowsableState.Never)]
67 | public int HeaderOffset { get; set; }
68 |
69 | [EditorBrowsable(EditorBrowsableState.Never)]
70 | public int DataOffset { get; set; }
71 | }
72 |
73 | ///
74 | /// CRC32 calculation helper.
75 | ///
76 | public class Crc32Calculator
77 | {
78 | private static readonly uint[] Crc32Table =
79 | {
80 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
81 | 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
82 | 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
83 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
84 | 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
85 | 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
86 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
87 | 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
88 | 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
89 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
90 | 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
91 | 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
92 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
93 | 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
94 | 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
95 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
96 | 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
97 | 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
98 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
99 | 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
100 | 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
101 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
102 | 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
103 | 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
104 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
105 | 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
106 | 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
107 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
108 | 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
109 | 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
110 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
111 | 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d,
112 | };
113 |
114 | private uint crcValue = 0xffffffff;
115 |
116 | public uint Crc32 { get { return crcValue ^ 0xffffffff; } }
117 |
118 | public void UpdateWithBlock(byte[] buffer, int numberOfBytes)
119 | {
120 | for (var i = 0; i < numberOfBytes; i++)
121 | {
122 | crcValue = (crcValue >> 8) ^ Crc32Table[buffer[i] ^ crcValue & 0xff];
123 | }
124 | }
125 | }
126 |
127 | ///
128 | /// Provides data for the ExtractProgress event.
129 | ///
130 | public class FileProgressEventArgs : ProgressChangedEventArgs
131 | {
132 | ///
133 | /// Initializes a new instance of the class.
134 | ///
135 | /// The current file.
136 | /// The total files.
137 | /// Name of the file.
138 | public FileProgressEventArgs(int currentFile, int totalFiles, string fileName)
139 | : base(totalFiles != 0 ? currentFile * 100 / totalFiles : 100, fileName)
140 | {
141 | CurrentFile = currentFile;
142 | TotalFiles = totalFiles;
143 | FileName = fileName;
144 | }
145 |
146 | ///
147 | /// Gets the current file.
148 | ///
149 | public int CurrentFile { get; private set; }
150 |
151 | ///
152 | /// Gets the total files.
153 | ///
154 | public int TotalFiles { get; private set; }
155 |
156 | ///
157 | /// Gets the name of the file.
158 | ///
159 | public string FileName { get; private set; }
160 | }
161 |
162 | private const int EntrySignature = 0x02014B50;
163 | private const int FileSignature = 0x04034b50;
164 | private const int DirectorySignature = 0x06054B50;
165 | private const int BufferSize = 16 * 1024;
166 |
167 | ///
168 | /// Occurs when a file or a directory is extracted from an archive.
169 | ///
170 | public event EventHandler ExtractProgress;
171 |
172 | ///
173 | /// Initializes a new instance of the class.
174 | ///
175 | /// Name of the file.
176 | public Unzip(string fileName)
177 | : this(File.OpenRead(fileName))
178 | {
179 | }
180 |
181 | ///
182 | /// Initializes a new instance of the class.
183 | ///
184 | /// The stream.
185 | public Unzip(Stream stream)
186 | {
187 | Stream = stream;
188 | Reader = new BinaryReader(Stream);
189 | }
190 |
191 | private Stream Stream { get; set; }
192 |
193 | private BinaryReader Reader { get; set; }
194 |
195 | ///
196 | /// Performs application-defined tasks associated with
197 | /// freeing, releasing, or resetting unmanaged resources.
198 | ///
199 | public void Dispose()
200 | {
201 | if (Stream != null)
202 | {
203 | Stream.Dispose();
204 | Stream = null;
205 | }
206 |
207 | if (Reader != null)
208 | {
209 | Reader.Close();
210 | Reader = null;
211 | }
212 | }
213 |
214 | ///
215 | /// Extracts the contents of the zip file to the given directory.
216 | ///
217 | /// Name of the directory.
218 | public void ExtractToDirectory(string directoryName)
219 | {
220 | for (int index = 0; index < Entries.Length; index++)
221 | {
222 | var entry = Entries[index];
223 |
224 | // create target directory for the file
225 | var fileName = Path.Combine(directoryName, entry.Name);
226 | var dirName = Path.GetDirectoryName(fileName);
227 | Directory.CreateDirectory(dirName);
228 |
229 | // save file if it is not only a directory
230 | if (!entry.IsDirectory)
231 | {
232 | Extract(entry.Name, fileName);
233 | }
234 |
235 | var extractProgress = ExtractProgress;
236 | if (extractProgress != null)
237 | {
238 | extractProgress(this, new FileProgressEventArgs(index + 1, Entries.Length, entry.Name));
239 | }
240 | }
241 | }
242 |
243 | ///
244 | /// Extracts the specified file to the specified name.
245 | ///
246 | /// Name of the file in zip archive.
247 | /// Name of the output file.
248 | public void Extract(string fileName, string outputFileName)
249 | {
250 | var entry = GetEntry(fileName);
251 |
252 | using (var outStream = File.Create(outputFileName))
253 | {
254 | Extract(entry, outStream);
255 | }
256 |
257 | var fileInfo = new FileInfo(outputFileName);
258 | if (fileInfo.Length != entry.OriginalSize)
259 | {
260 | throw new InvalidDataException(string.Format(
261 | "Corrupted archive: {0} has an uncompressed size {1} which does not match its expected size {2}",
262 | outputFileName, fileInfo.Length, entry.OriginalSize));
263 | }
264 |
265 | File.SetLastWriteTime(outputFileName, entry.Timestamp);
266 | }
267 |
268 | private Entry GetEntry(string fileName)
269 | {
270 | fileName = fileName.Replace("\\", "/").Trim().TrimStart('/');
271 | var entry = Entries.FirstOrDefault(e => e.Name == fileName);
272 |
273 | if (entry == null)
274 | {
275 | throw new FileNotFoundException("File not found in the archive: " + fileName);
276 | }
277 |
278 | return entry;
279 | }
280 |
281 | ///
282 | /// Extracts the specified file to the output .
283 | ///
284 | /// Name of the file in zip archive.
285 | /// The output stream.
286 | public void Extract(string fileName, Stream outputStream)
287 | {
288 | Extract(GetEntry(fileName), outputStream);
289 | }
290 |
291 | ///
292 | /// Extracts the specified entry.
293 | ///
294 | /// Zip file entry to extract.
295 | /// The stream to write the data to.
296 | /// is thrown when the file header signature doesn't match.
297 | public void Extract(Entry entry, Stream outputStream)
298 | {
299 | // check file signature
300 | Stream.Seek(entry.HeaderOffset, SeekOrigin.Begin);
301 | if (Reader.ReadInt32() != FileSignature)
302 | {
303 | throw new InvalidDataException("File signature doesn't match.");
304 | }
305 |
306 | // move to file data
307 | Stream.Seek(entry.DataOffset, SeekOrigin.Begin);
308 | var inputStream = Stream;
309 | if (entry.Deflated)
310 | {
311 | inputStream = new DeflateStream(Stream, CompressionMode.Decompress, true);
312 | }
313 |
314 | // allocate buffer, prepare for CRC32 calculation
315 | var count = entry.OriginalSize;
316 | var bufferSize = Math.Min(BufferSize, entry.OriginalSize);
317 | var buffer = new byte[bufferSize];
318 | var crc32Calculator = new Crc32Calculator();
319 |
320 | while (count > 0)
321 | {
322 | // decompress data
323 | var read = inputStream.Read(buffer, 0, bufferSize);
324 | if (read == 0)
325 | {
326 | break;
327 | }
328 |
329 | crc32Calculator.UpdateWithBlock(buffer, read);
330 |
331 | // copy to the output stream
332 | outputStream.Write(buffer, 0, read);
333 | count -= read;
334 | }
335 |
336 | if (crc32Calculator.Crc32 != entry.Crc32)
337 | {
338 | throw new InvalidDataException(string.Format(
339 | "Corrupted archive: CRC32 doesn't match on file {0}: expected {1:x8}, got {2:x8}.",
340 | entry.Name, entry.Crc32, crc32Calculator.Crc32));
341 | }
342 | }
343 |
344 | ///
345 | /// Gets the file names.
346 | ///
347 | public IEnumerable FileNames
348 | {
349 | get
350 | {
351 | return Entries.Select(e => e.Name).Where(f => !f.EndsWith("/")).OrderBy(f => f);
352 | }
353 | }
354 |
355 | private Entry[] entries;
356 |
357 | ///
358 | /// Gets zip file entries.
359 | ///
360 | public Entry[] Entries
361 | {
362 | get
363 | {
364 | if (entries == null)
365 | {
366 | entries = ReadZipEntries().ToArray();
367 | }
368 |
369 | return entries;
370 | }
371 | }
372 |
373 | private IEnumerable ReadZipEntries()
374 | {
375 | if (Stream.Length < 22)
376 | {
377 | yield break;
378 | }
379 |
380 | Stream.Seek(-22, SeekOrigin.End);
381 |
382 | // find directory signature
383 | while (Reader.ReadInt32() != DirectorySignature)
384 | {
385 | if (Stream.Position <= 5)
386 | {
387 | yield break;
388 | }
389 |
390 | // move 1 byte back
391 | Stream.Seek(-5, SeekOrigin.Current);
392 | }
393 |
394 | // read directory properties
395 | Stream.Seek(6, SeekOrigin.Current);
396 | var entries = Reader.ReadUInt16();
397 | var difSize = Reader.ReadInt32();
398 | var dirOffset = Reader.ReadUInt32();
399 | Stream.Seek(dirOffset, SeekOrigin.Begin);
400 |
401 | // read directory entries
402 | for (int i = 0; i < entries; i++)
403 | {
404 | if (Reader.ReadInt32() != EntrySignature)
405 | {
406 | continue;
407 | }
408 |
409 | // read file properties
410 | // TODO: Replace with a proper class to make this method a lot shorter.
411 | Reader.ReadInt32();
412 | bool utf8 = (Reader.ReadInt16() & 0x0800) != 0;
413 | short method = Reader.ReadInt16();
414 | int timestamp = Reader.ReadInt32();
415 | uint crc32 = Reader.ReadUInt32();
416 | int compressedSize = Reader.ReadInt32();
417 | int fileSize = Reader.ReadInt32();
418 | short fileNameSize = Reader.ReadInt16();
419 | short extraSize = Reader.ReadInt16();
420 | short commentSize = Reader.ReadInt16();
421 | int headerOffset = Reader.ReadInt32();
422 | Reader.ReadInt32();
423 | int fileHeaderOffset = Reader.ReadInt32();
424 | var fileNameBytes = Reader.ReadBytes(fileNameSize);
425 | Stream.Seek(extraSize, SeekOrigin.Current);
426 | var fileCommentBytes = Reader.ReadBytes(commentSize);
427 | var fileDataOffset = CalculateFileDataOffset(fileHeaderOffset);
428 |
429 | // decode zip file entry
430 | var encoder = utf8 ? Encoding.UTF8 : Encoding.Default;
431 | yield return new Entry
432 | {
433 | Name = encoder.GetString(fileNameBytes),
434 | Comment = encoder.GetString(fileCommentBytes),
435 | Crc32 = crc32,
436 | CompressedSize = compressedSize,
437 | OriginalSize = fileSize,
438 | HeaderOffset = fileHeaderOffset,
439 | DataOffset = fileDataOffset,
440 | Deflated = method == 8,
441 | Timestamp = ConvertToDateTime(timestamp)
442 | };
443 | }
444 | }
445 |
446 | private int CalculateFileDataOffset(int fileHeaderOffset)
447 | {
448 | var position = Stream.Position;
449 | Stream.Seek(fileHeaderOffset + 26, SeekOrigin.Begin);
450 | var fileNameSize = Reader.ReadInt16();
451 | var extraSize = Reader.ReadInt16();
452 |
453 | var fileOffset = (int)Stream.Position + fileNameSize + extraSize;
454 | Stream.Seek(position, SeekOrigin.Begin);
455 | return fileOffset;
456 | }
457 |
458 | ///
459 | /// Converts DOS timestamp to a instance.
460 | ///
461 | /// The dos timestamp.
462 | /// The instance.
463 | public static DateTime ConvertToDateTime(int dosTimestamp)
464 | {
465 | return new DateTime((dosTimestamp >> 25) + 1980, (dosTimestamp >> 21) & 15, (dosTimestamp >> 16) & 31,
466 | (dosTimestamp >> 11) & 31, (dosTimestamp >> 5) & 63, (dosTimestamp & 31) * 2);
467 | }
468 | }
469 | }
--------------------------------------------------------------------------------
/MonkeModManager/MonkeModManager.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {D30E6357-6B15-4EDC-8DD3-8A6D040D83B2}
8 | WinExe
9 | Properties
10 | MonkeModManager
11 | MonkeModManager
12 | v4.5
13 | 512
14 |
15 | false
16 | publish\
17 | true
18 | Disk
19 | false
20 | Foreground
21 | 7
22 | Days
23 | false
24 | false
25 | true
26 | 0
27 | 1.0.0.%2a
28 | false
29 | true
30 |
31 |
32 | AnyCPU
33 | true
34 | full
35 | false
36 | bin\Debug\
37 | TRACE;DEBUG
38 | prompt
39 | 4
40 | false
41 |
42 |
43 | AnyCPU
44 | pdbonly
45 | true
46 | bin\Release\
47 | TRACE
48 | prompt
49 | 0
50 | false
51 |
52 |
53 | app.manifest
54 |
55 |
56 | monke_transparent.ico
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | Form
73 |
74 |
75 | FormMain.cs
76 |
77 |
78 | Form
79 |
80 |
81 | FormSelectPlatform.cs
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 | FormMain.cs
90 | Designer
91 |
92 |
93 | FormSelectPlatform.cs
94 |
95 |
96 | ResXFileCodeGenerator
97 | Resources.Designer.cs
98 | Designer
99 |
100 |
101 | True
102 | Resources.resx
103 | True
104 |
105 |
106 |
107 |
108 |
109 | SettingsSingleFileGenerator
110 | Settings.Designer.cs
111 |
112 |
113 | True
114 | Settings.settings
115 | True
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 | False
129 | .NET Framework 3.5 SP1
130 | false
131 |
132 |
133 |
134 |
141 |
--------------------------------------------------------------------------------
/MonkeModManager/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Windows.Forms;
4 |
5 | namespace MonkeModManager
6 | {
7 | static class Program
8 | {
9 | ///
10 | /// The main entry point for the application.
11 | ///
12 | [STAThread]
13 | static void Main()
14 | {
15 | Application.EnableVisualStyles();
16 | Application.SetCompatibleTextRenderingDefault(false);
17 | Application.Run(new FormMain());
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/MonkeModManager/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("MonkeModManager")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("MonkeModManager")]
13 | [assembly: AssemblyCopyright("Copyright © 2021")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("61939567-2d7c-40b9-ad5b-818f3c5708ff")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.2.0")]
36 | [assembly: AssemblyFileVersion("1.2.0")]
37 |
--------------------------------------------------------------------------------
/MonkeModManager/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace MonkeModManager.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("MonkeModManager.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/MonkeModManager/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/MonkeModManager/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace MonkeModManager.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.8.1.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/MonkeModManager/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MonkeModManager/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/MonkeModManager/app.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/MonkeModManager/mods.json:
--------------------------------------------------------------------------------
1 | {
2 | "totalMods" : 2,
3 | "0" : {
4 | "name" : "BepInEx",
5 | "tag" : "",
6 | "gitPath" : "BepInEx/BepInEx",
7 | "releaseId" : 1
8 | },
9 | "1" : {
10 | "name" : "Gorilla Cosmetics",
11 | "tag" : "",
12 | "gitPath" : "legoandmars/GorillaCosmetics",
13 | "releaseId" : 0
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/MonkeModManager/monke.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burritosoftware/MonkeModManager/94e7b8a96bd2e4c8b7b5495f56e61680016155af/MonkeModManager/monke.ico
--------------------------------------------------------------------------------
/MonkeModManager/monke.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burritosoftware/MonkeModManager/94e7b8a96bd2e4c8b7b5495f56e61680016155af/MonkeModManager/monke.png
--------------------------------------------------------------------------------
/MonkeModManager/monke_transparent.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burritosoftware/MonkeModManager/94e7b8a96bd2e4c8b7b5495f56e61680016155af/MonkeModManager/monke_transparent.ico
--------------------------------------------------------------------------------
/MonkeModManager/monke_transparent.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/burritosoftware/MonkeModManager/94e7b8a96bd2e4c8b7b5495f56e61680016155af/MonkeModManager/monke_transparent.png
--------------------------------------------------------------------------------
/MonkeModManager/update.txt:
--------------------------------------------------------------------------------
1 | 1
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Monke Mod Manager
2 | 
3 |
4 | This program will install custom mods into Gorilla Tag automatically, and can be re-run in order to update the mods
5 |
6 | The program currently supports
7 |
8 | * [BepInEx](https://github.com/BepInEx/BepInEx) by **The BepInEx Team**
9 | * [Gorilla Cosmetics](https://github.com/legoandmars/GorillaCosmetics) by **Bobbie**
10 |
11 |
12 | This uses the github api to get the latest release of all these mods, so you know you'll always be getting the latest version!
13 | (If you've made a mod that you want added to the installer, send me a message on Discord! `Steven 🎀#0001`)
14 |
--------------------------------------------------------------------------------
/mods.json:
--------------------------------------------------------------------------------
1 | {
2 | "mods" : [
3 | {
4 | "name" : "BepInEx",
5 | "tag" : "",
6 | "gitPath" : "BepInEx/BepInEx",
7 | "releaseId" : 1,
8 | "author" : "BepInEx Team",
9 | "group" : "Core"
10 | },
11 | {
12 | "name" : "Gorilla Cosmetics",
13 | "tag" : "",
14 | "gitPath" : "legoandmars/GorillaCosmetics",
15 | "releaseId" : 0,
16 | "author" : "Bobbie",
17 | "group" : "Cosmetic",
18 | "dependencies" : [
19 | "Newtonsoft.JSON"
20 | ]
21 | },
22 | {
23 | "name" : "Space Monke",
24 | "tag" : "",
25 | "gitPath" : "legoandmars/SpaceMonke",
26 | "releaseId" : 0,
27 | "author" : "Bobbie",
28 | "group" : "Gameplay",
29 | "dependencies" : [
30 | "Utilla"
31 | ]
32 | },
33 | {
34 | "name" : "Utilla",
35 | "tag" : "",
36 | "gitPath" : "legoandmars/Utilla",
37 | "releaseId" : 0,
38 | "author" : "Bobbie",
39 | "group" : "Libraries"
40 | },
41 | {
42 | "name" : "Super Monke",
43 | "tag" : "",
44 | "gitPath" : "jeydevv/Super-Monke",
45 | "releaseId": 1,
46 | "author" : "jeydevv",
47 | "group" : "Gameplay"
48 | },
49 | {
50 | "name" : "Monke Mod Menu",
51 | "tag" : "",
52 | "gitPath" : "jeydevv/MonkeModMenu",
53 | "releaseId": 1,
54 | "author" : "jeydevv",
55 | "group" : "Gameplay"
56 | }
57 | {
58 | "name" : "Computer Interface",
59 | "tag" : "",
60 | "gitPath" : "ToniMacaroni/ComputerInterface",
61 | "releaseId": 0,
62 | "author" : "Toni Macaroni",
63 | "group" : "Libraries",
64 | "dependencies" : [
65 | "Bepinject"
66 | ]
67 | }
68 | {
69 | "name" : "Bepinject",
70 | "tag" : "",
71 | "gitPath" : "Auros/Bepinject",
72 | "releaseId": 0,
73 | "author" : "Auros",
74 | "group" : "Libraries",
75 | "installPath" : "BepInEx/plugins",
76 | "dependencies" : [
77 | "Extenject"
78 | ]
79 | },
80 | {
81 | "name" : "Extenject",
82 | "tag" : "",
83 | "gitPath" : "Auros/Bepinject",
84 | "releaseId": 1,
85 | "author" : "svermeulen",
86 | "group" : "Libraries",
87 | "installPath" : "BepInEx/plugins"
88 | },
89 | {
90 | "name" : "Newtonsoft.JSON",
91 | "tag" : "",
92 | "gitPath" : "legoandmars/Newtonsoft.Json",
93 | "releaseId" : 0,
94 | "author" : "James Newton-King",
95 | "group" : "Libraries"
96 | }
97 | ],
98 | "groups" : [
99 | {
100 | "name" : "Core",
101 | "rank" : 0
102 | },
103 | {
104 | "name" : "Cosmetic",
105 | "rank" : 1
106 | },
107 | {
108 | "name" : "Gameplay",
109 | "rank" : 2
110 | },
111 | {
112 | "name" : "Libraries",
113 | "rank" : 3
114 | }
115 | ]
116 | }
117 |
--------------------------------------------------------------------------------
/update.txt:
--------------------------------------------------------------------------------
1 | 4
2 |
--------------------------------------------------------------------------------