├── .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 | ![Preview](https://i.imgur.com/6mEIBxm.png) 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 | --------------------------------------------------------------------------------