├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── SatisfactorySaveEditor.sln └── SatisfactorySaveEditor ├── App.config ├── Audio ├── WAVHeader.cs └── WaveFinder.cs ├── Changelog ├── v0.1.txt ├── v0.2.txt ├── v1.2.txt ├── v1.3.txt ├── v1.4.txt ├── v1.5.txt ├── v1.6.txt ├── v1.7.txt ├── v1.8.txt ├── v1.9.txt ├── v2.0.txt ├── v2.1.txt ├── v2.2.txt ├── v2.3.txt ├── v3.0.txt ├── v3.1.txt └── v3.2.txt ├── Compression.cs ├── ErrorHandler ├── ErrorHandler.cs ├── frmErrorHandler.cs ├── frmErrorHandler.designer.cs └── frmErrorHandler.resx ├── FeatureReport.cs ├── Help ├── Welcome.txt ├── _no.txt ├── frmApiRegister.txt ├── frmAudioExtract.txt ├── frmChangeLog.txt ├── frmCloudEdit.txt ├── frmCounter.txt ├── frmDeleter.txt ├── frmDuplicator.txt ├── frmElementList.txt ├── frmExport.txt ├── frmHeaderEditor.txt ├── frmHelp.txt ├── frmMain.txt ├── frmManager.txt ├── frmRegionDeleter.txt ├── frmRename.txt └── frmSettings.txt ├── Images ├── Height.png ├── Icons │ ├── audio.ico │ ├── copy.ico │ ├── delete.ico │ ├── edit.ico │ ├── help.ico │ ├── network.ico │ ├── rename.ico │ ├── search.ico │ └── settings.ico ├── Map.png └── MapRender.cs ├── Log.cs ├── Program.cs ├── Properties └── AssemblyInfo.cs ├── PropertyValues ├── ArrayProperty.cs ├── IPropertyValue.cs ├── IntProperty.cs ├── Inventory.cs ├── InventoryItem.cs ├── NoneProperty.cs ├── Property.cs └── StructProperty.cs ├── QuickPlay.cs ├── SMR ├── API.cs ├── HTTP.cs ├── Responses.cs └── Tools.cs ├── SatisfactorySaveEditor.csproj ├── SaveFileComponents ├── CompressedHeader.cs ├── ObjectTypes │ ├── GameBaseObject.cs │ ├── GameObject.cs │ ├── GameScript.cs │ └── Types.cs ├── PropertyString.cs ├── SaveFile.cs ├── SaveFileEntry.cs ├── SaveFileHelper.cs ├── Vector3.cs └── Vector4.cs ├── Settings.cs ├── ShortName.cs ├── Tools.cs ├── UI ├── frmApiRegister.Designer.cs ├── frmApiRegister.cs ├── frmApiRegister.resx ├── frmAudioExtract.Designer.cs ├── frmAudioExtract.cs ├── frmAudioExtract.resx ├── frmChangeLog.Designer.cs ├── frmChangeLog.cs ├── frmChangeLog.resx ├── frmCloudEdit.Designer.cs ├── frmCloudEdit.cs ├── frmCloudEdit.resx ├── frmCounter.Designer.cs ├── frmCounter.cs ├── frmCounter.resx ├── frmDeleter.Designer.cs ├── frmDeleter.cs ├── frmDeleter.resx ├── frmDuplicator.Designer.cs ├── frmDuplicator.cs ├── frmDuplicator.resx ├── frmElementList.Designer.cs ├── frmElementList.cs ├── frmElementList.resx ├── frmExport.Designer.cs ├── frmExport.cs ├── frmExport.resx ├── frmHeaderEditor.Designer.cs ├── frmHeaderEditor.cs ├── frmHeaderEditor.resx ├── frmHelp.Designer.cs ├── frmHelp.cs ├── frmHelp.resx ├── frmMain.Designer.cs ├── frmMain.cs ├── frmMain.resx ├── frmManager.Designer.cs ├── frmManager.cs ├── frmManager.resx ├── frmRegionDeleter.Designer.cs ├── frmRegionDeleter.cs ├── frmRegionDeleter.resx ├── frmRename.Designer.cs ├── frmRename.cs ├── frmRename.resx ├── frmSettings.Designer.cs ├── frmSettings.cs └── frmSettings.resx ├── UpdateHandler.cs └── packages.config /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | [Xx]64/ 19 | [Xx]86/ 20 | [Bb]uild/ 21 | bld/ 22 | [Bb]in/ 23 | [Oo]bj/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | artifacts/ 46 | 47 | *_i.c 48 | *_p.c 49 | *_i.h 50 | *.ilk 51 | *.meta 52 | *.obj 53 | *.pch 54 | *.pdb 55 | *.pgc 56 | *.pgd 57 | *.rsp 58 | *.sbr 59 | *.tlb 60 | *.tli 61 | *.tlh 62 | *.tmp 63 | *.tmp_proj 64 | *.log 65 | *.vspscc 66 | *.vssscc 67 | .builds 68 | *.pidb 69 | *.svclog 70 | *.scc 71 | 72 | # Chutzpah Test files 73 | _Chutzpah* 74 | 75 | # Visual C++ cache files 76 | ipch/ 77 | *.aps 78 | *.ncb 79 | *.opendb 80 | *.opensdf 81 | *.sdf 82 | *.cachefile 83 | *.VC.db 84 | 85 | # Visual Studio profiler 86 | *.psess 87 | *.vsp 88 | *.vspx 89 | *.sap 90 | 91 | # TFS 2012 Local Workspace 92 | $tf/ 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | *.DotSettings.user 101 | 102 | # JustCode is a .NET coding add-in 103 | .JustCode 104 | 105 | # TeamCity is a build add-in 106 | _TeamCity* 107 | 108 | # DotCover is a Code Coverage Tool 109 | *.dotCover 110 | 111 | # NCrunch 112 | _NCrunch_* 113 | .*crunch*.local.xml 114 | nCrunchTemp_* 115 | 116 | # MightyMoose 117 | *.mm.* 118 | AutoTest.Net/ 119 | 120 | # Web workbench (sass) 121 | .sass-cache/ 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.[Pp]ublish.xml 141 | *.azurePubxml 142 | 143 | # TODO: Un-comment the next line if you do not want to checkin 144 | # your web deploy settings because they may include unencrypted 145 | # passwords 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # NuGet Packages 150 | *.nupkg 151 | # The packages folder can be ignored because of Package Restore 152 | **/packages/* 153 | # except build/, which is used as an MSBuild target. 154 | !**/packages/build/ 155 | # Uncomment if necessary however generally it will be regenerated when needed 156 | #!**/packages/repositories.config 157 | # NuGet v3's project.json files produces more ignoreable files 158 | *.nuget.props 159 | *.nuget.targets 160 | 161 | # Microsoft Azure Build Output 162 | csx/ 163 | *.build.csdef 164 | 165 | # Microsoft Azure Emulator 166 | ecf/ 167 | rcf/ 168 | 169 | # Windows Store app package directory 170 | AppPackages/ 171 | BundleArtifacts/ 172 | 173 | # Visual Studio cache files 174 | # files ending in .cache can be ignored 175 | *.[Cc]ache 176 | # but keep track of directories ending in .cache 177 | !*.[Cc]ache/ 178 | 179 | # Others 180 | ClientBin/ 181 | [Ss]tyle[Cc]op.* 182 | ~$* 183 | *~ 184 | *.dbmdl 185 | *.dbproj.schemaview 186 | *.pfx 187 | *.publishsettings 188 | node_modules/ 189 | orleans.codegen.cs 190 | 191 | # RIA/Silverlight projects 192 | Generated_Code/ 193 | 194 | # Backup & report files from converting an old project file 195 | # to a newer Visual Studio version. Backup files are not needed, 196 | # because we have git ;-) 197 | _UpgradeReport_Files/ 198 | Backup*/ 199 | UpgradeLog*.XML 200 | UpgradeLog*.htm 201 | 202 | # SQL Server files 203 | *.mdf 204 | *.ldf 205 | 206 | # Business Intelligence projects 207 | *.rdl.data 208 | *.bim.layout 209 | *.bim_*.settings 210 | 211 | # Microsoft Fakes 212 | FakesAssemblies/ 213 | 214 | # GhostDoc plugin setting file 215 | *.GhostDoc.xml 216 | 217 | # Node.js Tools for Visual Studio 218 | .ntvs_analysis.dat 219 | 220 | # Visual Studio 6 build log 221 | *.plg 222 | 223 | # Visual Studio 6 workspace options file 224 | *.opt 225 | 226 | # Visual Studio LightSwitch build output 227 | **/*.HTMLClient/GeneratedArtifacts 228 | **/*.DesktopClient/GeneratedArtifacts 229 | **/*.DesktopClient/ModelManifest.xml 230 | **/*.Server/GeneratedArtifacts 231 | **/*.Server/ModelManifest.xml 232 | _Pvt_Extensions 233 | 234 | # LightSwitch generated files 235 | GeneratedArtifacts/ 236 | ModelManifest.xml 237 | 238 | # Paket dependency manager 239 | .paket/paket.exe 240 | 241 | # FAKE - F# Make 242 | .fake/ 243 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 AyrA 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SatisfactorySaveEditor", "SatisfactorySaveEditor\SatisfactorySaveEditor.csproj", "{5A95AFC0-2278-42AB-B18F-BEE58E642337}" 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 | {5A95AFC0-2278-42AB-B18F-BEE58E642337}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {5A95AFC0-2278-42AB-B18F-BEE58E642337}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {5A95AFC0-2278-42AB-B18F-BEE58E642337}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {5A95AFC0-2278-42AB-B18F-BEE58E642337}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Audio/WAVHeader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | 5 | namespace SatisfactorySaveEditor.Audio 6 | { 7 | public class WAVHeader 8 | { 9 | public uint RIFFSize 10 | { get; set; } 11 | public ushort AudioFormat 12 | { get; set; } 13 | public ushort ChannelCount 14 | { get; set; } 15 | public uint SampleRate 16 | { get; set; } 17 | public uint ByteRate 18 | { get; set; } 19 | public ushort BlockAlign 20 | { get; set; } 21 | public ushort BitsPerSample 22 | { get; set; } 23 | public uint DataSize 24 | { get; set; } 25 | public long DataOffset 26 | { get; set; } 27 | 28 | public TimeSpan Duration 29 | { 30 | get 31 | { 32 | if (ChannelCount == 0 || BitsPerSample == 0) 33 | { 34 | return TimeSpan.FromSeconds(0); 35 | } 36 | var BytesPerSecond = SampleRate * BitsPerSample * ChannelCount / 8; 37 | return TimeSpan.FromSeconds(DataSize / BytesPerSecond); 38 | } 39 | } 40 | 41 | public WAVHeader(Stream Input) 42 | { 43 | var DE = Encoding.Default; 44 | using (var BR = new BinaryReader(Input, DE, true)) 45 | { 46 | if (DE.GetString(BR.ReadBytes(4)) != "RIFF") 47 | { 48 | throw new InvalidDataException("Wave file must start with 'RIFF'"); 49 | } 50 | RIFFSize = BR.ReadUInt32(); 51 | if (DE.GetString(BR.ReadBytes(4)) != "WAVE") 52 | { 53 | throw new InvalidDataException("'WAVE' chunk expected but not there"); 54 | } 55 | if (DE.GetString(BR.ReadBytes(4)) != "fmt ") 56 | { 57 | throw new InvalidDataException("Format chunk expected but not there"); 58 | } 59 | var Chunksize = BR.ReadUInt32(); 60 | AudioFormat = BR.ReadUInt16(); 61 | ChannelCount = BR.ReadUInt16(); 62 | SampleRate = BR.ReadUInt32(); 63 | ByteRate = BR.ReadUInt32(); 64 | BlockAlign = BR.ReadUInt16(); 65 | BitsPerSample = BR.ReadUInt16(); 66 | //Discard rest of chunk 67 | BR.ReadBytes((int)(Chunksize - 16)); 68 | while (DE.GetString(BR.ReadBytes(4)) != "data") 69 | { 70 | var Length = BR.ReadUInt32(); 71 | BR.ReadBytes((int)Length); 72 | } 73 | DataSize = BR.ReadUInt32(); 74 | DataOffset = Input.Position; 75 | } 76 | } 77 | 78 | public void Write(Stream Output) 79 | { 80 | var DE = Encoding.Default; 81 | using (var BW = new BinaryWriter(Output, DE, true)) 82 | { 83 | BW.Write(DE.GetBytes("RIFF")); 84 | BW.Write((uint)(DataOffset + DataSize)); 85 | BW.Write(DE.GetBytes("WAVEfmt ")); 86 | BW.Write(16); 87 | BW.Write(AudioFormat); 88 | BW.Write(ChannelCount); 89 | BW.Write(SampleRate); 90 | BW.Write(ByteRate); 91 | BW.Write(BlockAlign); 92 | BW.Write(BitsPerSample); 93 | BW.Write(DE.GetBytes("data")); 94 | BW.Write(DataSize); 95 | BW.Flush(); 96 | } 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Audio/WaveFinder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | 8 | namespace SatisfactorySaveEditor.Audio 9 | { 10 | public static class WaveFinder 11 | { 12 | private static Thread T; 13 | private static volatile bool Cont; 14 | private static List Files; 15 | 16 | public static WaveFileInfo[] WaveFiles 17 | { 18 | get 19 | { 20 | return Files == null ? null : Files.ToArray(); 21 | } 22 | } 23 | 24 | public static bool IsScanning 25 | { 26 | get 27 | { 28 | return T != null && T.IsAlive; 29 | } 30 | } 31 | 32 | public static void StopSearch() 33 | { 34 | if (T != null) 35 | { 36 | Cont = false; 37 | T.Join(); 38 | } 39 | } 40 | 41 | public static void FindAudio(Stream Input) 42 | { 43 | if (T != null) 44 | { 45 | throw new InvalidOperationException("Can't start a secondary scan process"); 46 | } 47 | if (Input == null) 48 | { 49 | throw new ArgumentNullException(nameof(Input)); 50 | } 51 | if (!Input.CanSeek) 52 | { 53 | throw new ArgumentException("Stream must be seekable", nameof(Input)); 54 | } 55 | T = new Thread(Scanner); 56 | T.Priority = ThreadPriority.BelowNormal; 57 | T.IsBackground = true; 58 | T.Start(Input); 59 | } 60 | 61 | private static void Scanner(object o) 62 | { 63 | var S = (Stream)o; 64 | Cont = true; 65 | Files = new List(); 66 | var buffer = new byte[100 * 1000 * 1000]; 67 | var SEQ = Encoding.Default.GetBytes("WAVEfmt "); 68 | #if DEBUG 69 | //For debug only. 70 | //Right now the audio files seem to be towards the end of the file. 71 | //To speed up debugging, we skip the first half of the file. 72 | S.Seek(S.Length / 2, SeekOrigin.Begin); 73 | #endif 74 | while (Cont) 75 | { 76 | if (S.Position > 0) 77 | { 78 | S.Seek(-SEQ.Length, SeekOrigin.Current); 79 | } 80 | var StartPos = S.Position; 81 | int count = S.Read(buffer, 0, buffer.Length); 82 | Cont = count == buffer.Length; 83 | for (var i = 0; i < count - SEQ.Length; i++) 84 | { 85 | //Don't bother doing anything if the first character is no match 86 | if (SEQ[0] == buffer[i]) 87 | { 88 | for (var j = 0; j < SEQ.Length; j++) 89 | { 90 | if (SEQ[j] != buffer[i + j]) 91 | { 92 | break; 93 | } 94 | else if (j == SEQ.Length - 1) 95 | { 96 | Log.Write("WaveFinder: Found audio at {0}", S.Position - count + i); 97 | var LastPos = S.Position; 98 | //Found wave chunk, go to start of it 99 | S.Seek(StartPos + i - 8, SeekOrigin.Begin); 100 | var Info = new WaveFileInfo() 101 | { 102 | FilePosition = StartPos + i - 8, 103 | Header = new WAVHeader(S) 104 | }; 105 | //This is always 1 for some reason in Satisfactory 106 | Log.Write("WaveFinder: Audio channel count: {0}", Info.Header.ChannelCount); 107 | //Info.Header.ChannelCount = 2; 108 | 109 | S.Seek(Info.Header.DataOffset, SeekOrigin.Begin); 110 | if (IsOgg(S)) 111 | { 112 | Info.Type = WaveFileType.OGG; 113 | } 114 | else if (Info.Header.SampleRate == 44100 && Info.Header.BitsPerSample == 16) 115 | { 116 | Info.Type = WaveFileType.PCM; 117 | } 118 | else 119 | { 120 | Info.Type = WaveFileType.Invalid; 121 | } 122 | //Remove overlapping section 123 | //This means the Wave file was inside another wave file 124 | if (Overlaps(Info, Files.LastOrDefault())) 125 | { 126 | Log.Write("WaveFinder: Removed overlapping WAV segment at NEW={0}; OLD={1}", Info.FilePosition, Files.Last().FilePosition); 127 | Files.RemoveAt(Files.Count - 1); 128 | } 129 | Files.Add(Info); 130 | //Restore stream position 131 | S.Seek(LastPos, SeekOrigin.Begin); 132 | } 133 | } 134 | } 135 | } 136 | } 137 | T = null; 138 | } 139 | 140 | private static bool Overlaps(WaveFileInfo A, WaveFileInfo B) 141 | { 142 | if (A == null || B == null) 143 | { 144 | return false; 145 | } 146 | //End of A 147 | var AE = A.FilePosition + A.Header.DataSize + (A.Header.DataOffset - A.FilePosition); 148 | //End of B 149 | var BE = B.FilePosition + B.Header.DataSize + (B.Header.DataOffset - B.FilePosition); 150 | //A is entirely before B or B is entirely before A 151 | if (AE <= B.FilePosition || BE <= A.FilePosition) 152 | { 153 | return false; 154 | } 155 | 156 | //A starts inside B or B starts inside A 157 | if ((A.FilePosition > B.FilePosition && A.FilePosition < BE) || (B.FilePosition > A.FilePosition && B.FilePosition < AE)) 158 | { 159 | return true; 160 | } 161 | 162 | //A ends inside B or B ends inside A 163 | if ((AE > B.FilePosition && AE < BE) || (BE > A.FilePosition && BE < AE)) 164 | { 165 | return true; 166 | } 167 | 168 | throw new NotImplementedException(""); 169 | } 170 | 171 | private static bool IsOgg(Stream S) 172 | { 173 | byte[] Data = new byte[4]; 174 | S.Seek(-S.Read(Data, 0, Data.Length), SeekOrigin.Current); 175 | return Data[0] == 'O' && Data[1] == 'g' && Data[2] == 'g' && Data[3] == 'S'; 176 | } 177 | } 178 | 179 | public class WaveFileInfo 180 | { 181 | public long FilePosition; 182 | public WaveFileType Type; 183 | public WAVHeader Header; 184 | } 185 | 186 | public enum WaveFileType 187 | { 188 | PCM, 189 | OGG, 190 | Invalid 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v0.1.txt: -------------------------------------------------------------------------------- 1 | Version 0.1 2 | 3 | Released: 2019-04-21 23:10:11 UTC 4 | 5 | First public release of the editor containing a few quick action commands. 6 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v0.2.txt: -------------------------------------------------------------------------------- 1 | Version 0.2 2 | 3 | Released: 2019-05-18 19:10:41 UTC 4 | 5 | - There's now a resize option for your lizard doggos 6 | - You can now remove friendly and hostile mobs 7 | - You can restore the slugs 8 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v1.2.txt: -------------------------------------------------------------------------------- 1 | Version 1.2 2 | 3 | Released: 2019-06-12 18:55:08 UTC 4 | 5 | You can now duplicate and delete entries from the file. 6 | Be aware that it will not automatically update related/dependent entries in the file. 7 | For example deleting a container this way will not get rid of the inventory entry of said container. 8 | 9 | The UI should also work consistently now. 10 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v1.3.txt: -------------------------------------------------------------------------------- 1 | Version 1.3 2 | 3 | Released: 2019-06-30 11:55:18 UTC 4 | 5 | UI overhaul 6 | 7 | The UI has been changed to add new features. 8 | 9 | Shortcuts 10 | 11 | The File menu now has commonly accepted shortcuts. 12 | For example CTRL+O to open or F5 to refresh the map. 13 | 14 | Settings 15 | 16 | A settings class has been added to remember settings. 17 | There are no user changeable settings as of now, 18 | but it already remembers which alert boxes it has shown to you. 19 | 20 | Short names 21 | 22 | Much shorter names are now displayed in drop down fields. 23 | The ordering is still alphabetical 24 | 25 | Map 26 | 27 | There is now a map in the main window that renders a crude list of items on your map. 28 | It shows nodes, natural items and player generated items as well as the player position(s). 29 | 30 | The map is automatically exported to the save file location each time you reload it using [F5] or the menu. 31 | 32 | Note: I don't know the actual ingame map coordinates exactly. 33 | The map you will see is only an approximation of the real position but close enough to give you a general overview 34 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v1.4.txt: -------------------------------------------------------------------------------- 1 | Version 1.4 2 | 3 | Released: 2019-07-06 02:36:30 UTC 4 | 5 | Export/Import 6 | 7 | Allows you to copy entries from one save file to another. 8 | This is facilitated via XML code that is placed in the clipboard. 9 | The code can also be copied from or pasted into any text editor. 10 | This allows you to save individual objects to disk. 11 | 12 | Map item finder 13 | 14 | Item select boxes now have an M button next to them to locate the item(s) in the main window map. 15 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v1.5.txt: -------------------------------------------------------------------------------- 1 | Version 1.5 2 | 3 | Released: 2019-07-20 00:04:00 UTC 4 | 5 | New Fature: Save File Manager 6 | 7 | The save file manager allows you to easily manage all your satisfactory save files: 8 | 9 | - Show all files in a tree structure, sorted by session name 10 | - Open file by double clicking or [ENTER] key 11 | - Rename file without opening it in the editor 12 | - Delete unwanted saves 13 | - Rendering map preview of a save file 14 | - Duplicator to easily experiment with files 15 | - Backup and import save files. This supports compressed saves 16 | 17 | The save file manager shows corrupted save files in a different color. 18 | Most options are unavailable for those. 19 | 20 | Compression 21 | 22 | Save files are compressed using gzip. 23 | You can use tools like 7-zip to open a compressed save file and decompress it. 24 | Compression saves 90% of space on average due to the high verbosity of the save file. 25 | 26 | Note: You can't directly load compressed save files into satisfactory. 27 | Import using the button in the save file manager or decompress manually. 28 | The save file importer automatically detects if decompression is necessary or not. 29 | 30 | New Feature: Clear String List 31 | 32 | The "Clear String List" option removes the list of items considered removed from the map. 33 | This is useful if you used the deleter form to reset map features, for example the poison plants. 34 | 35 | New Feature: Help 36 | 37 | Pressing [F1] at any time opens a help window with relevant information to the current location you are at. 38 | You can also open the main window help using the "Help" menu option 39 | 40 | New Feature: Updater 41 | 42 | You can now update the client. 43 | This is a real updater. 44 | It will not just open a website or only download a file but actually updates your version. 45 | You can either update manually through the red main menu item that is shown when an update is available, 46 | or you can wait and the application automatically updates during the next launch. 47 | 48 | Map image 49 | 50 | - Foundations are now grey 51 | - Walkways are now white 52 | 53 | Bug fixes 54 | 55 | - Only editing the header is now detected as a change 56 | - Removing the last item in the deleter list no longer crashes 57 | - Saves with empty session names are now supported 58 | 59 | Other changes 60 | 61 | Project structure now in order 62 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v1.6.txt: -------------------------------------------------------------------------------- 1 | Version 1.6 2 | 3 | Released: 2019-07-21 14:50:26 UTC 4 | 5 | New Feature: Region based deleter 6 | 7 | The region deleter allows you to remove entries based on map location. 8 | 9 | Usage is quite simple: 10 | 11 | Load a save file and open the region deleter (Menu: General Actions). 12 | Repeatedly click on the map to set coordinates of a polygon. 13 | The polygon is rendered after the 3rd click. 14 | 15 | After you marked the region you want to clear, use the [DEL] key or the menu. 16 | This will show a list of all objects about to be removed and those being ignored. 17 | Using the [DEL] key you can move selected entries between the two lists. 18 | 19 | Other changes 20 | 21 | - Forms can now be closed using the [ESC] key 22 | - Updater no longer fires in debug mode. 23 | - Reorganizing "General Actions" menu to combine the two delete options. 24 | - Added icons 25 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v1.7.txt: -------------------------------------------------------------------------------- 1 | Version 1.7 2 | 3 | Released: 2019-07-23 18:50:54 UTC 4 | 5 | This version adds a logger that makes debugging issues easier. 6 | By default the file is placed in the directory the executable is in and it's named lastrun.log 7 | 8 | It's overwritten each time you run the application again. -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v1.8.txt: -------------------------------------------------------------------------------- 1 | Version 1.8 2 | 3 | Released: 2019-07-24 02:20:15 UTC 4 | 5 | This update fixes the error handling: 6 | 7 | - Proper message is now written to the log files 8 | - Better error message is generated for invalid save files. 9 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v1.9.txt: -------------------------------------------------------------------------------- 1 | Version 1.9 2 | 3 | Released: 2019-07-27 19:36:57 UTC 4 | 5 | The primary function of this update is to allow the user to automatically report errors. 6 | If the user wants to, it uploads the log, 7 | error message and current save file to our server for later analysis. 8 | 9 | More Features: 10 | 11 | - Windows that pop up are now placed more predictable 12 | - Some dialog windows were enlarged to avoid clipping of controls in Windows 8 and later. 13 | - Object counter. Shows types and numbers of all things in your save file. 14 | - Protect hub from being deleted by default in map area delete mode. 15 | - "M" button in object deleter now shows the correct item (was off by one) -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v2.0.txt: -------------------------------------------------------------------------------- 1 | Version 2.0 2 | 3 | Released: 2019-08-01 23:22:58 UTC 4 | 5 | This version brings you the Audio Extracter. 6 | It allows you to extract music and more from the game automatically. 7 | Check its help by pressing [F1] when the form is open. 8 | 9 | This version requires QuickPlay.exe. 10 | If the editor has trouble downloading it, you can do so manually here: 11 | https://cable.ayra.ch/satisfactory/QuickPlay.exe 12 | 13 | Other changes 14 | 15 | - The editor is now configured to prefer 64 bit instructions. 16 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v2.1.txt: -------------------------------------------------------------------------------- 1 | Version 2.1 2 | 3 | Released: 2019-08-05 19:02:17 UTC 4 | 5 | Visible changes: 6 | 7 | - Fixed button layout in a few windows (nicer alignment, tab order, resizing) 8 | - Updated the very outdated help from the main window to reflect the latest few changes to the menus 9 | - New "Settings" option under the "More" item 10 | - Adding and updating tooltip texts to all main window menu items 11 | - "Help" menu can open the editor website now 12 | - Changelog window 13 | 14 | Invisible changes: 15 | 16 | - QuickPlay downloader is now its own component 17 | - QuickPlay downloader retries failed downloads a few times 18 | - Manual update system 19 | - Commented many functions that were lacking them 20 | - Added more utility functions to the Tools class 21 | - Added more log lines 22 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v2.2.txt: -------------------------------------------------------------------------------- 1 | Version 2.2 2 | 3 | Released: 2019-08-14 01:19:53 UTC 4 | 5 | Visible changes: 6 | 7 | To further improve the application, anonymous usage statistics will be sent to the server from now on. 8 | You can view all submitted data as well as disable statistics in the settings window. 9 | 10 | Invisible changes: 11 | 12 | - Simple Usage Logger -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v2.3.txt: -------------------------------------------------------------------------------- 1 | Version 2.3 2 | 3 | Released: 2019-08-29 21:40:21 UTC 4 | 5 | Visible changes: 6 | 7 | - Command line mode (see help of main window) 8 | - The session name change works properly now 9 | - Map coordinates have been refined to render positions more accurate 10 | 11 | Invisible changes: 12 | 13 | - More robust handling of rendering errors 14 | - Logger logs crashes of the rendering engine now -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v3.0.txt: -------------------------------------------------------------------------------- 1 | Version 3.0 2 | 3 | Released: 2019-09-13 20:47:15 UTC 4 | 5 | Visible changes: 6 | 7 | - Full SMR API integration into the file manager 8 | - Redesign of the settings window 9 | 10 | Invisible changes: 11 | 12 | - Usage reporter has been adapter to support the new SMR API -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v3.1.txt: -------------------------------------------------------------------------------- 1 | Version 3.1 2 | 3 | Released: 2019-09-27 16:05:11 UTC 4 | 5 | Invisible changes: 6 | 7 | - Support for new save file type -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Changelog/v3.2.txt: -------------------------------------------------------------------------------- 1 | Version 3.2 2 | 3 | Latest Release 4 | 5 | Invisible changes: 6 | 7 | - Temporarily downgrade save file until I figure out why it won't load anymore. -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Compression.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.IO.Compression; 4 | 5 | namespace SatisfactorySaveEditor 6 | { 7 | /// 8 | /// Provides Easy access to gzip compression 9 | /// 10 | public static class Compression 11 | { 12 | /// 13 | /// Copies a file to a new location and compresses it 14 | /// 15 | /// Original file 16 | /// New file 17 | /// true, if copied and compressed sucessfully 18 | /// 19 | /// The user must clean up the file referenced by if the function fails. 20 | /// 21 | public static bool CompressFile(string InName, string OutName) 22 | { 23 | Log.Write("Compressing \"{0}\" --> \"{1}\"", InName, OutName); 24 | try 25 | { 26 | using (var IN = File.OpenRead(InName)) 27 | { 28 | using (var OUT = File.Create(OutName)) 29 | { 30 | using (var GZS = new GZipStream(OUT, CompressionLevel.Optimal)) 31 | { 32 | IN.CopyTo(GZS); 33 | } 34 | } 35 | } 36 | } 37 | catch (Exception ex) 38 | { 39 | Log.Write(ex); 40 | return false; 41 | } 42 | return true; 43 | } 44 | 45 | /// 46 | /// Compresses a byte array into gzip 47 | /// 48 | /// byte array 49 | /// Compressed data 50 | public static byte[] CompressData(byte[] In) 51 | { 52 | return CompressData(In, 0, In.Length); 53 | } 54 | 55 | /// 56 | /// Compresses a byte array into gzip 57 | /// 58 | /// byte array 59 | /// array offset 60 | /// byte count 61 | /// Compressed data 62 | public static byte[] CompressData(byte[] In, int Index, int Count) 63 | { 64 | using (var MS = new MemoryStream()) 65 | { 66 | using (var GZS = new GZipStream(MS, CompressionLevel.Optimal)) 67 | { 68 | GZS.Write(In, Index, Count); 69 | } 70 | return MS.ToArray(); 71 | } 72 | } 73 | 74 | /// 75 | /// Compresses a stream 76 | /// 77 | /// Input stream 78 | /// Output stream 79 | /// is left open after the operation 80 | public static void CompressStream(Stream In, Stream Out) 81 | { 82 | using (var GZS = new GZipStream(Out, CompressionLevel.Optimal, true)) 83 | { 84 | In.CopyTo(GZS); 85 | } 86 | } 87 | 88 | /// 89 | /// Copies a file to a new location and decompresses it 90 | /// 91 | /// Original file 92 | /// New file 93 | /// true, if copied and decompressed sucessfully 94 | /// 95 | /// The user must clean up the file referenced by if the function fails. 96 | /// 97 | public static bool DecompressFile(string InName, string OutName) 98 | { 99 | Log.Write("Decompressing \"{0}\" --> \"{1}\"", InName, OutName); 100 | try 101 | { 102 | using (var IN = File.OpenRead(InName)) 103 | { 104 | using (var OUT = File.Create(OutName)) 105 | { 106 | using (var GZS = new GZipStream(IN, CompressionMode.Decompress)) 107 | { 108 | GZS.CopyTo(OUT); 109 | } 110 | } 111 | } 112 | } 113 | catch (Exception ex) 114 | { 115 | Log.Write(ex); 116 | return false; 117 | } 118 | return true; 119 | } 120 | 121 | /// 122 | /// Decompresses a byte array from gzip 123 | /// 124 | /// Compressed byte array 125 | /// array offset 126 | /// byte count 127 | /// Decompressed data 128 | public static byte[] DecompressData(byte[] In) 129 | { 130 | return DecompressData(In, 0, In.Length); 131 | } 132 | 133 | /// 134 | /// Decompresses a byte array from gzip 135 | /// 136 | /// Compressed byte array 137 | /// array offset 138 | /// byte count 139 | /// Decompressed data 140 | public static byte[] DecompressData(byte[] In, int Index, int Count) 141 | { 142 | using (var MS = new MemoryStream(In, Index, Count)) 143 | { 144 | using (var OUT = new MemoryStream()) 145 | { 146 | using (var GZS = new GZipStream(MS, CompressionMode.Decompress)) 147 | { 148 | GZS.CopyTo(OUT); 149 | } 150 | return OUT.ToArray(); 151 | } 152 | } 153 | } 154 | 155 | /// 156 | /// Decompresses a stream 157 | /// 158 | /// Input stream 159 | /// Output stream 160 | /// is left open after the operation 161 | public static void DecompressStream(Stream In, Stream Out) 162 | { 163 | using (var GZS = new GZipStream(In, CompressionMode.Decompress, true)) 164 | { 165 | GZS.CopyTo(Out); 166 | } 167 | } 168 | 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/ErrorHandler/frmErrorHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace SatisfactorySaveEditor 5 | { 6 | internal partial class frmErrorHandler : Form 7 | { 8 | private bool reported; 9 | private ErrorHandler Handler; 10 | private Exception SourceException; 11 | 12 | public frmErrorHandler(Exception E, ErrorHandler EH) 13 | { 14 | InitializeComponent(); 15 | reported = false; 16 | Handler = EH; 17 | SourceException = E; 18 | tbReport.Text = ErrorHandler.generateReport(SourceException); 19 | } 20 | 21 | private void btnClose_Click(object sender, EventArgs e) 22 | { 23 | if (!reported) 24 | { 25 | switch (MessageBox.Show("You have not yet reported the error. This makes it hard to improve the application. Do you want to report it before terminating the application?", "Unsent report", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question)) 26 | { 27 | case DialogResult.No: 28 | Close(); 29 | break; 30 | case DialogResult.Yes: 31 | if (report()) 32 | { 33 | Close(); 34 | } 35 | break; 36 | default: 37 | break; 38 | } 39 | } 40 | else 41 | { 42 | Close(); 43 | } 44 | } 45 | 46 | private bool report() 47 | { 48 | if (reported) 49 | { 50 | MessageBox.Show("You have already reported the error", "Error reporting", MessageBoxButtons.OK, MessageBoxIcon.Information); 51 | } 52 | else 53 | { 54 | if (Handler.raise(SourceException)) 55 | { 56 | reported = true; 57 | } 58 | } 59 | return reported; 60 | } 61 | 62 | private void btnReport_Click(object sender, EventArgs e) 63 | { 64 | var sent = report(); 65 | btnReport.Enabled = !sent; 66 | if (!sent) 67 | { 68 | Tools.E($"Unable to send error report.\r\nDetails: {Handler.LastReportResult}", "Errror Report"); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/ErrorHandler/frmErrorHandler.designer.cs: -------------------------------------------------------------------------------- 1 | namespace SatisfactorySaveEditor 2 | { 3 | internal partial class frmErrorHandler 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(frmErrorHandler)); 33 | this.btnClose = new System.Windows.Forms.Button(); 34 | this.lblTitle = new System.Windows.Forms.Label(); 35 | this.tbReport = new System.Windows.Forms.TextBox(); 36 | this.btnReport = new System.Windows.Forms.Button(); 37 | this.ttMain = new System.Windows.Forms.ToolTip(this.components); 38 | this.SuspendLayout(); 39 | // 40 | // btnClose 41 | // 42 | this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 43 | this.btnClose.DialogResult = System.Windows.Forms.DialogResult.Cancel; 44 | this.btnClose.Location = new System.Drawing.Point(316, 335); 45 | this.btnClose.Name = "btnClose"; 46 | this.btnClose.Size = new System.Drawing.Size(75, 23); 47 | this.btnClose.TabIndex = 2; 48 | this.btnClose.Text = "&Close"; 49 | this.ttMain.SetToolTip(this.btnClose, "Close this message and terminate the Application"); 50 | this.btnClose.UseVisualStyleBackColor = true; 51 | this.btnClose.Click += new System.EventHandler(this.btnClose_Click); 52 | // 53 | // lblTitle 54 | // 55 | this.lblTitle.AutoSize = true; 56 | this.lblTitle.Font = new System.Drawing.Font("Microsoft Sans Serif", 22F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 57 | this.lblTitle.Location = new System.Drawing.Point(12, 9); 58 | this.lblTitle.Name = "lblTitle"; 59 | this.lblTitle.Size = new System.Drawing.Size(401, 36); 60 | this.lblTitle.TabIndex = 0; 61 | this.lblTitle.Text = "An unexpected error occured"; 62 | // 63 | // tbReport 64 | // 65 | this.tbReport.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 66 | | System.Windows.Forms.AnchorStyles.Left) 67 | | System.Windows.Forms.AnchorStyles.Right))); 68 | this.tbReport.BackColor = System.Drawing.SystemColors.Window; 69 | this.tbReport.Font = new System.Drawing.Font("Courier New", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 70 | this.tbReport.Location = new System.Drawing.Point(18, 63); 71 | this.tbReport.Multiline = true; 72 | this.tbReport.Name = "tbReport"; 73 | this.tbReport.ReadOnly = true; 74 | this.tbReport.ScrollBars = System.Windows.Forms.ScrollBars.Both; 75 | this.tbReport.Size = new System.Drawing.Size(454, 258); 76 | this.tbReport.TabIndex = 1; 77 | // 78 | // btnReport 79 | // 80 | this.btnReport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 81 | this.btnReport.Location = new System.Drawing.Point(397, 335); 82 | this.btnReport.Name = "btnReport"; 83 | this.btnReport.Size = new System.Drawing.Size(75, 23); 84 | this.btnReport.TabIndex = 3; 85 | this.btnReport.Text = "&Send"; 86 | this.ttMain.SetToolTip(this.btnReport, "Try to report the error automatically"); 87 | this.btnReport.UseVisualStyleBackColor = true; 88 | this.btnReport.Click += new System.EventHandler(this.btnReport_Click); 89 | // 90 | // frmErrorHandler 91 | // 92 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 93 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 94 | this.ClientSize = new System.Drawing.Size(492, 373); 95 | this.Controls.Add(this.tbReport); 96 | this.Controls.Add(this.lblTitle); 97 | this.Controls.Add(this.btnReport); 98 | this.Controls.Add(this.btnClose); 99 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 100 | this.MinimumSize = new System.Drawing.Size(500, 400); 101 | this.Name = "frmErrorHandler"; 102 | this.Text = "Unexpected error occured"; 103 | this.ResumeLayout(false); 104 | this.PerformLayout(); 105 | 106 | } 107 | 108 | #endregion 109 | 110 | private System.Windows.Forms.Button btnClose; 111 | private System.Windows.Forms.Label lblTitle; 112 | private System.Windows.Forms.TextBox tbReport; 113 | private System.Windows.Forms.Button btnReport; 114 | private System.Windows.Forms.ToolTip ttMain; 115 | } 116 | } -------------------------------------------------------------------------------- /SatisfactorySaveEditor/FeatureReport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace SatisfactorySaveEditor 10 | { 11 | public static class FeatureReport 12 | { 13 | public const string REPORT_URL = "https://cable.ayra.ch/satisfactory/report/"; 14 | 15 | public enum Feature : int 16 | { 17 | OpenFile, 18 | OpenByCommandLine, 19 | Manager, 20 | SaveFile, 21 | QuickAction, 22 | DoBackup, 23 | RestoreBackup, 24 | EditHeader, 25 | CountItems, 26 | DuplicateItems, 27 | DeleteByType, 28 | DeleteByArea, 29 | XmlExport, 30 | XmlImport, 31 | ClearStringList, 32 | ExtractAudio, 33 | ChangeSettings, 34 | DisableReport, 35 | HelpRequest, 36 | ViewChanges, 37 | ApiAction, 38 | ApiRegisterAuto, 39 | /// 40 | /// Application is running in debug mode 41 | /// 42 | DebugMode, 43 | /// 44 | /// Application was terminated due to an unhandled exception 45 | /// 46 | TerminateByError, 47 | /// 48 | /// The image renderer crashed 49 | /// 50 | RendererCrash, 51 | } 52 | 53 | public static Guid Id 54 | { get; set; } 55 | 56 | private static List UsedFeatures; 57 | 58 | static FeatureReport() 59 | { 60 | UsedFeatures = new List(); 61 | Id = Guid.NewGuid(); 62 | } 63 | 64 | public static void Used(Feature F) 65 | { 66 | UsedFeatures.Add(F); 67 | } 68 | 69 | public static void Report() 70 | { 71 | //Don't bother reporting if it doesn't works 72 | if (Id == Guid.Empty) 73 | { 74 | Log.Write("{0}: Reporting Failed. ID not set", nameof(FeatureReport)); 75 | return; 76 | } 77 | //Prevent multiple reports from stacking up 78 | lock (UsedFeatures) 79 | { 80 | //Don't bother reporting if nothing is there to report 81 | if (UsedFeatures.Count > 0) 82 | { 83 | var BaseFields = new string[] { 84 | $"id={Id}", 85 | $"version={Tools.CurrentVersion}" 86 | }; 87 | WebResponse Res = null; 88 | var Req = WebRequest.CreateHttp(REPORT_URL); 89 | Req.UserAgent = UpdateHandler.UserAgent; 90 | Req.Method = "POST"; 91 | Req.ContentType = "application/x-www-form-urlencoded"; 92 | try 93 | { 94 | var Features = Encoding.UTF8.GetBytes(string.Join("&", UsedFeatures.Distinct().Select(m => $"feature[]={m}").Concat(BaseFields))); 95 | Req.ContentLength = Features.Length; 96 | using (var S = Req.GetRequestStream()) 97 | { 98 | S.Write(Features, 0, Features.Length); 99 | S.Close(); 100 | } 101 | Res = Req.GetResponse(); 102 | } 103 | catch (WebException ex) 104 | { 105 | try 106 | { 107 | using (Res = ex.Response) 108 | { 109 | using (var SR = new StreamReader(Res.GetResponseStream())) 110 | { 111 | #if DEBUG 112 | Tools.E(SR.ReadToEnd(), "DEBUG Report Error"); 113 | #else 114 | Log.Write("{0}: Reporting failed. Data: {1}", nameof(FeatureReport), SR.ReadToEnd()); 115 | #endif 116 | } 117 | } 118 | } 119 | catch 120 | { 121 | Log.Write("{0}: Reporting failed (unable to provide a reason, response is null)", nameof(FeatureReport)); 122 | } 123 | return; 124 | } 125 | catch (Exception ex) 126 | { 127 | Log.Write("{0}: Reporting failed (unable to provide a reason, not a WebException)", nameof(FeatureReport)); 128 | Log.Write(ex); 129 | } 130 | //Report OK. Clear list and log message 131 | UsedFeatures.Clear(); 132 | using (Res) 133 | { 134 | using (var SR = new StreamReader(Res.GetResponseStream())) 135 | { 136 | Log.Write("{0}: Reporting OK. Data: {1}", nameof(FeatureReport), SR.ReadToEnd()); 137 | } 138 | } 139 | } 140 | } 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/Welcome.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor 2 | 3 | Welcome to the Satisfactory Save File Editor. 4 | This application allows you to make generic changes to your save files. 5 | If you are stuck at any point, press [F1] to bring up this window again. 6 | 7 | Getting Help: 8 | 9 | If you encounter any problems, contact /u/AyrA_ch on reddit. 10 | 11 | Reporting Bugs: 12 | 13 | If you believe you found a bug, you can contact me on reddit, 14 | or if you know how, open an issue on Github: 15 | https://github.com/AyrA/SatisfactorySaveEditor/issues 16 | 17 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/_no.txt: -------------------------------------------------------------------------------- 1 | There is no help available for this item right now. 2 | 3 | If you believe this is an error, 4 | you can open an issue at https://github.com/AyrA/SatisfactorySaveEditor/issues 5 | 6 | The missing file is {0} -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmApiRegister.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor API Registration Form 2 | 3 | Here you can register your API key to manage your save files in the cloud. 4 | 5 | You can either enter the key manually or click the button to get the key from the API automatically. 6 | 7 | Clearing the key from the text box will disable the API again. 8 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmAudioExtract.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor Audio Extracter 2 | 3 | This form helps you in extracting audio files from your satisfactory game. 4 | Files included are: 5 | - Music 6 | - Ambient noise 7 | - A.D.A messages 8 | 9 | The files have an invalid header and are fixed up properly to play in any standard player. 10 | Most files are RIFF WAVE, A.D.A messages are OGG. 11 | 12 | WAV Format: PCM, 16 bit, LE, 1ch or 2ch(Mono or Stereo) 13 | OGG Format: Opus, VBR, 1ch(Mono), Converter arguments: --vbr --padding 0 --bitrate 64 --comp 10 --serial 0 --framesize 20 14 | 15 | Thanks to the uncompressed wav files you will find that the music sounds great. 16 | If a WAV file is Mono or Stereo depends on the type of sound. 17 | Music and "global" effects are usually Stereo. 18 | Ambient noise that is location sensitive is usually Mono. 19 | 20 | File name Format 21 | ================ 22 | The files don't have names on their own in the game file, so they are numbered from 0001 onwards. 23 | WAV and OGG share the same number pool. 24 | 25 | Game Required 26 | ============= 27 | You need to have the game installed. 28 | Doesn't matters if it's the weekend demo, early access or experimental version. 29 | The experimental version tends to give most files. 30 | 31 | Finding the file 32 | ================ 33 | We try to find the required file for you when you open the form. 34 | This works if the game is installed in the program files folder only. 35 | It prefers the experimental over the early access version. 36 | 37 | Manual file search 38 | ================== 39 | If it's not possible to find the file automatically or you want to try a different file, 40 | you can search for it manually. 41 | You need to specify the "FactoryGame-WindowsNoEditor.pak" file. 42 | It's inside the "FactoryGame\Content\Paks" folder of your satisfactory installation. 43 | 44 | WARNING! You can select files from other games. 45 | This is likely not going to extract the files properly unless they are unencrypted. 46 | For wav files, they will be unusable unless they are in the format specified above 47 | because the header is rewritten. 48 | 49 | This application is open source. 50 | You can of course just take out the wav file scanner part and adapt it to support your game. 51 | 52 | Automatic file search 53 | ===================== 54 | As an alternative to a manual search, the automatic search is offered. 55 | This will scan all hard drives (it will not scan network drives or removable drives). 56 | The scan is aborted as soon as a game file is found. 57 | 58 | Audio extraction 59 | ================ 60 | Audio extraction is done in two steps. 61 | First, we scan the entire game file for audio streams. 62 | This is a time consuming process because the file is 15 GB in size. 63 | Note: Because we try to process the input file at maximum speed, 64 | you will find that your computer will operate sluggish until this process is complete. 65 | 66 | The second step is filtering and extraction of the actual files. 67 | We remove files that are invalid and export all others, ordered by type, then size descending. 68 | This means you end up with the background music first and the A.D.A. messages last. 69 | 70 | Player 71 | ====== 72 | This application features a very crude audio player that's displayed when at least one file has been found. 73 | It's meant to preview the files. 74 | For proper playback, add the files in the "Audio" directory to your audio player. 75 | 76 | Note: This application uses QuickPlay.exe to play the audio files. 77 | It will automatically be downloaded if it doesn't exists. 78 | 79 | File protection 80 | =============== 81 | Files will not be overwritten. 82 | Delete the "Audio" folder if you want to search for audio files again. 83 | This is useful after a game update. 84 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmChangeLog.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor Changelog 2 | 3 | This form simply shows all changes that were made in a specific version 4 | 5 | It pops up automatically after an update 6 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmCloudEdit.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor Cloud File Editor 2 | 3 | This window allows you to edit the properties of maps in your cloud account. 4 | 5 | Conflicting categories are automatically disabled. 6 | If you can't select a checkbox, it's because another category is mutually exclusive to the disabled one. 7 | 8 | Enabled category checkboxes have a tooltip on mouse hover that describes the category. 9 | 10 | You can also change the hidden id. 11 | Note that this change is in effect immediately, you don't need to save other changes. 12 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmCounter.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor Object Counter 2 | 3 | This form counts all objects in your save file. 4 | 5 | You can select multiple entries with the mouse by holding down [SHIFT] or [CTRL]. 6 | 7 | Pressing [ENTER] renders the selected items on the main window map. 8 | Double clicking on an entry renders that item only. 9 | 10 | Entries with a "disabled" background color don't have coordinates, or they are unset. 11 | The button on the bottom of the window allows you to show and hide those items. -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmDeleter.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor Object Deleter 2 | 3 | Here you can delete any entry from the save file. 4 | Deleting objects will not give you the resources for them. 5 | 6 | Select the object to delete from the list and then chose the range you want to delete. 7 | 8 | The [M] button will render your selection on the main window map. 9 | 10 | Tip: After you are done deleting and are back to the main window, press [F5] to refresh the map. -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmDuplicator.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor Item Duplicator 2 | 3 | You can clone any kind of entry with this window, 4 | including those you almost certainly should not. 5 | 6 | You can optionally apply an offset to the copied item. 7 | When cloning multiple times, the offset is calculated from the previously placed object, 8 | essentially creating a chain of them. 9 | 10 | Be aware that map coordinate units are approximately centimeters. 11 | An 8x2 foundation is therefore X=800, Y=800, Z=200 12 | 13 | The offset is pretty much mandatory when cloning an object lots of times to avoid significant lag. 14 | 15 | The [M] button will render your selection (without potential duplicates) on the main window map. 16 | 17 | Tip: After you are done duplicating and are back to the main window, press [F5] to refresh the map. -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmElementList.txt: -------------------------------------------------------------------------------- 1 | Element Process List 2 | 3 | This form shows the item type and count that are to be processed. 4 | 5 | The top list contains items that are to be processed. 6 | The bottom list contains items to skip. 7 | 8 | Select Items and use the [DEL] key to move it to the other list. 9 | 10 | Use the "Confirm" button to confirm the changes. 11 | To do nothing, move all items to the bottom list and then click "Confirm". 12 | Closing the form using the [X] control will also discard the changes. 13 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmExport.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor Object Exporter 2 | 3 | Here you can export and import individual items or groups of them. 4 | 5 | Data format: 6 | 7 | The exporter uses XML as underlying data format. 8 | If you feel confident enough, you can edit it in a text editor before importing again. 9 | 10 | Exporting: 11 | 12 | Select the item, offset and count you want to export, then click the "Export" button. 13 | The exported content is placed as text in your clipboard. 14 | Feel free to either import it into another save file (see "Import" below) or save it in a text editor of your choice. 15 | 16 | Note: Exports ARE NOT cumulative. Exporting will erase previously exported content from the clipboard. 17 | 18 | Import: 19 | 20 | Click the "Import" button to import whatever entries are in your clipboard. 21 | Optionally apply one of these options: 22 | 23 | "Remove identical existing entries" deletes all entries of the same types you try to import. 24 | For example when you import foundations, it will delete all foundations in the file before importing yours. 25 | This option is most useful if you want to import entries that are not supposed to exist multiple times. 26 | 27 | "Fix internal names" changes the 'InternalName' property of imported items when needed to avoid duplicates. 28 | This is necessary because this name is supposed to be unique. It is therefore a good idea to leave enabled. 29 | 30 | Note: This will not change references that no longer point to a valid name because it was changed. 31 | In most cases, this is not a problem. It will cause some objects to not work properly, 32 | for example storage containers. 33 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmHeaderEditor.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor Header Editor 2 | 3 | The header editor allows you to edit basic header properties that don't directly affect the progress of the file. 4 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmHelp.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor Help Form 2 | 3 | This is the help window. 4 | It is shown whenever you press [F1]. 5 | You can continue to work on your save file while this window is being shown. 6 | 7 | Pressing [F1] at any time will show the help content relevant to the currently open form. 8 | You do not need to close the help window for the new text to show up. 9 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmMain.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor Main Form 2 | 3 | This is the window where you do most of your changes. 4 | The main area of the window is used to show a map of your save file. 5 | An image of the map is saved in the same location your current save game is at. 6 | 7 | Automatic Backups 8 | ================= 9 | This editor will automatically create a single backup of any file you save, 10 | provided that no such backup already exists. 11 | To create multiple backups, use the Save File Manager [CTRL]+[M]. 12 | 13 | File Menu 14 | ========= 15 | 16 | This provides basic File operations 17 | and has the usual commands (Load, Save, Exit, ...) you find in this menu in most applications. 18 | 19 | Quick Actions Menu 20 | ================== 21 | 22 | This provides pre-made bulk actions for your save file. 23 | They are mostly made of features that clean up the map and undo player made changes. 24 | 25 | General Actions Menu 26 | ==================== 27 | 28 | This provides general editing capabilities of the save file. 29 | 30 | More Menu 31 | ========= 32 | 33 | This provides actions not related to save file editing. 34 | All options in this menu are going to work without a loaded save file 35 | 36 | Help Menu 37 | ========= 38 | 39 | Provides help and information. 40 | This menu also contains a manual update check option for when you disable automatic update checks. 41 | 42 | Update Available Menu 43 | ===================== 44 | 45 | This red menu item is only shown when an update has been downloaded sucessfully and is ready for installation. 46 | Clicking this is optional. If you don't, the update is installed during the next launch. 47 | 48 | Command Line 49 | ============ 50 | 51 | Basic operations are available as command line mode now. 52 | This mode is invoked when at least one "switch" is provided. 53 | If only a file name is provided, it will open that file in the normal user interface mode. 54 | Be aware that this application is not a true command line application. 55 | It will spawn its own command line window and always waits for a user key press at the end. 56 | You can combine multiple arguments. 57 | 58 | Arguments: [/verify] [/list] [/pack] [/render] [/info] [/rename ] [SaveFile] 59 | 60 | /verify - Reads the entire file and verifies basic constraints 61 | /list - Lists all entries and counts them 62 | /pack - Compresses file if uncompressed, or uncompresses if compressed. 63 | /rename - Renames the session 64 | /render - Renders a PNG image of the map into the same directory (replaces .sav with .png) 65 | /info - Print some basic information 66 | SaveFile - File to open. Required argument if switches are used -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmManager.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor manager 2 | 3 | Here you can manage all your save games, locally and in the cloud. 4 | 5 | Most options are available via the context menu on the local and the cloud side. 6 | Keyboard (local side): [ENTER] will open a file and [DEL] will delete a file. 7 | Keyboard (cloud side): [ENTER] will preview a file and [DEL] will delete a file. 8 | For import/restore, press the button at the bottom. 9 | 10 | Context menu descriptions (local side): 11 | 12 | Open: 13 | This opens a save file in the current save file editor instance. 14 | You will lose any unsaved progress in a currently opened file. 15 | 16 | Render: 17 | This shows a preview image of the map 18 | 19 | Upload: 20 | This uploads the file to the cloud. 21 | 22 | Rename: 23 | This renames a save file and/or session name. 24 | 25 | Backup: 26 | Creates a compressed backup in a location you can chose from 27 | 28 | Duplicate: 29 | Creates an exact copy of the selected file with a new name. 30 | 31 | Delete: 32 | This deletes a save file after confirmation is given. 33 | Will not delete copies in the cloud. 34 | 35 | Context menu descriptions (remote side): 36 | 37 | Download: 38 | Downloads the map to your local map collection 39 | 40 | Preview: 41 | This shows a preview image of the map 42 | 43 | Copy Hidden Id: 44 | Copies the hidden id, which can be used to install maps that are not public. 45 | 46 | Import Id: 47 | This imports a map by id. 48 | You can use the real or hidden id for this. 49 | 50 | Edit: 51 | Edits properties of this map (name, description, etc) 52 | 53 | Delete: 54 | Deletes the map from the cloud. Will not delete local copies. 55 | 56 | Format for backups: 57 | Backup files are gzip compressed to save on space. 58 | On average, the result will be less than 10% of the original save file size 59 | 60 | Importing and restoring maps: 61 | The import function will autodetect if save files are compressed or not. 62 | 63 | Invalid files: 64 | Invalid files are given in red color. 65 | Most options are unavailable for these. 66 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmRegionDeleter.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor Region Deleter 2 | 3 | This window allows you to delete all objects inside a user selected region. 4 | 5 | Click on the map to set points of a polygon. 6 | To delete the last point, use the right mouse button. 7 | 8 | The objects inside the selected region will be destroyed. 9 | You will not be given back any items in the process. 10 | This includes but is not limited to: 11 | - Items needed to craft the object 12 | - Items in association with the object (items on belts, storage content, etc) 13 | - Power Shards 14 | 15 | Menu Options: 16 | 17 | New Selection (CTRL+N) 18 | This will erase all points and begins selecting from the start 19 | 20 | Delete Objects... (DEL) 21 | Shows a list of objects inside the region and offers to delete them. 22 | 23 | Close (CTRL+Q) 24 | Closes the window. 25 | 26 | Tip: Keep the window maximized for maximum accuracy. 27 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmRename.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor Rename 2 | 3 | Here you can rename the session name and file name of a save file. 4 | Session name and File name must be valid file names. 5 | This means, these characters are forbidden: 6 | 7 | \ / " : ? * < > | 8 | 9 | Additionally forbidden are some special unicode characters which you are very unlikely to type and the "NULL" character. 10 | The "OK" button will become unavailable immediately if any invalid characters are entered or a box is empty. 11 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Help/frmSettings.txt: -------------------------------------------------------------------------------- 1 | Satisfactory Save Editor Settings 2 | 3 | Here you can change various settings. 4 | They are saved immediately when you make a change. 5 | 6 | Dialog Boxes 7 | ============ 8 | 9 | In this section you can mark all hint dialogs as read or unread. 10 | 11 | Update Settings 12 | =============== 13 | 14 | In this section you can change update related settings. 15 | 16 | You can turn automatic updates on or off. 17 | If they are on, we check for updates once at most every 24 hours. 18 | If you turn them off, you get a manual "Check for Updates" in the "Help" main menu. 19 | 20 | After a successful update the change log is shown to give you a quick info of the changes. 21 | You can turn this off using the second checkbox in this section. 22 | You can still at any time bring up the changelog window in the "Help" main menu 23 | 24 | How Automatic Updates Work 25 | ========================== 26 | 27 | Automatic updates are only checked for when the editor is running. 28 | We do not install and run background applications. 29 | This would go against the portable nature of this application. 30 | 31 | During the update, no personal or statistical information is sent to our server. 32 | The update checker gets a document from our server that contains the latest version and a download link. 33 | If your version is older, the file is downloaded. 34 | After a successful download, you get the red "Update Available" main menu item. 35 | You can click this item to install the update immediately, or you can ignore it, 36 | in which case the update is installed during the next start. 37 | Installation takes about half a second as of now. 38 | 39 | Usage Statistics 40 | ================ 41 | 42 | This application collects and submits very limited usage statistics. 43 | The statistics are reported once you close the application or if it gets terminated due to an error. 44 | 45 | What is included: 46 | ----------------- 47 | 48 | - Current version number 49 | - Randomly generated Id 50 | - List of features you used 51 | 52 | What is NEVER included: 53 | ----------------------- 54 | 55 | - Anything related to your satisfactory installation 56 | - Your save games 57 | - Username 58 | - IP address* 59 | - Operating system information (Type, environment, language, etc) 60 | - Information about other installed software 61 | 62 | *) Your IP address will appear in the regular server logs of cable.ayra.ch as does every IP of every visitor. 63 | This is normal and practiced by almost all web servers world wide. 64 | The address WILL NOT be collected from the logs and tied to the Id. 65 | 66 | What we do: 67 | ----------- 68 | 69 | - Analyze your statistics to focus development on the most used features. 70 | - Build and display anonymous statistics for the public to see. 71 | 72 | What we NEVER do: 73 | ----------------- 74 | 75 | - Share your statistics with 3rd parties. 76 | - Sell your statistics. 77 | 78 | Your Usage Statistics Options 79 | ============================= 80 | 81 | You can decide to change the Id each time you launch the application. 82 | This will add a layer of anonymity but it prevents you from viewing your own statistics. 83 | 84 | You can also completely disable usage statistics reporting. -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Images/Height.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyrA/SatisfactorySaveEditor/1c7d5f8b7658541e7e979e5605717da068407140/SatisfactorySaveEditor/Images/Height.png -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Images/Icons/audio.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyrA/SatisfactorySaveEditor/1c7d5f8b7658541e7e979e5605717da068407140/SatisfactorySaveEditor/Images/Icons/audio.ico -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Images/Icons/copy.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyrA/SatisfactorySaveEditor/1c7d5f8b7658541e7e979e5605717da068407140/SatisfactorySaveEditor/Images/Icons/copy.ico -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Images/Icons/delete.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyrA/SatisfactorySaveEditor/1c7d5f8b7658541e7e979e5605717da068407140/SatisfactorySaveEditor/Images/Icons/delete.ico -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Images/Icons/edit.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyrA/SatisfactorySaveEditor/1c7d5f8b7658541e7e979e5605717da068407140/SatisfactorySaveEditor/Images/Icons/edit.ico -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Images/Icons/help.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyrA/SatisfactorySaveEditor/1c7d5f8b7658541e7e979e5605717da068407140/SatisfactorySaveEditor/Images/Icons/help.ico -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Images/Icons/network.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyrA/SatisfactorySaveEditor/1c7d5f8b7658541e7e979e5605717da068407140/SatisfactorySaveEditor/Images/Icons/network.ico -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Images/Icons/rename.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyrA/SatisfactorySaveEditor/1c7d5f8b7658541e7e979e5605717da068407140/SatisfactorySaveEditor/Images/Icons/rename.ico -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Images/Icons/search.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyrA/SatisfactorySaveEditor/1c7d5f8b7658541e7e979e5605717da068407140/SatisfactorySaveEditor/Images/Icons/search.ico -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Images/Icons/settings.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyrA/SatisfactorySaveEditor/1c7d5f8b7658541e7e979e5605717da068407140/SatisfactorySaveEditor/Images/Icons/settings.ico -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Images/Map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AyrA/SatisfactorySaveEditor/1c7d5f8b7658541e7e979e5605717da068407140/SatisfactorySaveEditor/Images/Map.png -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Log.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Runtime.InteropServices; 6 | using System.Threading; 7 | 8 | namespace SatisfactorySaveEditor 9 | { 10 | public static class Log 11 | { 12 | [DllImport("kernel32.dll", CharSet = CharSet.Auto)] 13 | private static extern void OutputDebugString(string message); 14 | 15 | private static TextWriter Logger; 16 | private static Stopwatch SW; 17 | private static List LogMessages; 18 | 19 | public static string Logfile 20 | { get; private set; } 21 | 22 | static Log() 23 | { 24 | LogMessages = new List(); 25 | SW = new Stopwatch(); 26 | SW.Start(); 27 | using (var P = Process.GetCurrentProcess()) 28 | { 29 | try 30 | { 31 | //Try logging into application directory first 32 | Logger = File.CreateText(Logfile = Path.Combine(Path.GetDirectoryName(P.MainModule.FileName), "lastrun.log")); 33 | } 34 | catch 35 | { 36 | try 37 | { 38 | //Try temporary file on errors 39 | var F = Path.GetTempFileName(); 40 | File.Delete(F); 41 | Path.ChangeExtension(F, ".log"); 42 | Logger = File.CreateText(Logfile = F); 43 | } 44 | catch 45 | { 46 | //Give up 47 | Logfile = ""; 48 | Logger = TextWriter.Null; 49 | } 50 | } 51 | } 52 | var DT = DateTime.UtcNow; 53 | Write("{0} {1}: Logger initialized", DT.ToLongDateString(), DT.ToLongTimeString()); 54 | //Flush logger periodically 55 | Thread T = new Thread(delegate () 56 | { 57 | while (Logger != null && Logger != TextWriter.Null) 58 | { 59 | Thread.Sleep(5000); 60 | lock (Logger) 61 | { 62 | Logger.Flush(); 63 | } 64 | } 65 | }); 66 | T.IsBackground = true; 67 | T.Start(); 68 | } 69 | 70 | public static string[] GetMessages() 71 | { 72 | lock (Logger) 73 | { 74 | return LogMessages.ToArray(); 75 | } 76 | } 77 | 78 | public static void Close() 79 | { 80 | lock (Logger) 81 | { 82 | if (Logger != TextWriter.Null) 83 | { 84 | var DT = DateTime.UtcNow; 85 | Write("{0} {1}: Logger ended", DT.ToLongDateString(), DT.ToLongTimeString()); 86 | Logger.Flush(); 87 | Logger.Close(); 88 | Logger.Dispose(); 89 | Logger = TextWriter.Null; 90 | } 91 | } 92 | } 93 | 94 | public static void Write(string Text) 95 | { 96 | var Msg = string.Format("[{0,9}] {1}", SW.ElapsedMilliseconds, Text); 97 | lock (Logger) 98 | { 99 | if (Program.DEBUG) 100 | { 101 | Debug.WriteLine(Msg); 102 | OutputDebugString(Msg); 103 | } 104 | Logger.WriteLine(Msg); 105 | LogMessages.Add(Msg); 106 | if (LogMessages.Count > 1000) 107 | { 108 | LogMessages.RemoveRange(1, LogMessages.Count - 999); 109 | LogMessages[0] = ""; 110 | } 111 | } 112 | } 113 | 114 | public static void Write(string Text, params object[] Args) 115 | { 116 | Write(string.Format(Text, Args)); 117 | } 118 | 119 | public static void Write(Exception ex, bool IsRoot = true) 120 | { 121 | if (ex == null) 122 | { 123 | return; 124 | } 125 | if (IsRoot) 126 | { 127 | Write("=== START: {0} handler ===", ex.GetType().FullName); 128 | } 129 | Write("Error: {0}", ex.Message); 130 | Write("Stack: {0}", ex.StackTrace); 131 | if (ex.Data != null && ex.Data.Count > 0) 132 | { 133 | foreach (var Entry in ex.Data) 134 | { 135 | Write("Data: {0}", Entry); 136 | } 137 | } 138 | if (ex is AggregateException) 139 | { 140 | foreach (var E in ((AggregateException)ex).InnerExceptions) 141 | { 142 | Write(E, false); 143 | } 144 | } 145 | else 146 | { 147 | Write(ex.InnerException, false); 148 | } 149 | if (IsRoot) 150 | { 151 | Write("=== END: {0} handler ===", ex.GetType().FullName); 152 | lock (Logger) 153 | { 154 | //Always flush on exceptions 155 | Logger.Flush(); 156 | } 157 | } 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle("SatisfactorySaveEditor")] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany("AyrA.ch")] 11 | [assembly: AssemblyProduct("SatisfactorySaveEditor")] 12 | [assembly: AssemblyCopyright("Copyright © AyrA.ch 2019")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("5a95afc0-2278-42ab-b18f-bee58e642337")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion("3.2.0.0")] 35 | [assembly: AssemblyFileVersion("3.2.0.0")] 36 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/PropertyValues/ArrayProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace SatisfactorySaveEditor.PropertyValues 6 | { 7 | public class ArrayProperty : IPropertyValue 8 | { 9 | public List Properties; 10 | 11 | public ArrayProperty(BinaryReader BR) 12 | { 13 | Properties = new List(); 14 | var Bytes = BR.ReadInt64(); 15 | var Pos = BR.BaseStream.Position; 16 | while (BR.BaseStream.Position < Pos + Bytes) 17 | { 18 | Properties.Add(new Property(BR)); 19 | } 20 | } 21 | 22 | public void Export(BinaryWriter BW) 23 | { 24 | throw new NotImplementedException(); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/PropertyValues/IPropertyValue.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SatisfactorySaveEditor.PropertyValues 4 | { 5 | public interface IPropertyValue 6 | { 7 | void Export(BinaryWriter BW); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/PropertyValues/IntProperty.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SatisfactorySaveEditor.PropertyValues 4 | { 5 | class IntProperty : IPropertyValue 6 | { 7 | public int UnknownInt { get; private set; } 8 | public int Value { get; private set; } 9 | 10 | public IntProperty(BinaryReader BR) 11 | { 12 | //Always 4 13 | BR.ReadInt32(); 14 | UnknownInt = BR.ReadInt32(); 15 | BR.ReadByte(); 16 | Value = BR.ReadInt32(); 17 | } 18 | 19 | public void Export(BinaryWriter BW) 20 | { 21 | BW.WriteIntString("IntProperty"); 22 | BW.Write(4); 23 | BW.Write(UnknownInt); 24 | BW.Write((byte)0); 25 | BW.Write(Value); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/PropertyValues/Inventory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | 4 | namespace SatisfactorySaveEditor.PropertyValues 5 | { 6 | public class Inventory 7 | { 8 | public List Items; 9 | public Inventory(byte[] Data) 10 | { 11 | Items = new List(); 12 | 13 | using (var MS = new MemoryStream(Data, false)) 14 | { 15 | using (var BR = new BinaryReader(MS)) 16 | { 17 | var Name = BR.ReadIntString(); 18 | var ValueType = BR.ReadIntString(); 19 | //Discard unknown bytes for now. Example was 5420, probably byte count 20 | BR.ReadBytes(8); 21 | //"StructProperty" 22 | BR.ReadIntString(); 23 | //?? 24 | BR.ReadBytes(5); 25 | //"InventoryStacks" 26 | BR.ReadIntString(); 27 | //"StructProperty" 28 | BR.ReadIntString(); 29 | //?? 30 | BR.ReadBytes(8); 31 | //"InventoryStack" 32 | BR.ReadIntString(); 33 | //?? All are Zero 34 | BR.ReadBytes(17); 35 | 36 | //This likely loops 37 | 38 | //"Item" 39 | BR.ReadIntString(); 40 | //"StructProperty" 41 | BR.ReadIntString(); 42 | //?? 43 | BR.ReadBytes(8); 44 | 45 | //"InventoryItem" 46 | BR.ReadIntString(); 47 | //?? All are Zero 48 | BR.ReadBytes(21); 49 | 50 | var Item = new InventoryItem(BR); 51 | 52 | } 53 | } 54 | } 55 | 56 | public Inventory() 57 | { 58 | 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/PropertyValues/InventoryItem.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SatisfactorySaveEditor.PropertyValues 4 | { 5 | public class InventoryItem 6 | { 7 | public int Count { get; set; } 8 | public string Name { get; set; } 9 | public string Persistence1 { get; set; } 10 | public string Persistence2 { get; set; } 11 | 12 | public InventoryItem(BinaryReader BR) 13 | { 14 | //name of item 15 | Name = BR.ReadIntString(); 16 | //"PersistentLevel" type string 17 | Persistence1 = BR.ReadIntString(); 18 | //"PersistentLevel" type string (Again) 19 | Persistence2 = BR.ReadIntString(); 20 | 21 | //"NumItems" 22 | BR.ReadIntString(); 23 | //"IntProperty" 24 | BR.ReadIntString(); 25 | 26 | //Discard. 04 00 00 00 00 00 00 00 00 27 | BR.ReadBytes(BR.ReadInt32()); 28 | BR.ReadByte(); 29 | 30 | Count = BR.ReadInt32(); 31 | //"None" 32 | BR.ReadIntString(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/PropertyValues/NoneProperty.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace SatisfactorySaveEditor.PropertyValues 4 | { 5 | public class NoneProperty : IPropertyValue 6 | { 7 | public NoneProperty(BinaryReader BR) 8 | { 9 | //This property has no values 10 | } 11 | 12 | public void Export(BinaryWriter BW) 13 | { 14 | BW.WriteIntString("None"); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/PropertyValues/Property.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace SatisfactorySaveEditor.PropertyValues 5 | { 6 | public class Property : IPropertyValue 7 | { 8 | public string Name { get; set; } 9 | 10 | public IPropertyValue Value { get; set; } 11 | 12 | public Property(BinaryReader BR) 13 | { 14 | var ValueType = BR.ReadIntString(); 15 | switch (ValueType) 16 | { 17 | case "ArrayProperty": 18 | Value = new ArrayProperty(BR); 19 | break; 20 | case "StructProperty": 21 | Value = new StructProperty(BR); 22 | break; 23 | case "None": 24 | Value = new NoneProperty(BR); 25 | break; 26 | default: 27 | throw new NotImplementedException(); 28 | } 29 | } 30 | 31 | public void Export(BinaryWriter BW) 32 | { 33 | throw new NotImplementedException(); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/PropertyValues/StructProperty.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | 5 | namespace SatisfactorySaveEditor.PropertyValues 6 | { 7 | public class StructProperty : IPropertyValue 8 | { 9 | public Dictionary Properties { get; set; } 10 | 11 | public byte[] Sequence { get; set; } 12 | 13 | public StructProperty(BinaryReader BR) 14 | { 15 | Properties = new Dictionary(); 16 | //Sequence = BR.ReadBytes(5); //This number is dynamic. No Idea how to read 17 | while (true) 18 | { 19 | var Name = BR.ReadIntString(); 20 | //Empty property name means we have them all now. 21 | if (Name == string.Empty) 22 | { 23 | break; 24 | } 25 | Properties.Add(Name, new Property(BR)); 26 | } 27 | } 28 | 29 | public void Export(BinaryWriter BW) 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/QuickPlay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel; 3 | using System.IO; 4 | using System.Net; 5 | using System.Threading; 6 | 7 | namespace SatisfactorySaveEditor 8 | { 9 | /// 10 | /// Provides a QuickPlay obtain and update mechanism 11 | /// 12 | public static class QuickPlay 13 | { 14 | /// 15 | /// Expected QuickPlay hash 16 | /// 17 | private const string QP_HASH = "888178F6B888A295DB8269CF12B8803BAF2065B6"; 18 | /// 19 | /// QuickPlay download URL 20 | /// 21 | private const string QP_URL = @"https://cable.ayra.ch/satisfactory/QuickPlay.exe"; 22 | /// 23 | /// Default number of retries 24 | /// 25 | private const int DEFAULT_RETRIES = 5; 26 | 27 | /// 28 | /// Gets if QuickPlay is available 29 | /// 30 | public static bool HasQuickPlay 31 | { get; private set; } 32 | 33 | /// 34 | /// Number of Retries to download so far 35 | /// 36 | public static int QuickPlayTries 37 | { get; private set; } 38 | 39 | /// 40 | /// Gets or sets the maximum number of download retries 41 | /// 42 | public static int MaximumRetries 43 | { get; set; } 44 | 45 | /// 46 | /// Gets the Path+Name of QuickPlay 47 | /// 48 | public static string QuickPlayPath 49 | { 50 | get 51 | { 52 | return Path.Combine(UpdateHandler.ExecutablePath, "QuickPlay.exe"); 53 | } 54 | } 55 | 56 | static QuickPlay() 57 | { 58 | MaximumRetries = DEFAULT_RETRIES; 59 | } 60 | 61 | public static void ResetQuickPlay() 62 | { 63 | try 64 | { 65 | File.Delete(QuickPlayPath); 66 | } 67 | catch (Exception ex) 68 | { 69 | Log.Write(new Exception("Unable to reset QuickPlay because we can't delete the file.", ex)); 70 | } 71 | HasQuickPlay = File.Exists(QuickPlayPath); 72 | if (!HasQuickPlay) 73 | { 74 | QuickPlayTries = 0; 75 | } 76 | var F = Tools.GetForm(); 77 | if(F!=null) 78 | { 79 | F.QPComplete(new Exception("QuickPlay was reset")); 80 | } 81 | } 82 | 83 | /// 84 | /// Attempts to obtain QuickPlay 85 | /// 86 | public static void CheckQuickPlay() 87 | { 88 | if (!File.Exists(QuickPlayPath)) 89 | { 90 | ++QuickPlayTries; 91 | HasQuickPlay = false; 92 | var F = Tools.GetForm(); 93 | if(F!=null) 94 | { 95 | F.QPProgress(0); 96 | } 97 | WebClient WC = new WebClient(); 98 | WC.Headers.Add("User-Agent", UpdateHandler.UserAgent); 99 | WC.DownloadProgressChanged += WC_DownloadProgressChanged; 100 | WC.DownloadFileCompleted += WC_DownloadFileCompleted; 101 | try 102 | { 103 | WC.DownloadFileAsync(new Uri(QP_URL), QuickPlayPath); 104 | } 105 | catch (Exception ex) 106 | { 107 | Log.Write(new Exception("Unable to 'DownloadFileAsync' QuickPlay", ex)); 108 | HasQuickPlay = false; 109 | } 110 | } 111 | else 112 | { 113 | if (!IsQuickPlayValid()) 114 | { 115 | HasQuickPlay = false; 116 | //Hash invalid. Get new copy 117 | //But not immediately 118 | Thread.Sleep(1000); 119 | try 120 | { 121 | File.Delete(QuickPlayPath); 122 | CheckQuickPlay(); 123 | } 124 | catch (Exception ex) 125 | { 126 | Log.Write(new Exception("Unable to retry QuickPlay Download", ex)); 127 | } 128 | } 129 | else 130 | { 131 | HasQuickPlay = true; 132 | } 133 | } 134 | } 135 | 136 | private static bool IsQuickPlayValid() 137 | { 138 | if (File.Exists(QuickPlayPath)) 139 | { 140 | try 141 | { 142 | using (var FS = File.OpenRead(QuickPlayPath)) 143 | { 144 | return Tools.GetHash(FS) == QP_HASH; 145 | } 146 | } 147 | catch(Exception ex) 148 | { 149 | Log.Write(new Exception("Unable to test QuickPlay Hash", ex)); 150 | } 151 | } 152 | return false; 153 | } 154 | 155 | private static void WC_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e) 156 | { 157 | var WC = (WebClient)sender; 158 | var F = Tools.GetForm(); 159 | if (F != null && e.Cancelled) 160 | { 161 | F.QPComplete(new Exception("The user cancelled the operation")); 162 | } 163 | //Check QuickPlay after an update but not too often to avoid infinite loops 164 | if (QuickPlayTries < MaximumRetries) 165 | { 166 | if (F != null) 167 | { 168 | F.QPComplete(e.Error); 169 | } 170 | //On some network errors, this is fired immediately, so we wait for a second. 171 | Thread.Sleep(1000); 172 | CheckQuickPlay(); 173 | } 174 | else 175 | { 176 | if (F != null) 177 | { 178 | F.QPComplete(new Exception($"QuickPlay download failed after {MaximumRetries} attempts")); 179 | } 180 | } 181 | WC.Dispose(); 182 | } 183 | 184 | private static void WC_DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e) 185 | { 186 | if (e.TotalBytesToReceive > e.BytesReceived) 187 | { 188 | var F = Tools.GetForm(); 189 | if (F != null) 190 | { 191 | var Perc = (int)(e.BytesReceived * 100 / e.TotalBytesToReceive); 192 | F.QPProgress(Perc); 193 | } 194 | } 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/SMR/Tools.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | using System.Xml.Serialization; 4 | 5 | namespace SMRAPI 6 | { 7 | /// 8 | /// Tools to make our life easier 9 | /// 10 | internal static class Tools 11 | { 12 | /// 13 | /// Serialize an object as XML 14 | /// 15 | /// object 16 | /// XML string 17 | internal static string ToXml(this object o) 18 | { 19 | XmlSerializer S = new XmlSerializer(o.GetType()); 20 | using (var MS = new MemoryStream()) 21 | { 22 | S.Serialize(MS, o); 23 | return Encoding.UTF8.GetString(MS.ToArray()); 24 | } 25 | } 26 | 27 | /// 28 | /// Deserialize an XML string to an object 29 | /// 30 | /// Object type 31 | /// XML string 32 | /// Deserialized object 33 | internal static T FromXml(this string s) 34 | { 35 | XmlSerializer S = new XmlSerializer(typeof(T)); 36 | using (var SR = new StringReader(s)) 37 | { 38 | return (T)S.Deserialize(SR); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/SaveFileComponents/CompressedHeader.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.Text; 3 | 4 | namespace SatisfactorySaveEditor 5 | { 6 | public class CompressedHeader 7 | { 8 | /// 9 | /// The default chunk size utilized by the engine 10 | /// 11 | public const ulong DEFAULT_CHUNK_SIZE = 131072; 12 | /// 13 | /// Some kind of magic number 14 | /// 15 | public const ulong DEFAULT_FILE_TAG = 2653586369; 16 | 17 | /// 18 | /// File tag 19 | /// 20 | public ulong FileTag; 21 | /// 22 | /// Maximum supported chunk size 23 | /// 24 | public ulong MaxChunkSize; 25 | /// 26 | /// Length of compressed byte data 27 | /// 28 | public ulong CompressedLength; 29 | /// 30 | /// Length of decompressed byte data 31 | /// 32 | public ulong DecompressedLength; 33 | 34 | public CompressedHeader() 35 | { 36 | FileTag = DEFAULT_FILE_TAG; 37 | MaxChunkSize = DEFAULT_CHUNK_SIZE; 38 | CompressedLength = 0; 39 | DecompressedLength = 0; 40 | } 41 | 42 | public CompressedHeader(Stream Input) 43 | { 44 | using (var BR = new BinaryReader(Input, Encoding.Default, true)) 45 | { 46 | FileTag = BR.ReadUInt64(); 47 | MaxChunkSize = BR.ReadUInt64(); 48 | CompressedLength = BR.ReadUInt64(); 49 | DecompressedLength = BR.ReadUInt64(); 50 | //Discard duplicate values 51 | CompressedLength = BR.ReadUInt64(); 52 | DecompressedLength = BR.ReadUInt64(); 53 | } 54 | } 55 | 56 | public void WriteHeader(BinaryWriter BW) 57 | { 58 | BW.Write(FileTag); 59 | BW.Write(MaxChunkSize); 60 | BW.Write(CompressedLength); 61 | BW.Write(DecompressedLength); 62 | BW.Write(CompressedLength); 63 | BW.Write(DecompressedLength); 64 | } 65 | 66 | public void WriteHeader(Stream S) 67 | { 68 | using (var BW = new BinaryWriter(S, Encoding.Default, true)) 69 | { 70 | WriteHeader(BW); 71 | } 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/SaveFileComponents/ObjectTypes/GameBaseObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace SatisfactorySaveEditor.ObjectTypes 5 | { 6 | /// 7 | /// Serves as the base for object entries 8 | /// 9 | [Serializable] 10 | public class GameBaseObject : ICloneable 11 | { 12 | /// 13 | /// Reads the base values present in every object 14 | /// 15 | /// Open Reader 16 | internal void Fill(BinaryReader BR) 17 | { 18 | Name = BR.ReadIntString(); 19 | LevelType = BR.ReadIntString(); 20 | InternalName = BR.ReadIntString(); 21 | } 22 | 23 | /// 24 | /// Type of object. See 25 | /// 26 | /// Maybe an enum would be better 27 | public int ObjectType { get; set; } 28 | /// 29 | /// Some Path-like internal name, probably path inside the game data file 30 | /// 31 | public string InternalName { get; set; } 32 | /// 33 | /// Level type entry. Almost always "Persistent_Level" 34 | /// 35 | public string LevelType { get; set; } 36 | /// 37 | /// Property Name 38 | /// 39 | public string Name { get; set; } 40 | 41 | /// 42 | /// Exports the base data to a stream 43 | /// 44 | /// Open Writer 45 | /// Do not call directly. Call on the child class 46 | public virtual void Export(BinaryWriter BW) 47 | { 48 | BW.WriteIntString(Name); 49 | BW.WriteIntString(LevelType); 50 | BW.WriteIntString(InternalName); 51 | } 52 | 53 | /// 54 | /// Clones the object 55 | /// 56 | /// The base object can't be cloned. Children of this type have to implement it 57 | /// - 58 | public virtual object Clone() 59 | { 60 | throw new NotImplementedException("BaseObject can't be cloned"); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/SaveFileComponents/ObjectTypes/GameObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace SatisfactorySaveEditor.ObjectTypes 5 | { 6 | /// 7 | /// "Constructable" Game object 8 | /// 9 | [Serializable] 10 | public class GameObject : GameBaseObject 11 | { 12 | /// 13 | /// Object rotation 14 | /// 15 | public Vector4 ObjectRotation { get; set; } 16 | /// 17 | /// Object position 18 | /// 19 | public Vector3 ObjectPosition { get; set; } 20 | /// 21 | /// Object scale 22 | /// 23 | public Vector3 ObjectScale { get; set; } 24 | /// 25 | /// Unknown integer at header end, always 1 26 | /// 27 | public int UnknownHeaderEnd { get; set; } 28 | /// 29 | /// Unknown integer at header start, always 1 30 | /// 31 | public int UnknownInt { get; set; } 32 | 33 | /// 34 | /// Creates new game object 35 | /// 36 | /// Open Reader 37 | public GameObject(BinaryReader BR) 38 | { 39 | Fill(BR); 40 | UnknownInt = BR.ReadInt32(); //Discard? always 1, maybe object type again 41 | 42 | ObjectRotation = new Vector4(BR); 43 | ObjectPosition = new Vector3(BR); 44 | ObjectScale = new Vector3(BR); 45 | UnknownHeaderEnd = BR.ReadInt32(); 46 | 47 | ObjectType = OBJECT_TYPE.OBJECT; 48 | } 49 | 50 | /// 51 | /// Creates an empty game object 52 | /// 53 | public GameObject() 54 | { 55 | 56 | } 57 | 58 | /// 59 | /// Exports this game object to file 60 | /// 61 | /// Open Writer 62 | public override void Export(BinaryWriter BW) 63 | { 64 | base.Export(BW); 65 | BW.Write(UnknownInt); 66 | ObjectRotation.Export(BW); 67 | ObjectPosition.Export(BW); 68 | ObjectScale.Export(BW); 69 | BW.Write(UnknownHeaderEnd); 70 | } 71 | 72 | /// 73 | /// Clones this instance 74 | /// 75 | /// Copy 76 | public override object Clone() 77 | { 78 | var Copy = (GameObject)MemberwiseClone(); 79 | Copy.ObjectPosition = (Vector3)Copy.ObjectPosition.Clone(); 80 | Copy.ObjectRotation = (Vector4)Copy.ObjectRotation.Clone(); 81 | Copy.ObjectScale = (Vector3)Copy.ObjectScale.Clone(); 82 | return Copy; 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /SatisfactorySaveEditor/SaveFileComponents/ObjectTypes/GameScript.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace SatisfactorySaveEditor.ObjectTypes 5 | { 6 | /// 7 | /// Script object entry 8 | /// 9 | [Serializable] 10 | public class GameScript : GameBaseObject 11 | { 12 | /// 13 | /// Name of script 14 | /// 15 | public string ScriptName { get; set; } 16 | 17 | /// 18 | /// Initializes agame script 19 | /// 20 | /// Open Reader 21 | public GameScript(BinaryReader BR) 22 | { 23 | Fill(BR); 24 | ScriptName = BR.ReadIntString(); 25 | 26 | ObjectType = OBJECT_TYPE.SCRIPT; 27 | } 28 | 29 | /// 30 | /// Creates an empty script entry 31 | /// 32 | public GameScript() 33 | { 34 | 35 | } 36 | 37 | /// 38 | /// Exports script to stream 39 | /// 40 | /// Open Writer 41 | public override void Export(BinaryWriter BW) 42 | { 43 | base.Export(BW); 44 | BW.WriteIntString(ScriptName); 45 | } 46 | 47 | /// 48 | /// Clones this instance 49 | /// 50 | /// Copy 51 | public override object Clone() 52 | { 53 | return (GameScript)MemberwiseClone(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/SaveFileComponents/ObjectTypes/Types.cs: -------------------------------------------------------------------------------- 1 | namespace SatisfactorySaveEditor.ObjectTypes 2 | { 3 | /// 4 | /// Contains known object types 5 | /// 6 | public struct OBJECT_TYPE 7 | { 8 | /// 9 | /// Object is a script 10 | /// 11 | public const int SCRIPT = 0; 12 | /// 13 | /// Object is a "constructable" entity 14 | /// 15 | public const int OBJECT = 1; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/SaveFileComponents/PropertyString.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace SatisfactorySaveEditor 5 | { 6 | /// 7 | /// String from the property list. 8 | /// 9 | public class PropertyString : ICloneable 10 | { 11 | /// 12 | /// String Name 13 | /// 14 | /// Names can be duplicate, so don't use them as key 15 | public string Name { get; private set; } 16 | /// 17 | /// String Value 18 | /// 19 | /// Values seem to be unique as they seem to reference objects on the map 20 | public string Value { get; private set; } 21 | 22 | /// 23 | /// Reads a string from the property list 24 | /// 25 | /// Open Reader 26 | public PropertyString(BinaryReader BR) 27 | { 28 | Name = BR.ReadIntString(); 29 | Value = BR.ReadIntString(); 30 | } 31 | 32 | /// 33 | /// Initializes a new property string instance 34 | /// 35 | /// String name 36 | /// String value 37 | public PropertyString(string Name,string Value) 38 | { 39 | this.Name = Name; 40 | this.Value = Value; 41 | } 42 | 43 | /// 44 | /// Writes this string to a stream 45 | /// 46 | /// Open Writer 47 | public void Export(BinaryWriter BW) 48 | { 49 | BW.WriteIntString(Name); 50 | BW.WriteIntString(Value); 51 | } 52 | 53 | /// 54 | /// Creates an independent copy of this instance 55 | /// 56 | /// new 57 | public object Clone() 58 | { 59 | return new PropertyString(string.Copy(Name), string.Copy(Value)); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/SaveFileComponents/SaveFileEntry.cs: -------------------------------------------------------------------------------- 1 | using SatisfactorySaveEditor.ObjectTypes; 2 | using System; 3 | using System.IO; 4 | using System.Xml.Serialization; 5 | 6 | namespace SatisfactorySaveEditor 7 | { 8 | /// 9 | /// Generic entry in the save file 10 | /// 11 | [XmlInclude(typeof(GameObject)),XmlInclude(typeof(GameScript))] 12 | [Serializable] 13 | public class SaveFileEntry : ICloneable 14 | { 15 | /// 16 | /// Entry specific data 17 | /// 18 | public GameBaseObject ObjectData 19 | { get; set; } 20 | 21 | /// 22 | /// Type of entry 23 | /// 24 | /// See 25 | public int EntryType 26 | { get; set; } 27 | 28 | /// 29 | /// Entry specific properties 30 | /// 31 | public byte[] Properties 32 | { get; set; } 33 | 34 | /// 35 | /// Creates an empty save file entry 36 | /// 37 | public SaveFileEntry() 38 | { 39 | Properties = new byte[0]; 40 | } 41 | 42 | /// 43 | /// Reads a new entry from the stream 44 | /// 45 | /// Open Reader 46 | public SaveFileEntry(BinaryReader BR) 47 | { 48 | EntryType = BR.ReadInt32(); 49 | switch (EntryType) 50 | { 51 | case OBJECT_TYPE.SCRIPT: 52 | ObjectData = new GameScript(BR); 53 | break; 54 | case OBJECT_TYPE.OBJECT: 55 | ObjectData = new GameObject(BR); 56 | break; 57 | default: 58 | throw new NotImplementedException($"Unknown Entry TYPE={EntryType} at POS={BR.BaseStream.Position - 4}"); 59 | } 60 | 61 | } 62 | 63 | /// 64 | /// Export Entry to stream 65 | /// 66 | /// 67 | /// Not the as this is done later 68 | public void Export(BinaryWriter BW) 69 | { 70 | BW.Write(EntryType); 71 | ObjectData.Export(BW); 72 | } 73 | 74 | /// 75 | /// Copies this instance 76 | /// 77 | /// Fully independent clone of this instance 78 | /// 79 | /// This will not in any way change any properties, 80 | /// including those supposed to be unique 81 | /// 82 | public object Clone() 83 | { 84 | var Copy = (SaveFileEntry)MemberwiseClone(); 85 | //Make sure the properties are only a copy and not a reference 86 | Copy.Properties = (byte[])Copy.Properties.Clone(); 87 | Copy.ObjectData = (GameBaseObject)Copy.ObjectData.Clone(); 88 | return Copy; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/SaveFileComponents/Vector3.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace SatisfactorySaveEditor 5 | { 6 | /// 7 | /// Map coordinates for 3 dimensions 8 | /// 9 | [Serializable] 10 | public class Vector3 : ICloneable 11 | { 12 | /// 13 | /// X position 14 | /// 15 | public float X 16 | { get; set; } 17 | /// 18 | /// Y position 19 | /// 20 | public float Y 21 | { get; set; } 22 | /// 23 | /// Z position 24 | /// 25 | /// This will be the height if this instance represents map coordinates 26 | public float Z 27 | { get; set; } 28 | 29 | /// 30 | /// Reads positons 31 | /// 32 | /// Open Reader 33 | public Vector3(BinaryReader BR) 34 | { 35 | X = BR.ReadSingle(); 36 | Y = BR.ReadSingle(); 37 | Z = BR.ReadSingle(); 38 | } 39 | 40 | /// 41 | /// Creates an empty instance for deserialization 42 | /// 43 | public Vector3() : this(0, 0, 0) 44 | { 45 | } 46 | 47 | /// 48 | /// Creates an instance with the given points 49 | /// 50 | /// X 51 | /// Y 52 | /// Z 53 | public Vector3(float x = 0f, float y = 0f, float z = 0f) 54 | { 55 | X = x; 56 | Y = y; 57 | Z = z; 58 | } 59 | 60 | /// 61 | /// Show nice coordinates 62 | /// 63 | /// Coordinates 64 | public override string ToString() 65 | { 66 | return string.Format("X={0} Y={1} Z={2}", Math.Round(X, 3), Math.Round(Y, 3), Math.Round(Z, 3)); 67 | } 68 | 69 | /// 70 | /// Writes position to a stream 71 | /// 72 | /// Open Writer 73 | public void Export(BinaryWriter BW) 74 | { 75 | BW.Write(X); 76 | BW.Write(Y); 77 | BW.Write(Z); 78 | } 79 | 80 | /// 81 | /// Creates a copy of this instance 82 | /// 83 | /// Copy 84 | public object Clone() 85 | { 86 | return new Vector3(X, Y, Z); 87 | } 88 | 89 | /// 90 | /// Checks if the given Vector has the same coordinates 91 | /// 92 | /// Vector 93 | /// true if same coordinates, false if other coordinates or not same vector type 94 | public override bool Equals(object obj) 95 | { 96 | if (obj == null) 97 | { 98 | return false; 99 | } 100 | if (obj is Vector3) 101 | { 102 | var o = (Vector3)obj; 103 | return o.X == X && o.Y == Y && o.Z == Z; 104 | } 105 | return false; 106 | } 107 | 108 | public override int GetHashCode() 109 | { 110 | return X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/SaveFileComponents/Vector4.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace SatisfactorySaveEditor 5 | { 6 | /// 7 | /// Coordinates for 4 dimensions 8 | /// 9 | [Serializable] 10 | public class Vector4 : ICloneable 11 | { 12 | /// 13 | /// X position 14 | /// 15 | public float X 16 | { get; set; } 17 | /// 18 | /// Y position 19 | /// 20 | public float Y 21 | { get; set; } 22 | /// 23 | /// Z position 24 | /// 25 | public float Z 26 | { get; set; } 27 | /// 28 | /// W position 29 | /// 30 | public float W 31 | { get; set; } 32 | 33 | /// 34 | /// Reads positons 35 | /// 36 | /// Open Reader 37 | public Vector4(BinaryReader BR) 38 | { 39 | X = BR.ReadSingle(); 40 | Y = BR.ReadSingle(); 41 | Z = BR.ReadSingle(); 42 | W = BR.ReadSingle(); 43 | } 44 | 45 | /// 46 | /// Creates an empty instance for deserialization 47 | /// 48 | public Vector4() : this(0, 0, 0, 0) 49 | { 50 | } 51 | 52 | /// 53 | /// Creates an instance with the given points 54 | /// 55 | /// X 56 | /// Y 57 | /// Z 58 | /// W 59 | public Vector4(float x = 0f, float y = 0f, float z = 0f, float w = 0f) 60 | { 61 | X = x; 62 | Y = y; 63 | Z = z; 64 | W = w; 65 | } 66 | 67 | 68 | /// 69 | /// Show nice coordinates 70 | /// 71 | /// Coordinates 72 | public override string ToString() 73 | { 74 | return string.Format("X={0} Y={1} Z={2} W={2}", Math.Round(X, 3), Math.Round(Y, 3), Math.Round(Z, 3), Math.Round(W, 3)); 75 | } 76 | 77 | /// 78 | /// Writes position to a stream 79 | /// 80 | /// Open Writer 81 | public void Export(BinaryWriter BW) 82 | { 83 | BW.Write(X); 84 | BW.Write(Y); 85 | BW.Write(Z); 86 | BW.Write(W); 87 | } 88 | 89 | /// 90 | /// Creates a copy of this instance 91 | /// 92 | /// Copy 93 | public object Clone() 94 | { 95 | return new Vector4(X, Y, Z, W); 96 | } 97 | 98 | /// 99 | /// Checks if the given Vector has the same coordinates 100 | /// 101 | /// Vector 102 | /// true if same coordinates, false if other coordinates or not same vector type 103 | public override bool Equals(object obj) 104 | { 105 | if (obj == null) 106 | { 107 | return false; 108 | } 109 | if (obj is Vector4) 110 | { 111 | var o = (Vector4)obj; 112 | return o.W == W && o.X == X && o.Y == Y && o.Z == Z; 113 | } 114 | return false; 115 | } 116 | 117 | public override int GetHashCode() 118 | { 119 | return W.GetHashCode() ^ X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/Settings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Xml.Serialization; 4 | 5 | namespace SatisfactorySaveEditor 6 | { 7 | /// 8 | /// Provides application settings 9 | /// 10 | [Serializable] 11 | public class Settings 12 | { 13 | /// 14 | /// Automatically starts the manager on startup 15 | /// 16 | public bool AutostartManager 17 | { get; set; } 18 | /// 19 | /// Show the resize hint 20 | /// 21 | public bool ShowResizeHint 22 | { get; set; } 23 | /// 24 | /// Show the general editor limitations 25 | /// 26 | public bool ShowLimited 27 | { get; set; } 28 | /// 29 | /// Show the duplication problems hint 30 | /// 31 | public bool ShowDuplicationHint 32 | { get; set; } 33 | /// 34 | /// Show the item removal hint 35 | /// 36 | public bool ShowDeletionHint 37 | { get; set; } 38 | /// 39 | /// Shows a one time welcome message 40 | /// 41 | public bool ShowWelcomeMessage 42 | { get; set; } 43 | /// 44 | /// Shows a hint about the range deleter 45 | /// 46 | public bool ShowRangeDeleterHint 47 | { get; set; } 48 | 49 | /// 50 | /// Performs automatic update checks 51 | /// 52 | public bool AutoUpdate 53 | { get; set; } 54 | 55 | /// 56 | /// Show the changelog automatically after an update 57 | /// 58 | public bool ShowChangelog 59 | { get; set; } 60 | 61 | /// 62 | /// Uses a random Id for each time the application is launched 63 | /// 64 | public bool UseRandomId 65 | { get; set; } 66 | 67 | /// 68 | /// Disables usage reporting completely 69 | /// 70 | public bool DisableUsageReport 71 | { get; set; } 72 | 73 | /// 74 | /// Id used for usage report 75 | /// 76 | public Guid ReportId 77 | { get; set; } 78 | 79 | /// 80 | /// Key for the SMR API 81 | /// 82 | /// See https://cable.ayra.ch/satiafactory/maps/help 83 | public Guid ApiKey 84 | { get; set; } 85 | 86 | /// 87 | /// Last time an update check was performed 88 | /// 89 | public DateTime LastUpdateCheck 90 | { get; set; } 91 | 92 | /// 93 | /// The last version where the changelog was shown 94 | /// 95 | public string LastVersionLogShown 96 | { get; set; } 97 | 98 | /// 99 | /// Initializes default settings 100 | /// 101 | public Settings() 102 | { 103 | LastVersionLogShown = "0.0.0.0"; 104 | LastUpdateCheck = DateTime.MinValue; 105 | //Default message status 106 | MarkDialogsRead(false); 107 | //Update settings 108 | AutoUpdate = true; 109 | //Changelog settings 110 | ShowChangelog = true; 111 | //Report settings 112 | ReportId = Guid.NewGuid(); 113 | UseRandomId = false; 114 | DisableUsageReport = false; 115 | //UI Settings 116 | AutostartManager = true; 117 | } 118 | 119 | /// 120 | /// Marks all dialogs as read or unread 121 | /// 122 | /// Mark dialogs as read 123 | public void MarkDialogsRead(bool MarkAsRead) 124 | { 125 | ShowResizeHint = 126 | ShowLimited = 127 | ShowDuplicationHint = 128 | ShowDeletionHint = 129 | ShowWelcomeMessage = 130 | ShowRangeDeleterHint = 131 | !MarkAsRead; 132 | 133 | } 134 | 135 | /// 136 | /// Loads settings from a string 137 | /// 138 | /// Swettings string 139 | /// Settings instance 140 | public static Settings Load(string Content) 141 | { 142 | Log.Write("Settings: Loading settings"); 143 | var S = new XmlSerializer(typeof(Settings)); 144 | using (var SR = new StringReader(Content)) 145 | { 146 | return (Settings)S.Deserialize(SR); 147 | } 148 | } 149 | 150 | /// 151 | /// Saves settings to a string 152 | /// 153 | /// Settings string 154 | public string Save() 155 | { 156 | Log.Write("Settings: Saving settings"); 157 | var S = new XmlSerializer(typeof(Settings)); 158 | using (var SW = new StringWriter()) 159 | { 160 | S.Serialize(SW, this); 161 | return SW.ToString(); 162 | } 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/ShortName.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace SatisfactorySaveEditor 5 | { 6 | /// 7 | /// Provides a short name for save file entries to make the UI look nicer 8 | /// 9 | public class ShortName : IComparable 10 | { 11 | /// 12 | /// Full entry name 13 | /// 14 | public string Long 15 | { get; private set; } 16 | 17 | /// 18 | /// Short entry name 19 | /// 20 | public string Short 21 | { get; private set; } 22 | 23 | /// 24 | /// Use 25 | /// in 26 | /// instead of 27 | /// 28 | public bool UseLong 29 | { get; set; } 30 | 31 | /// 32 | /// Use 33 | /// in 34 | /// instead of 35 | /// 36 | public bool SortLong 37 | { get; set; } 38 | 39 | /// 40 | /// Initializes a short name instance from a long name instance 41 | /// 42 | /// Long name 43 | public ShortName(string LongName) 44 | { 45 | Long = LongName; 46 | //Default short name is last segment of long name without very short parts 47 | Short = string.Join(" ", LongName.Split('/', '.').Last().Split('_').Where(m => m.Length > 2)); 48 | 49 | //TODO: Provide better short names 50 | SortLong = UseLong = false; 51 | } 52 | 53 | /// 54 | /// Gets the string of this instance 55 | /// 56 | /// string 57 | /// Use for configuration of this value 58 | public override string ToString() 59 | { 60 | return UseLong ? Long : Short; 61 | } 62 | 63 | /// 64 | /// Compares this object to another for sorting purposes 65 | /// 66 | /// Other object 67 | /// Sort value 68 | /// See for configuration of this value 69 | public int CompareTo(object obj) 70 | { 71 | if (obj == null) 72 | { 73 | throw new ArgumentNullException(); 74 | } 75 | if (obj.GetType() != typeof(ShortName)) 76 | { 77 | throw new ArgumentException("Expecting argument of type ShortName"); 78 | } 79 | var o = (ShortName)obj; 80 | 81 | return SortLong ? Long.CompareTo(o.Long) : Short.CompareTo(o.Short); 82 | } 83 | 84 | public override bool Equals(object obj) 85 | { 86 | if (obj == null) 87 | { 88 | return false; 89 | } 90 | if (obj is ShortName) 91 | { 92 | return Short == ((ShortName)obj).Short; 93 | } 94 | return false; 95 | } 96 | 97 | public override int GetHashCode() 98 | { 99 | return Long.GetHashCode(); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmApiRegister.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace SatisfactorySaveEditor 2 | { 3 | partial class frmApiRegister 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 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmApiRegister)); 32 | this.btnRegister = new System.Windows.Forms.Button(); 33 | this.btnOK = new System.Windows.Forms.Button(); 34 | this.btnCancel = new System.Windows.Forms.Button(); 35 | this.tbKey = new System.Windows.Forms.TextBox(); 36 | this.label1 = new System.Windows.Forms.Label(); 37 | this.SuspendLayout(); 38 | // 39 | // btnRegister 40 | // 41 | this.btnRegister.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 42 | | System.Windows.Forms.AnchorStyles.Right))); 43 | this.btnRegister.Location = new System.Drawing.Point(12, 57); 44 | this.btnRegister.Name = "btnRegister"; 45 | this.btnRegister.Size = new System.Drawing.Size(298, 23); 46 | this.btnRegister.TabIndex = 2; 47 | this.btnRegister.Text = "&Automatically Register"; 48 | this.btnRegister.UseVisualStyleBackColor = true; 49 | this.btnRegister.Click += new System.EventHandler(this.btnRegister_Click); 50 | // 51 | // btnOK 52 | // 53 | this.btnOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 54 | this.btnOK.Location = new System.Drawing.Point(154, 88); 55 | this.btnOK.Name = "btnOK"; 56 | this.btnOK.Size = new System.Drawing.Size(75, 23); 57 | this.btnOK.TabIndex = 3; 58 | this.btnOK.Text = "&OK"; 59 | this.btnOK.UseVisualStyleBackColor = true; 60 | this.btnOK.Click += new System.EventHandler(this.btnOK_Click); 61 | // 62 | // btnCancel 63 | // 64 | this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 65 | this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; 66 | this.btnCancel.Location = new System.Drawing.Point(235, 88); 67 | this.btnCancel.Name = "btnCancel"; 68 | this.btnCancel.Size = new System.Drawing.Size(75, 23); 69 | this.btnCancel.TabIndex = 4; 70 | this.btnCancel.Text = "&Cancel"; 71 | this.btnCancel.UseVisualStyleBackColor = true; 72 | // 73 | // tbKey 74 | // 75 | this.tbKey.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 76 | | System.Windows.Forms.AnchorStyles.Right))); 77 | this.tbKey.Location = new System.Drawing.Point(12, 28); 78 | this.tbKey.Name = "tbKey"; 79 | this.tbKey.Size = new System.Drawing.Size(298, 20); 80 | this.tbKey.TabIndex = 1; 81 | // 82 | // label1 83 | // 84 | this.label1.AutoSize = true; 85 | this.label1.Location = new System.Drawing.Point(9, 9); 86 | this.label1.Name = "label1"; 87 | this.label1.Size = new System.Drawing.Size(299, 13); 88 | this.label1.TabIndex = 0; 89 | this.label1.Text = "Enter your API key or click the button to obtain it automatically"; 90 | // 91 | // frmApiRegister 92 | // 93 | this.AcceptButton = this.btnOK; 94 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 95 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 96 | this.CancelButton = this.btnCancel; 97 | this.ClientSize = new System.Drawing.Size(322, 123); 98 | this.Controls.Add(this.label1); 99 | this.Controls.Add(this.tbKey); 100 | this.Controls.Add(this.btnCancel); 101 | this.Controls.Add(this.btnOK); 102 | this.Controls.Add(this.btnRegister); 103 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 104 | this.MaximizeBox = false; 105 | this.MinimizeBox = false; 106 | this.MinimumSize = new System.Drawing.Size(330, 150); 107 | this.Name = "frmApiRegister"; 108 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 109 | this.Text = "Register API"; 110 | this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.frmApiRegister_FormClosed); 111 | this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.frmApiRegister_HelpRequested); 112 | this.ResumeLayout(false); 113 | this.PerformLayout(); 114 | 115 | } 116 | 117 | #endregion 118 | 119 | private System.Windows.Forms.Button btnRegister; 120 | private System.Windows.Forms.Button btnOK; 121 | private System.Windows.Forms.Button btnCancel; 122 | private System.Windows.Forms.TextBox tbKey; 123 | private System.Windows.Forms.Label label1; 124 | } 125 | } -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmApiRegister.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.Windows.Forms; 4 | 5 | namespace SatisfactorySaveEditor 6 | { 7 | public partial class frmApiRegister : Form 8 | { 9 | private Settings S; 10 | private SMRAPI.HTTP Server = null; 11 | 12 | public frmApiRegister(Settings CurrentSettings) 13 | { 14 | S = CurrentSettings; 15 | InitializeComponent(); 16 | MaximumSize = new Size(int.MaxValue, MinimumSize.Height); 17 | if (S.ApiKey != Guid.Empty) 18 | { 19 | tbKey.Text = S.ApiKey.ToString(); 20 | } 21 | Tools.SetupKeyHandlers(this); 22 | } 23 | 24 | private void btnRegister_Click(object sender, EventArgs e) 25 | { 26 | if (Server == null) 27 | { 28 | int Port = Tools.GetRandom(5000, 50000); 29 | Server = new SMRAPI.HTTP(Port); 30 | Log.Write("{0}: Starting HTTP server on port {1}", GetType().Name, Port); 31 | Server.Start(); 32 | Server.ApiKeyEvent += delegate (object source, Guid Key) 33 | { 34 | Invoke((MethodInvoker)delegate 35 | { 36 | if (Key == Guid.Empty) 37 | { 38 | tbKey.Text = string.Empty; 39 | } 40 | else 41 | { 42 | tbKey.Text = Key.ToString(); 43 | } 44 | }); 45 | }; 46 | } 47 | Server.OpenBrowser(); 48 | } 49 | 50 | private void btnOK_Click(object sender, EventArgs e) 51 | { 52 | Guid TempKey; 53 | DialogResult = DialogResult.OK; 54 | if (string.IsNullOrEmpty(tbKey.Text)) 55 | { 56 | S.ApiKey = Guid.Empty; 57 | } 58 | else if (!Guid.TryParse(tbKey.Text, out TempKey)) 59 | { 60 | Log.Write($"Invalid Api Key in Text box: {tbKey.Text}"); 61 | Tools.E("The entered API key is invalid. Please double check or use the button to obtain it automatically", Text); 62 | DialogResult = DialogResult.None; 63 | } 64 | else 65 | { 66 | var OldApiKey = SMRAPI.API.ApiKey; 67 | try 68 | { 69 | SMRAPI.API.ApiKey = TempKey; 70 | var I = SMRAPI.API.Info(); 71 | if (I.success) 72 | { 73 | S.ApiKey = TempKey; 74 | } 75 | else 76 | { 77 | throw new Exception(I.msg); 78 | } 79 | } 80 | catch (Exception ex) 81 | { 82 | SMRAPI.API.ApiKey = OldApiKey; 83 | Tools.E("Error checking key:\r\n" + ex.Message, ex.GetType().Name); 84 | DialogResult = DialogResult.None; 85 | } 86 | } 87 | } 88 | 89 | private void frmApiRegister_FormClosed(object sender, FormClosedEventArgs e) 90 | { 91 | if (Server != null) 92 | { 93 | Log.Write("{0}: Stopping HTTP server", GetType().Name); 94 | Server.Dispose(); 95 | Server = null; 96 | } 97 | } 98 | 99 | private void frmApiRegister_HelpRequested(object sender, HelpEventArgs hlpevent) 100 | { 101 | Tools.ShowHelp(GetType().Name); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmChangeLog.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace SatisfactorySaveEditor 2 | { 3 | partial class frmChangeLog 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 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmChangeLog)); 32 | this.cbVersion = new System.Windows.Forms.ComboBox(); 33 | this.tbVersion = new System.Windows.Forms.TextBox(); 34 | this.SuspendLayout(); 35 | // 36 | // cbVersion 37 | // 38 | this.cbVersion.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 39 | | System.Windows.Forms.AnchorStyles.Right))); 40 | this.cbVersion.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; 41 | this.cbVersion.FormattingEnabled = true; 42 | this.cbVersion.Location = new System.Drawing.Point(12, 12); 43 | this.cbVersion.Name = "cbVersion"; 44 | this.cbVersion.Size = new System.Drawing.Size(568, 21); 45 | this.cbVersion.TabIndex = 0; 46 | this.cbVersion.SelectedIndexChanged += new System.EventHandler(this.cbVersion_SelectedIndexChanged); 47 | // 48 | // tbVersion 49 | // 50 | this.tbVersion.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) 51 | | System.Windows.Forms.AnchorStyles.Left) 52 | | System.Windows.Forms.AnchorStyles.Right))); 53 | this.tbVersion.BackColor = System.Drawing.SystemColors.Window; 54 | this.tbVersion.Location = new System.Drawing.Point(12, 39); 55 | this.tbVersion.Multiline = true; 56 | this.tbVersion.Name = "tbVersion"; 57 | this.tbVersion.ReadOnly = true; 58 | this.tbVersion.ScrollBars = System.Windows.Forms.ScrollBars.Both; 59 | this.tbVersion.Size = new System.Drawing.Size(568, 522); 60 | this.tbVersion.TabIndex = 1; 61 | // 62 | // frmChangeLog 63 | // 64 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 65 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 66 | this.ClientSize = new System.Drawing.Size(592, 573); 67 | this.Controls.Add(this.tbVersion); 68 | this.Controls.Add(this.cbVersion); 69 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 70 | this.Name = "frmChangeLog"; 71 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 72 | this.Text = "Change Log"; 73 | this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.frmChangeLog_HelpRequested); 74 | this.ResumeLayout(false); 75 | this.PerformLayout(); 76 | 77 | } 78 | 79 | #endregion 80 | 81 | private System.Windows.Forms.ComboBox cbVersion; 82 | private System.Windows.Forms.TextBox tbVersion; 83 | } 84 | } -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmChangeLog.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Linq; 4 | using System.Windows.Forms; 5 | 6 | namespace SatisfactorySaveEditor 7 | { 8 | public partial class frmChangeLog : Form 9 | { 10 | public frmChangeLog() 11 | { 12 | InitializeComponent(); 13 | cbVersion.Items.AddRange(Tools.GetVersionFiles().OrderByDescending(m => m).Cast().ToArray()); 14 | cbVersion.SelectedIndex = 0; 15 | Tools.SetupKeyHandlers(this); 16 | } 17 | 18 | private void cbVersion_SelectedIndexChanged(object sender, EventArgs e) 19 | { 20 | if (cbVersion.SelectedIndex >= 0) 21 | { 22 | tbVersion.Text = Tools.GetVersionFile((string)cbVersion.SelectedItem); 23 | } 24 | } 25 | 26 | private void frmChangeLog_HelpRequested(object sender, HelpEventArgs hlpevent) 27 | { 28 | Tools.ShowHelp(nameof(frmChangeLog)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmCloudEdit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | using SMRAPI; 4 | using SMRAPI.Responses; 5 | using System.Threading; 6 | using System.Linq; 7 | using System.Collections.Generic; 8 | 9 | namespace SatisfactorySaveEditor 10 | { 11 | public partial class frmCloudEdit : Form 12 | { 13 | InfoResponse.map CurrentMap = null; 14 | InfoResponse.DataValue.CategoriesResult Categories = null; 15 | 16 | public frmCloudEdit(InfoResponse.map Map) 17 | { 18 | CurrentMap = Map; 19 | InitializeComponent(); 20 | Tools.SetupKeyHandlers(this); 21 | tbFileName.Text = Map.name; 22 | tbDescription.Text = Map.description; 23 | tbHiddenId.Text = Map.hidden_id.ToString(); 24 | cbPublic.Checked = Map.@public != 0; 25 | LoadCategories(); 26 | } 27 | 28 | private void LoadCategories() 29 | { 30 | Thread T = new Thread(delegate () 31 | { 32 | try 33 | { 34 | Categories = API.Info().data.categories; 35 | } 36 | catch (Exception ex) 37 | { 38 | Tools.E("Unable to get the categories from the API.\r\n" + ex.Message, "Cloud File Edit", this); 39 | } 40 | if (Categories != null) 41 | { 42 | Invoke((MethodInvoker)delegate 43 | { 44 | var Boxes = new List(); 45 | foreach (var Cat in Categories.list) 46 | { 47 | var Conflict = Categories.conflicts.FirstOrDefault(m => m.Category == Cat.id); 48 | if (Conflict != null && !Conflict.IsDisabled) 49 | { 50 | var Active = CurrentMap.categories != null && 51 | CurrentMap.categories.category != null && 52 | CurrentMap.categories.category.Any(m => m == Cat.id); 53 | var box = new CheckBox(); 54 | box.Size = new System.Drawing.Size(150, 25); 55 | box.TextAlign = System.Drawing.ContentAlignment.MiddleLeft; 56 | box.Text = Cat.name; 57 | box.Tag = Cat.id; 58 | box.Checked = Active; 59 | Boxes.Add(box); 60 | ttMain.SetToolTip(box, Cat.description); 61 | } 62 | } 63 | panelCategories.Controls.Clear(); 64 | panelCategories.Controls.AddRange(Boxes.ToArray()); 65 | ApplyCategoryMap(); 66 | //Attach events and render boxes. 67 | foreach (var Box in Boxes) 68 | { 69 | Box.CheckedChanged += CategoryChangeHandler; 70 | } 71 | }); 72 | } 73 | }); 74 | T.IsBackground = true; 75 | T.Start(); 76 | } 77 | 78 | private void ApplyCategoryMap() 79 | { 80 | SuspendLayout(); 81 | bool HadChange; 82 | //All category checkboxes 83 | var AllBoxes = panelCategories.Controls 84 | .OfType() 85 | .ToArray(); 86 | //Unregister events temporarily 87 | foreach (var Box in AllBoxes) 88 | { 89 | Box.CheckedChanged -= CategoryChangeHandler; 90 | } 91 | do 92 | { 93 | HadChange = false; 94 | //All checkboxes that are active 95 | var ActiveBoxes = AllBoxes 96 | .Where(m => m.Checked) 97 | .ToArray(); 98 | //All ids of active checkboxes 99 | var ActiveIds = ActiveBoxes 100 | .Select(m => (int)m.Tag) 101 | .ToArray(); 102 | //All conflicts of checkboxes 103 | var Conflicts = Categories.conflicts 104 | .Where(m => ActiveIds.Contains(m.Category)) 105 | .SelectMany(m => m.conflicts) 106 | .Distinct(); 107 | foreach (var Box in AllBoxes) 108 | { 109 | var IsConflict = Conflicts.Contains((int)Box.Tag); 110 | if (IsConflict && (Box.Enabled || Box.Checked)) 111 | { 112 | Box.Checked = Box.Enabled = false; 113 | HadChange = true; 114 | } 115 | else if (!IsConflict && !Box.Enabled) 116 | { 117 | Box.Enabled = true; 118 | Box.Checked = false; 119 | } 120 | } 121 | foreach (var Id in Conflicts) 122 | { 123 | //Get box of that category 124 | var Box = AllBoxes.FirstOrDefault(m => (int)m.Tag == Id); 125 | 126 | } 127 | 128 | } while (HadChange); 129 | //Add events back 130 | foreach (var Box in AllBoxes) 131 | { 132 | Box.CheckedChanged += CategoryChangeHandler; 133 | } 134 | ResumeLayout(); 135 | } 136 | 137 | private void CategoryChangeHandler(object sender, EventArgs e) 138 | { 139 | ApplyCategoryMap(); 140 | if (!(sender is CheckBox)) 141 | { 142 | return; 143 | } 144 | CheckBox Current = (CheckBox)sender; 145 | var Cat = (int)Current.Tag; 146 | } 147 | 148 | private void frmCloudEdit_HelpRequested(object sender, HelpEventArgs hlpevent) 149 | { 150 | Tools.ShowHelp(GetType().Name); 151 | } 152 | 153 | private void btnCancel_Click(object sender, EventArgs e) 154 | { 155 | DialogResult = DialogResult.Cancel; 156 | } 157 | 158 | private void btnNewId_Click(object sender, EventArgs e) 159 | { 160 | if (MessageBox.Show("Generate a new hidden id? This invalidates the current id immediately regardless of other pending changes in this window.", "Cloud File Edit", MessageBoxButtons.YesNo, MessageBoxIcon.Information) == DialogResult.Yes) 161 | { 162 | try 163 | { 164 | var Res = API.NewId(CurrentMap.id); 165 | if (Res.success) 166 | { 167 | CurrentMap.hidden_id = Res.data; 168 | tbHiddenId.Text = Res.data.ToString(); 169 | } 170 | else 171 | { 172 | throw new Exception(Res.msg); 173 | } 174 | } 175 | catch (Exception ex) 176 | { 177 | Tools.E("Unable to change the id.\r\n" + ex.Message, "Cloud File Edit", this); 178 | } 179 | } 180 | } 181 | 182 | private void btnOK_Click(object sender, EventArgs e) 183 | { 184 | var Categories = panelCategories.Controls 185 | .OfType() 186 | .Where(m => m.Checked) 187 | .Select(m => (int)m.Tag) 188 | .ToArray(); 189 | Thread T = new Thread(delegate () 190 | { 191 | try 192 | { 193 | var Result = API.EditMap(CurrentMap.id, tbFileName.Text, tbDescription.Text, Categories, cbPublic.Checked ? 1 : 0); 194 | if (!Result.success) 195 | { 196 | throw new Exception(Result.msg); 197 | } 198 | Invoke((MethodInvoker)delegate 199 | { 200 | DialogResult = DialogResult.OK; 201 | }); 202 | } 203 | catch (Exception ex) 204 | { 205 | Invoke((MethodInvoker)delegate 206 | { 207 | btnCancel.Enabled = btnOK.Enabled = true; 208 | Tools.E("Error editing the map.\r\n" + ex.Message, "Cloud File Edit", this); 209 | }); 210 | } 211 | }); 212 | btnCancel.Enabled = btnOK.Enabled = false; 213 | T.IsBackground = true; 214 | T.Start(); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmCounter.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace SatisfactorySaveEditor 2 | { 3 | partial class frmCounter 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 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmCounter)); 32 | this.lvCount = new System.Windows.Forms.ListView(); 33 | this.chName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 34 | this.chCount = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 35 | this.btnHide = new System.Windows.Forms.Button(); 36 | this.SuspendLayout(); 37 | // 38 | // lvCount 39 | // 40 | this.lvCount.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { 41 | this.chName, 42 | this.chCount}); 43 | this.lvCount.Dock = System.Windows.Forms.DockStyle.Fill; 44 | this.lvCount.FullRowSelect = true; 45 | this.lvCount.Location = new System.Drawing.Point(0, 0); 46 | this.lvCount.Name = "lvCount"; 47 | this.lvCount.Size = new System.Drawing.Size(292, 550); 48 | this.lvCount.TabIndex = 0; 49 | this.lvCount.UseCompatibleStateImageBehavior = false; 50 | this.lvCount.View = System.Windows.Forms.View.Details; 51 | this.lvCount.DoubleClick += new System.EventHandler(this.lvCount_DoubleClick); 52 | this.lvCount.KeyDown += new System.Windows.Forms.KeyEventHandler(this.lvCount_KeyDown); 53 | // 54 | // chName 55 | // 56 | this.chName.Text = "Name"; 57 | this.chName.Width = 200; 58 | // 59 | // chCount 60 | // 61 | this.chCount.Text = "Count"; 62 | // 63 | // btnHide 64 | // 65 | this.btnHide.Dock = System.Windows.Forms.DockStyle.Bottom; 66 | this.btnHide.Location = new System.Drawing.Point(0, 550); 67 | this.btnHide.Name = "btnHide"; 68 | this.btnHide.Size = new System.Drawing.Size(292, 23); 69 | this.btnHide.TabIndex = 1; 70 | this.btnHide.Text = "Hide unpositioned objects"; 71 | this.btnHide.UseVisualStyleBackColor = true; 72 | this.btnHide.Click += new System.EventHandler(this.btnHide_Click); 73 | // 74 | // frmCounter 75 | // 76 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 77 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 78 | this.ClientSize = new System.Drawing.Size(292, 573); 79 | this.Controls.Add(this.lvCount); 80 | this.Controls.Add(this.btnHide); 81 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 82 | this.Name = "frmCounter"; 83 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 84 | this.Text = "Object Counter"; 85 | this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.frmCounter_HelpRequested); 86 | this.ResumeLayout(false); 87 | 88 | } 89 | 90 | #endregion 91 | 92 | private System.Windows.Forms.ListView lvCount; 93 | private System.Windows.Forms.ColumnHeader chName; 94 | private System.Windows.Forms.ColumnHeader chCount; 95 | private System.Windows.Forms.Button btnHide; 96 | } 97 | } -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmCounter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data; 4 | using System.Drawing; 5 | using System.Linq; 6 | using System.Windows.Forms; 7 | 8 | namespace SatisfactorySaveEditor 9 | { 10 | public partial class frmCounter : Form 11 | { 12 | private SaveFileEntry[] AllEntries; 13 | private SaveFileEntry[] MapEntries; 14 | private bool HiddenElements = false; 15 | 16 | public frmCounter(IEnumerable Entries) 17 | { 18 | var Blank = new Vector3(0, 0, 0); 19 | InitializeComponent(); 20 | Tools.SetupKeyHandlers(this); 21 | 22 | //All entries in the map 23 | AllEntries = Entries.ToArray(); 24 | //Entries that have a valid position 25 | MapEntries = AllEntries 26 | .Where(m => m.EntryType == ObjectTypes.OBJECT_TYPE.OBJECT && !((ObjectTypes.GameObject)m.ObjectData).ObjectPosition.Equals(Blank)) 27 | .ToArray(); 28 | 29 | RenderList(AllEntries); 30 | } 31 | 32 | private void RenderList(SaveFileEntry[] Entries) 33 | { 34 | lvCount.Items.Clear(); 35 | foreach (var E in Entries.GroupBy(m => m.ObjectData.Name).OrderByDescending(m => m.Count())) 36 | { 37 | var SN = new ShortName(E.Key); 38 | var Item = lvCount.Items.Add(SN.Short); 39 | Item.UseItemStyleForSubItems = true; 40 | Item.Tag = SN.Long; 41 | Item.SubItems.Add(E.Count().ToString()); 42 | if (!MapEntries.Contains(E.First())) 43 | { 44 | Item.BackColor = Color.FromKnownColor(KnownColor.Control); 45 | Item.ForeColor = Color.FromKnownColor(KnownColor.WindowText); 46 | } 47 | } 48 | Text = $"Object Counter ({Entries.Length} Objects)"; 49 | lvCount.AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent); 50 | } 51 | 52 | private void RenderSelected() 53 | { 54 | if (lvCount.SelectedItems.Count > 0) 55 | { 56 | var Selected = lvCount.SelectedItems 57 | .OfType() 58 | .SelectMany(m => MapEntries.Where(n => n.ObjectData.Name == m.Tag.ToString())) 59 | .Select(m => new DrawObject(m, Color.Fuchsia, 3)) 60 | .ToArray(); 61 | if (Selected.Length > 0) 62 | { 63 | Image I = null; 64 | try 65 | { 66 | I = MapRender.Render(Selected); 67 | } 68 | catch (Exception ex) 69 | { 70 | Log.Write("{0}: Unable to render selection", GetType().Name); 71 | Log.Write(ex); 72 | Tools.E(@"Unable to render your selection. This is usually the result of memory constraints or a corrupted executable. 73 | It is highly recommended that you verify the application integrity (check if it has a valid signature in the file properties). 74 | You can try to continue using this application but you might see reduced functionality whenever map drawing is involved.", "Item rendering error"); 75 | return; 76 | } 77 | MapRender.MapForm.BackgroundImage.Dispose(); 78 | MapRender.MapForm.BackgroundImage = I; 79 | } 80 | else 81 | { 82 | MessageBox.Show("The selected objects don't have map coordinates.", "No objects to render"); 83 | } 84 | } 85 | } 86 | 87 | private void lvCount_KeyDown(object sender, KeyEventArgs e) 88 | { 89 | if (e.KeyCode == Keys.Enter) 90 | { 91 | RenderSelected(); 92 | } 93 | if (e.KeyCode == Keys.A && e.Control) 94 | { 95 | lvCount.Items.OfType().All(m => m.Selected = true); 96 | } 97 | } 98 | 99 | private void frmCounter_HelpRequested(object sender, HelpEventArgs hlpevent) 100 | { 101 | Tools.ShowHelp(nameof(frmCounter)); 102 | } 103 | 104 | private void btnHide_Click(object sender, EventArgs e) 105 | { 106 | if (HiddenElements) 107 | { 108 | HiddenElements = false; 109 | RenderList(AllEntries); 110 | btnHide.Text = "Hide unpositioned objects"; 111 | } 112 | else 113 | { 114 | HiddenElements = true; 115 | RenderList(MapEntries); 116 | btnHide.Text = "Show unpositioned objects"; 117 | } 118 | } 119 | 120 | private void lvCount_DoubleClick(object sender, EventArgs e) 121 | { 122 | RenderSelected(); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmDeleter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Windows.Forms; 6 | 7 | namespace SatisfactorySaveEditor 8 | { 9 | public partial class frmDeleter : Form 10 | { 11 | private bool HasChange = false; 12 | private SaveFile F; 13 | 14 | public frmDeleter(SaveFile SaveGame) 15 | { 16 | F = SaveGame; 17 | InitializeComponent(); 18 | MaximumSize = new Size(int.MaxValue, MinimumSize.Height); 19 | 20 | initItemList(); 21 | Tools.SetupKeyHandlers(this); 22 | } 23 | 24 | private void initItemList() 25 | { 26 | var LastItem = cbItem.Items.Count > 0 ? cbItem.SelectedIndex : 0; 27 | cbItem.Items.Clear(); 28 | cbItem.Items.AddRange(F.Entries 29 | .Select(m => m.ObjectData.Name) 30 | .Distinct() 31 | .Select(m => new ShortName(m)) 32 | .OrderBy(m => m) 33 | .Cast() 34 | .ToArray()); 35 | if (cbItem.Items.Count > 0) 36 | { 37 | //Don't select beyond the list 38 | cbItem.SelectedIndex = Math.Min(LastItem, cbItem.Items.Count - 1); 39 | } 40 | Log.Write("{0}: List initialized with {1} entries", GetType().Name, cbItem.Items.Count); 41 | } 42 | 43 | private void btnOK_Click(object sender, EventArgs e) 44 | { 45 | var Entries = F.Entries.Where(m => m.ObjectData.Name == ((ShortName)cbItem.SelectedItem).Long); 46 | 47 | if (rbRange.Checked) 48 | { 49 | Entries = Entries.Skip((int)nudStart.Value - 1).Take((int)nudCount.Value); 50 | } 51 | 52 | if (MessageBox.Show(rbAllItems.Checked ? $"Really delete this entry ({Entries.Count()} occurences)?" : $"Really delete {nudCount.Value} instances of this entry?", Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) 53 | { 54 | Log.Write("{0}: Deleting {1} entries", GetType().Name, Entries.Count()); 55 | foreach (var E in Entries.ToArray()) 56 | { 57 | F.Entries.Remove(E); 58 | } 59 | MessageBox.Show($"Done", Text, MessageBoxButtons.OK, MessageBoxIcon.Information); 60 | HasChange = true; 61 | //Reload list if last item taken, otherwise just update the total 62 | var Leftover = F.Entries.Count(m => m.ObjectData.Name == ((ShortName)cbItem.SelectedItem).Long); 63 | if (Leftover == 0) 64 | { 65 | initItemList(); 66 | } 67 | else 68 | { 69 | //Clamp count if needed 70 | nudCount.Value = Math.Min(nudCount.Value, Leftover); 71 | nudCount.Maximum = Leftover; 72 | } 73 | } 74 | } 75 | 76 | private void btnClose_Click(object sender, EventArgs e) 77 | { 78 | if (HasChange) 79 | { 80 | DialogResult = DialogResult.OK; 81 | } 82 | else 83 | { 84 | DialogResult = DialogResult.Cancel; 85 | } 86 | Close(); 87 | } 88 | 89 | private void cbItem_SelectedIndexChanged(object sender, EventArgs e) 90 | { 91 | if (cbItem.SelectedIndex >= 0) 92 | { 93 | var ItemName = ((ShortName)cbItem.SelectedItem).Long; 94 | var Count = F.Entries.Count(m => m.ObjectData.Name == ItemName); 95 | nudStart.Value = 1; 96 | nudStart.Maximum = Count; 97 | nudCount.Value = 1; 98 | nudCount.Maximum = Count; 99 | var Pos = rbAllItems.Location; 100 | rbAllItems.Text = $"All items (total: {Count})"; 101 | rbAllItems.Location = Pos; 102 | } 103 | } 104 | 105 | private void rbRange_CheckedChanged(object sender, EventArgs e) 106 | { 107 | nudStart.Enabled = nudCount.Enabled = rbRange.Checked; 108 | } 109 | 110 | private void btnMap_Click(object sender, EventArgs e) 111 | { 112 | var ItemName = ((ShortName)cbItem.SelectedItem).Long; 113 | var Items = F.Entries.Where(m => m.ObjectData.Name == ItemName); 114 | //Filter if needed 115 | if (rbRange.Checked) 116 | { 117 | Items = Items.Skip((int)nudStart.Value - 1).Take((int)nudCount.Value); 118 | } 119 | if (Items.Count() > 0) 120 | { 121 | if (Items.First().EntryType == ObjectTypes.OBJECT_TYPE.OBJECT) 122 | { 123 | MapRender.MapForm.BackgroundImage.Dispose(); 124 | MapRender.MapForm.BackgroundImage = MapRender.Render(Items.Select(m => new DrawObject(m, Color.Yellow, 10))); 125 | } 126 | else 127 | { 128 | MessageBox.Show("This type of entry has no map coordinates", "Invalid entry type", MessageBoxButtons.OK, MessageBoxIcon.Information); 129 | } 130 | } 131 | else 132 | { 133 | MessageBox.Show("No items to show (0 items selected)", "Invalid entry type", MessageBoxButtons.OK, MessageBoxIcon.Information); 134 | } 135 | } 136 | 137 | private void frmDeleter_HelpRequested(object sender, HelpEventArgs hlpevent) 138 | { 139 | Tools.ShowHelp(GetType().Name); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmDuplicator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Data; 3 | using System.Drawing; 4 | using System.Linq; 5 | using System.Windows.Forms; 6 | 7 | namespace SatisfactorySaveEditor 8 | { 9 | public partial class frmDuplicator : Form 10 | { 11 | private SaveFile F; 12 | private bool HasChange = false; 13 | 14 | public frmDuplicator(SaveFile SaveGame) 15 | { 16 | InitializeComponent(); 17 | //Make width infinitely resizable only 18 | MaximumSize = new Size(int.MaxValue, MinimumSize.Height); 19 | F = SaveGame; 20 | cbObject.Items.AddRange(F.Entries 21 | .Select(m => m.ObjectData.Name) 22 | .Distinct() 23 | .Select(m => new ShortName(m)) 24 | .OrderBy(m => m) 25 | .Cast() 26 | .ToArray()); 27 | if (cbObject.Items.Count > 0) 28 | { 29 | cbObject.SelectedIndex = 0; 30 | } 31 | Log.Write("{0}: List initialized with {1} entries", GetType().Name, cbObject.Items.Count); 32 | Tools.SetupKeyHandlers(this); 33 | } 34 | 35 | private void cbObject_SelectedIndexChanged(object sender, EventArgs e) 36 | { 37 | if (cbObject.SelectedIndex >= 0) 38 | { 39 | var ItemName = ((ShortName)cbObject.SelectedItem).Long; 40 | var Count = F.Entries.Count(m => m.ObjectData.Name == ItemName); 41 | nudOffset.Value = 1; 42 | nudOffset.Maximum = Count; 43 | nudCount.Value = 1; 44 | var Example = F.Entries.First(m => m.ObjectData.Name == ItemName); 45 | if (Example.ObjectData.ObjectType == ObjectTypes.OBJECT_TYPE.OBJECT) 46 | { 47 | cbApplyOffset.Enabled = true; 48 | } 49 | else 50 | { 51 | cbApplyOffset.Checked = false; 52 | cbApplyOffset.Enabled = false; 53 | } 54 | } 55 | } 56 | 57 | private void btnCancel_Click(object sender, EventArgs e) 58 | { 59 | if (HasChange) 60 | { 61 | DialogResult = DialogResult.OK; 62 | } 63 | else 64 | { 65 | DialogResult = DialogResult.Cancel; 66 | } 67 | Close(); 68 | } 69 | 70 | private void btnOK_Click(object sender, EventArgs e) 71 | { 72 | if (MessageBox.Show($"Really copy the entry {nudCount.Value} times?", Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) 73 | { 74 | Log.Write("{0}: Creating {1} duplicates", GetType().Name, nudCount.Value); 75 | //Names of all existing objects 76 | var Names = F.Entries 77 | .Select(m => m.ObjectData) 78 | .OfType() 79 | .Select(m => m.InternalName) 80 | .ToArray(); 81 | //User selected entry 82 | var Entry = F.Entries.Where(m => m.ObjectData.Name == ((ShortName)cbObject.SelectedItem).Long).Skip((int)nudOffset.Value - 1).First(); 83 | //Entry base name (only if object) 84 | var BaseName = (Entry.ObjectData.ObjectType == ObjectTypes.OBJECT_TYPE.OBJECT) ? ((ObjectTypes.GameObject)Entry.ObjectData).InternalName : null; 85 | int NameCounter = 0; 86 | //Remove the number at the end 87 | if (!string.IsNullOrEmpty(BaseName)) 88 | { 89 | BaseName = BaseName.Substring(0, BaseName.LastIndexOf('_')) + "_"; 90 | } 91 | if (Entry != null) 92 | { 93 | for (var i = 0; i < nudCount.Value; i++) 94 | { 95 | var Copy = (SaveFileEntry)Entry.Clone(); 96 | //Replace the InternalName property of copied instances 97 | if (Copy.ObjectData.ObjectType == ObjectTypes.OBJECT_TYPE.OBJECT) 98 | { 99 | var o = (ObjectTypes.GameObject)Copy.ObjectData; 100 | var NewName = BaseName; 101 | do 102 | { 103 | NewName = string.Format("{0}_{1}", BaseName, NameCounter++); 104 | } while (Names.Contains(NewName)); 105 | o.InternalName = NewName; 106 | if (cbApplyOffset.Checked) 107 | { 108 | o.ObjectPosition.X += (int)(i * nudOffsetX.Value); 109 | o.ObjectPosition.Y += (int)(i * nudOffsetY.Value); 110 | o.ObjectPosition.Y += (int)(i * nudOffsetZ.Value); 111 | } 112 | } 113 | F.Entries.Add(Copy); 114 | } 115 | //Update possible offsets for this item 116 | nudOffset.Maximum += nudCount.Value; 117 | MessageBox.Show($"Done", Text, MessageBoxButtons.OK, MessageBoxIcon.Information); 118 | HasChange = true; 119 | } 120 | } 121 | } 122 | 123 | private void cbApplyOffset_CheckedChanged(object sender, EventArgs e) 124 | { 125 | nudOffsetX.Enabled = nudOffsetY.Enabled = nudOffsetZ.Enabled = cbApplyOffset.Checked; 126 | } 127 | 128 | private void btnMap_Click(object sender, EventArgs e) 129 | { 130 | var ItemName = ((ShortName)cbObject.SelectedItem).Long; 131 | var Item = F.Entries.Where(m => m.ObjectData.Name == ItemName).Skip((int)nudOffset.Value - 1).First(); 132 | if (Item.EntryType == ObjectTypes.OBJECT_TYPE.OBJECT) 133 | { 134 | MapRender.MapForm.BackgroundImage.Dispose(); 135 | MapRender.MapForm.BackgroundImage = MapRender.Render(new DrawObject(Item, Color.Yellow, 10)); 136 | } 137 | else 138 | { 139 | MessageBox.Show("This type of entry has no map coordinates", "Invalid entry type", MessageBoxButtons.OK, MessageBoxIcon.Information); 140 | } 141 | } 142 | 143 | private void frmDuplicator_HelpRequested(object sender, HelpEventArgs hlpevent) 144 | { 145 | Tools.ShowHelp(GetType().Name); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmElementList.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace SatisfactorySaveEditor 2 | { 3 | partial class frmElementList 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 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmElementList)); 32 | this.divider = new System.Windows.Forms.SplitContainer(); 33 | this.lvProcess = new System.Windows.Forms.ListView(); 34 | this.chName = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 35 | this.chCount = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 36 | this.label1 = new System.Windows.Forms.Label(); 37 | this.lvSkip = new System.Windows.Forms.ListView(); 38 | this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 39 | this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 40 | this.lblSkip = new System.Windows.Forms.Label(); 41 | this.btnConfirm = new System.Windows.Forms.Button(); 42 | ((System.ComponentModel.ISupportInitialize)(this.divider)).BeginInit(); 43 | this.divider.Panel1.SuspendLayout(); 44 | this.divider.Panel2.SuspendLayout(); 45 | this.divider.SuspendLayout(); 46 | this.SuspendLayout(); 47 | // 48 | // divider 49 | // 50 | this.divider.Dock = System.Windows.Forms.DockStyle.Fill; 51 | this.divider.Location = new System.Drawing.Point(0, 0); 52 | this.divider.Name = "divider"; 53 | this.divider.Orientation = System.Windows.Forms.Orientation.Horizontal; 54 | // 55 | // divider.Panel1 56 | // 57 | this.divider.Panel1.Controls.Add(this.lvProcess); 58 | this.divider.Panel1.Controls.Add(this.label1); 59 | // 60 | // divider.Panel2 61 | // 62 | this.divider.Panel2.Controls.Add(this.lvSkip); 63 | this.divider.Panel2.Controls.Add(this.lblSkip); 64 | this.divider.Panel2.Controls.Add(this.btnConfirm); 65 | this.divider.Size = new System.Drawing.Size(592, 573); 66 | this.divider.SplitterDistance = 306; 67 | this.divider.SplitterWidth = 10; 68 | this.divider.TabIndex = 0; 69 | // 70 | // lvProcess 71 | // 72 | this.lvProcess.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { 73 | this.chName, 74 | this.chCount}); 75 | this.lvProcess.Dock = System.Windows.Forms.DockStyle.Fill; 76 | this.lvProcess.FullRowSelect = true; 77 | this.lvProcess.Location = new System.Drawing.Point(0, 23); 78 | this.lvProcess.Name = "lvProcess"; 79 | this.lvProcess.Size = new System.Drawing.Size(592, 283); 80 | this.lvProcess.TabIndex = 0; 81 | this.lvProcess.UseCompatibleStateImageBehavior = false; 82 | this.lvProcess.View = System.Windows.Forms.View.Details; 83 | this.lvProcess.KeyDown += new System.Windows.Forms.KeyEventHandler(this.lvProcess_KeyDown); 84 | // 85 | // chName 86 | // 87 | this.chName.Text = "Name"; 88 | this.chName.Width = 300; 89 | // 90 | // chCount 91 | // 92 | this.chCount.Text = "Count"; 93 | // 94 | // label1 95 | // 96 | this.label1.Dock = System.Windows.Forms.DockStyle.Top; 97 | this.label1.Location = new System.Drawing.Point(0, 0); 98 | this.label1.Name = "label1"; 99 | this.label1.Size = new System.Drawing.Size(592, 23); 100 | this.label1.TabIndex = 1; 101 | this.label1.Text = "Items in this list will be processed"; 102 | // 103 | // lvSkip 104 | // 105 | this.lvSkip.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { 106 | this.columnHeader1, 107 | this.columnHeader2}); 108 | this.lvSkip.Dock = System.Windows.Forms.DockStyle.Fill; 109 | this.lvSkip.FullRowSelect = true; 110 | this.lvSkip.Location = new System.Drawing.Point(0, 23); 111 | this.lvSkip.Name = "lvSkip"; 112 | this.lvSkip.Size = new System.Drawing.Size(592, 211); 113 | this.lvSkip.TabIndex = 1; 114 | this.lvSkip.UseCompatibleStateImageBehavior = false; 115 | this.lvSkip.View = System.Windows.Forms.View.Details; 116 | this.lvSkip.KeyDown += new System.Windows.Forms.KeyEventHandler(this.lvSkip_KeyDown); 117 | // 118 | // columnHeader1 119 | // 120 | this.columnHeader1.Text = "Name"; 121 | this.columnHeader1.Width = 300; 122 | // 123 | // columnHeader2 124 | // 125 | this.columnHeader2.Text = "Count"; 126 | // 127 | // lblSkip 128 | // 129 | this.lblSkip.Dock = System.Windows.Forms.DockStyle.Top; 130 | this.lblSkip.Location = new System.Drawing.Point(0, 0); 131 | this.lblSkip.Name = "lblSkip"; 132 | this.lblSkip.Size = new System.Drawing.Size(592, 23); 133 | this.lblSkip.TabIndex = 2; 134 | this.lblSkip.Text = "Items in this list will be skipped"; 135 | // 136 | // btnConfirm 137 | // 138 | this.btnConfirm.Dock = System.Windows.Forms.DockStyle.Bottom; 139 | this.btnConfirm.Location = new System.Drawing.Point(0, 234); 140 | this.btnConfirm.Name = "btnConfirm"; 141 | this.btnConfirm.Size = new System.Drawing.Size(592, 23); 142 | this.btnConfirm.TabIndex = 1; 143 | this.btnConfirm.Text = "&Confirm"; 144 | this.btnConfirm.UseVisualStyleBackColor = true; 145 | this.btnConfirm.Click += new System.EventHandler(this.btnConfirm_Click); 146 | // 147 | // frmElementList 148 | // 149 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 150 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 151 | this.ClientSize = new System.Drawing.Size(592, 573); 152 | this.Controls.Add(this.divider); 153 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 154 | this.Name = "frmElementList"; 155 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 156 | this.Text = "Element Process List"; 157 | this.Shown += new System.EventHandler(this.frmElementList_Shown); 158 | this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.frmElementList_HelpRequested); 159 | this.divider.Panel1.ResumeLayout(false); 160 | this.divider.Panel2.ResumeLayout(false); 161 | ((System.ComponentModel.ISupportInitialize)(this.divider)).EndInit(); 162 | this.divider.ResumeLayout(false); 163 | this.ResumeLayout(false); 164 | 165 | } 166 | 167 | #endregion 168 | 169 | private System.Windows.Forms.SplitContainer divider; 170 | private System.Windows.Forms.ListView lvProcess; 171 | private System.Windows.Forms.ColumnHeader chName; 172 | private System.Windows.Forms.ColumnHeader chCount; 173 | private System.Windows.Forms.ListView lvSkip; 174 | private System.Windows.Forms.ColumnHeader columnHeader1; 175 | private System.Windows.Forms.ColumnHeader columnHeader2; 176 | private System.Windows.Forms.Button btnConfirm; 177 | private System.Windows.Forms.Label label1; 178 | private System.Windows.Forms.Label lblSkip; 179 | } 180 | } -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmHeaderEditor.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace SatisfactorySaveEditor 2 | { 3 | partial class frmHeaderEditor 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 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmHeaderEditor)); 32 | this.btnOK = new System.Windows.Forms.Button(); 33 | this.btnCancel = new System.Windows.Forms.Button(); 34 | this.label1 = new System.Windows.Forms.Label(); 35 | this.label2 = new System.Windows.Forms.Label(); 36 | this.tbSessionName = new System.Windows.Forms.TextBox(); 37 | this.tbPlayTime = new System.Windows.Forms.TextBox(); 38 | this.SuspendLayout(); 39 | // 40 | // btnOK 41 | // 42 | this.btnOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 43 | this.btnOK.Location = new System.Drawing.Point(126, 70); 44 | this.btnOK.Name = "btnOK"; 45 | this.btnOK.Size = new System.Drawing.Size(75, 23); 46 | this.btnOK.TabIndex = 4; 47 | this.btnOK.Text = "&OK"; 48 | this.btnOK.UseVisualStyleBackColor = true; 49 | this.btnOK.Click += new System.EventHandler(this.btnOK_Click); 50 | // 51 | // btnCancel 52 | // 53 | this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 54 | this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; 55 | this.btnCancel.Location = new System.Drawing.Point(207, 70); 56 | this.btnCancel.Name = "btnCancel"; 57 | this.btnCancel.Size = new System.Drawing.Size(75, 23); 58 | this.btnCancel.TabIndex = 5; 59 | this.btnCancel.Text = "&Cancel"; 60 | this.btnCancel.UseVisualStyleBackColor = true; 61 | // 62 | // label1 63 | // 64 | this.label1.AutoSize = true; 65 | this.label1.Location = new System.Drawing.Point(5, 15); 66 | this.label1.Name = "label1"; 67 | this.label1.Size = new System.Drawing.Size(73, 13); 68 | this.label1.TabIndex = 0; 69 | this.label1.Text = "Session name"; 70 | // 71 | // label2 72 | // 73 | this.label2.AutoSize = true; 74 | this.label2.Location = new System.Drawing.Point(5, 41); 75 | this.label2.Name = "label2"; 76 | this.label2.Size = new System.Drawing.Size(53, 13); 77 | this.label2.TabIndex = 2; 78 | this.label2.Text = "Play Time"; 79 | // 80 | // tbSessionName 81 | // 82 | this.tbSessionName.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 83 | | System.Windows.Forms.AnchorStyles.Right))); 84 | this.tbSessionName.Location = new System.Drawing.Point(84, 12); 85 | this.tbSessionName.Name = "tbSessionName"; 86 | this.tbSessionName.Size = new System.Drawing.Size(198, 20); 87 | this.tbSessionName.TabIndex = 1; 88 | // 89 | // tbPlayTime 90 | // 91 | this.tbPlayTime.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 92 | | System.Windows.Forms.AnchorStyles.Right))); 93 | this.tbPlayTime.Location = new System.Drawing.Point(84, 38); 94 | this.tbPlayTime.Name = "tbPlayTime"; 95 | this.tbPlayTime.Size = new System.Drawing.Size(198, 20); 96 | this.tbPlayTime.TabIndex = 3; 97 | // 98 | // frmHeaderEditor 99 | // 100 | this.AcceptButton = this.btnOK; 101 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 102 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 103 | this.CancelButton = this.btnCancel; 104 | this.ClientSize = new System.Drawing.Size(294, 105); 105 | this.Controls.Add(this.tbPlayTime); 106 | this.Controls.Add(this.tbSessionName); 107 | this.Controls.Add(this.label2); 108 | this.Controls.Add(this.label1); 109 | this.Controls.Add(this.btnCancel); 110 | this.Controls.Add(this.btnOK); 111 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; 112 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 113 | this.MaximizeBox = false; 114 | this.MinimizeBox = false; 115 | this.Name = "frmHeaderEditor"; 116 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 117 | this.Text = "Header Editor"; 118 | this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.frmHeaderEditor_HelpRequested); 119 | this.ResumeLayout(false); 120 | this.PerformLayout(); 121 | 122 | } 123 | 124 | #endregion 125 | 126 | private System.Windows.Forms.Button btnOK; 127 | private System.Windows.Forms.Button btnCancel; 128 | private System.Windows.Forms.Label label1; 129 | private System.Windows.Forms.Label label2; 130 | private System.Windows.Forms.TextBox tbSessionName; 131 | private System.Windows.Forms.TextBox tbPlayTime; 132 | } 133 | } -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmHeaderEditor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace SatisfactorySaveEditor 5 | { 6 | public partial class frmHeaderEditor : Form 7 | { 8 | public string SessionName { get; private set; } 9 | public string PlayTime { get; private set; } 10 | 11 | public frmHeaderEditor(SaveFile F) 12 | { 13 | InitializeComponent(); 14 | tbSessionName.Text = F.SessionName; 15 | tbPlayTime.Text = F.PlayTime.ToString(); 16 | Tools.SetupKeyHandlers(this); 17 | } 18 | 19 | private void btnOK_Click(object sender, EventArgs e) 20 | { 21 | if(string.IsNullOrEmpty(tbSessionName.Text)) 22 | { 23 | MessageBox.Show("Please enter a session name", "Invalid session name", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); 24 | return; 25 | } 26 | else if(!IsValidTime(tbPlayTime.Text)) 27 | { 28 | MessageBox.Show("Please enter a valid play time in hh:mm:ss format", "Invalid game time", MessageBoxButtons.OK, MessageBoxIcon.Exclamation); 29 | return; 30 | } 31 | SessionName = tbSessionName.Text; 32 | PlayTime = tbPlayTime.Text; 33 | DialogResult = DialogResult.OK; 34 | Close(); 35 | } 36 | 37 | private bool IsValidTime(string text) 38 | { 39 | if (!string.IsNullOrEmpty(text)) 40 | { 41 | TimeSpan T; 42 | return TimeSpan.TryParse(text, out T) && T.TotalMilliseconds >= 0.0; 43 | } 44 | return false; 45 | } 46 | 47 | private void frmHeaderEditor_HelpRequested(object sender, HelpEventArgs hlpevent) 48 | { 49 | Tools.ShowHelp(GetType().Name); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmHelp.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace SatisfactorySaveEditor 2 | { 3 | partial class frmHelp 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 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmHelp)); 32 | this.tbHelp = new System.Windows.Forms.TextBox(); 33 | this.SuspendLayout(); 34 | // 35 | // tbHelp 36 | // 37 | this.tbHelp.Dock = System.Windows.Forms.DockStyle.Fill; 38 | this.tbHelp.Location = new System.Drawing.Point(0, 0); 39 | this.tbHelp.Multiline = true; 40 | this.tbHelp.Name = "tbHelp"; 41 | this.tbHelp.ReadOnly = true; 42 | this.tbHelp.ScrollBars = System.Windows.Forms.ScrollBars.Both; 43 | this.tbHelp.Size = new System.Drawing.Size(592, 373); 44 | this.tbHelp.TabIndex = 0; 45 | // 46 | // frmHelp 47 | // 48 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 49 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 50 | this.ClientSize = new System.Drawing.Size(592, 373); 51 | this.Controls.Add(this.tbHelp); 52 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 53 | this.Name = "frmHelp"; 54 | this.Text = "Satisfactory Save Editor Help"; 55 | this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.frmHelp_HelpRequested); 56 | this.ResumeLayout(false); 57 | this.PerformLayout(); 58 | 59 | } 60 | 61 | #endregion 62 | 63 | private System.Windows.Forms.TextBox tbHelp; 64 | } 65 | } -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmHelp.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Windows.Forms; 3 | 4 | namespace SatisfactorySaveEditor 5 | { 6 | public partial class frmHelp : Form 7 | { 8 | public string HelpText 9 | { 10 | get 11 | { 12 | return tbHelp.Text; 13 | } 14 | set 15 | { 16 | Log.Write("{0}: Updating help text", GetType().Name); 17 | tbHelp.Text = value; 18 | } 19 | } 20 | 21 | public frmHelp() 22 | { 23 | InitializeComponent(); 24 | 25 | //Increase from the default font size because this might contain lots of text 26 | var F = new Font(Font.FontFamily, Font.Size * 1.5f); 27 | Font = F; 28 | Log.Write("{0}: Form created", GetType().Name); 29 | Tools.SetupKeyHandlers(this); 30 | } 31 | 32 | private void frmHelp_HelpRequested(object sender, HelpEventArgs hlpevent) 33 | { 34 | Tools.ShowHelp(GetType().Name); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmRegionDeleter.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace SatisfactorySaveEditor 2 | { 3 | partial class frmRegionDeleter 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 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmRegionDeleter)); 32 | this.mnu = new System.Windows.Forms.MenuStrip(); 33 | this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 34 | this.newSelectionToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 35 | this.deleteObjectsToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 36 | this.closeToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 37 | this.helpToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); 38 | this.pbMap = new System.Windows.Forms.PictureBox(); 39 | this.mnu.SuspendLayout(); 40 | ((System.ComponentModel.ISupportInitialize)(this.pbMap)).BeginInit(); 41 | this.SuspendLayout(); 42 | // 43 | // mnu 44 | // 45 | this.mnu.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { 46 | this.fileToolStripMenuItem, 47 | this.helpToolStripMenuItem}); 48 | this.mnu.Location = new System.Drawing.Point(0, 0); 49 | this.mnu.Name = "mnu"; 50 | this.mnu.Size = new System.Drawing.Size(592, 24); 51 | this.mnu.TabIndex = 0; 52 | this.mnu.Text = "menuStrip1"; 53 | // 54 | // fileToolStripMenuItem 55 | // 56 | this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { 57 | this.newSelectionToolStripMenuItem, 58 | this.deleteObjectsToolStripMenuItem, 59 | this.closeToolStripMenuItem}); 60 | this.fileToolStripMenuItem.Name = "fileToolStripMenuItem"; 61 | this.fileToolStripMenuItem.Size = new System.Drawing.Size(35, 20); 62 | this.fileToolStripMenuItem.Text = "&File"; 63 | // 64 | // newSelectionToolStripMenuItem 65 | // 66 | this.newSelectionToolStripMenuItem.Name = "newSelectionToolStripMenuItem"; 67 | this.newSelectionToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.N))); 68 | this.newSelectionToolStripMenuItem.Size = new System.Drawing.Size(180, 22); 69 | this.newSelectionToolStripMenuItem.Text = "&New Selection"; 70 | this.newSelectionToolStripMenuItem.ToolTipText = "Discards the entire Region"; 71 | this.newSelectionToolStripMenuItem.Click += new System.EventHandler(this.newSelectionToolStripMenuItem_Click); 72 | // 73 | // deleteObjectsToolStripMenuItem 74 | // 75 | this.deleteObjectsToolStripMenuItem.Name = "deleteObjectsToolStripMenuItem"; 76 | this.deleteObjectsToolStripMenuItem.ShortcutKeys = System.Windows.Forms.Keys.Delete; 77 | this.deleteObjectsToolStripMenuItem.Size = new System.Drawing.Size(180, 22); 78 | this.deleteObjectsToolStripMenuItem.Text = "&Delete Objects..."; 79 | this.deleteObjectsToolStripMenuItem.ToolTipText = "Deletes elements in the region (with confirmation)"; 80 | this.deleteObjectsToolStripMenuItem.Click += new System.EventHandler(this.deleteObjectsToolStripMenuItem_Click); 81 | // 82 | // closeToolStripMenuItem 83 | // 84 | this.closeToolStripMenuItem.Name = "closeToolStripMenuItem"; 85 | this.closeToolStripMenuItem.ShortcutKeys = ((System.Windows.Forms.Keys)((System.Windows.Forms.Keys.Control | System.Windows.Forms.Keys.Q))); 86 | this.closeToolStripMenuItem.Size = new System.Drawing.Size(180, 22); 87 | this.closeToolStripMenuItem.Text = "&Close"; 88 | this.closeToolStripMenuItem.ToolTipText = "Closes the form"; 89 | this.closeToolStripMenuItem.Click += new System.EventHandler(this.closeToolStripMenuItem_Click); 90 | // 91 | // helpToolStripMenuItem 92 | // 93 | this.helpToolStripMenuItem.Name = "helpToolStripMenuItem"; 94 | this.helpToolStripMenuItem.Size = new System.Drawing.Size(40, 20); 95 | this.helpToolStripMenuItem.Text = "&Help"; 96 | this.helpToolStripMenuItem.ToolTipText = "Shows the context help. Same as pressing [F1]"; 97 | this.helpToolStripMenuItem.Click += new System.EventHandler(this.helpToolStripMenuItem_Click); 98 | // 99 | // pbMap 100 | // 101 | this.pbMap.Dock = System.Windows.Forms.DockStyle.Fill; 102 | this.pbMap.Location = new System.Drawing.Point(0, 24); 103 | this.pbMap.Name = "pbMap"; 104 | this.pbMap.Size = new System.Drawing.Size(592, 549); 105 | this.pbMap.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; 106 | this.pbMap.TabIndex = 1; 107 | this.pbMap.TabStop = false; 108 | this.pbMap.MouseClick += new System.Windows.Forms.MouseEventHandler(this.pbMap_MouseClick); 109 | this.pbMap.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.pbMap_MouseClick); 110 | // 111 | // frmRegionDeleter 112 | // 113 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 114 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 115 | this.ClientSize = new System.Drawing.Size(592, 573); 116 | this.Controls.Add(this.pbMap); 117 | this.Controls.Add(this.mnu); 118 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 119 | this.MainMenuStrip = this.mnu; 120 | this.Name = "frmRegionDeleter"; 121 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 122 | this.Text = "Region Deleter"; 123 | this.WindowState = System.Windows.Forms.FormWindowState.Maximized; 124 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.frmRegionDeleter_FormClosing); 125 | this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.frmRegionDeleter_HelpRequested); 126 | this.mnu.ResumeLayout(false); 127 | this.mnu.PerformLayout(); 128 | ((System.ComponentModel.ISupportInitialize)(this.pbMap)).EndInit(); 129 | this.ResumeLayout(false); 130 | this.PerformLayout(); 131 | 132 | } 133 | 134 | #endregion 135 | 136 | private System.Windows.Forms.MenuStrip mnu; 137 | private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem; 138 | private System.Windows.Forms.ToolStripMenuItem newSelectionToolStripMenuItem; 139 | private System.Windows.Forms.ToolStripMenuItem deleteObjectsToolStripMenuItem; 140 | private System.Windows.Forms.ToolStripMenuItem closeToolStripMenuItem; 141 | private System.Windows.Forms.ToolStripMenuItem helpToolStripMenuItem; 142 | private System.Windows.Forms.PictureBox pbMap; 143 | } 144 | } -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmRename.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace SatisfactorySaveEditor 2 | { 3 | partial class frmRename 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 | System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(frmRename)); 32 | this.btnOK = new System.Windows.Forms.Button(); 33 | this.btnCancel = new System.Windows.Forms.Button(); 34 | this.label1 = new System.Windows.Forms.Label(); 35 | this.label2 = new System.Windows.Forms.Label(); 36 | this.tbFileName = new System.Windows.Forms.TextBox(); 37 | this.tbSessionName = new System.Windows.Forms.TextBox(); 38 | this.btnHelp = new System.Windows.Forms.Button(); 39 | this.SuspendLayout(); 40 | // 41 | // btnOK 42 | // 43 | this.btnOK.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 44 | this.btnOK.DialogResult = System.Windows.Forms.DialogResult.OK; 45 | this.btnOK.Location = new System.Drawing.Point(124, 79); 46 | this.btnOK.Name = "btnOK"; 47 | this.btnOK.Size = new System.Drawing.Size(75, 23); 48 | this.btnOK.TabIndex = 5; 49 | this.btnOK.Text = "&OK"; 50 | this.btnOK.UseVisualStyleBackColor = true; 51 | // 52 | // btnCancel 53 | // 54 | this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); 55 | this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel; 56 | this.btnCancel.Location = new System.Drawing.Point(205, 79); 57 | this.btnCancel.Name = "btnCancel"; 58 | this.btnCancel.Size = new System.Drawing.Size(75, 23); 59 | this.btnCancel.TabIndex = 6; 60 | this.btnCancel.Text = "&Cancel"; 61 | this.btnCancel.UseVisualStyleBackColor = true; 62 | // 63 | // label1 64 | // 65 | this.label1.AutoSize = true; 66 | this.label1.Location = new System.Drawing.Point(19, 15); 67 | this.label1.Name = "label1"; 68 | this.label1.Size = new System.Drawing.Size(75, 13); 69 | this.label1.TabIndex = 0; 70 | this.label1.Text = "Session Name"; 71 | // 72 | // label2 73 | // 74 | this.label2.AutoSize = true; 75 | this.label2.Location = new System.Drawing.Point(40, 41); 76 | this.label2.Name = "label2"; 77 | this.label2.Size = new System.Drawing.Size(54, 13); 78 | this.label2.TabIndex = 2; 79 | this.label2.Text = "File Name"; 80 | // 81 | // tbFileName 82 | // 83 | this.tbFileName.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 84 | | System.Windows.Forms.AnchorStyles.Right))); 85 | this.tbFileName.Location = new System.Drawing.Point(100, 38); 86 | this.tbFileName.Name = "tbFileName"; 87 | this.tbFileName.Size = new System.Drawing.Size(180, 20); 88 | this.tbFileName.TabIndex = 3; 89 | this.tbFileName.TextChanged += new System.EventHandler(this.tbFileName_TextChanged); 90 | // 91 | // tbSessionName 92 | // 93 | this.tbSessionName.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Left) 94 | | System.Windows.Forms.AnchorStyles.Right))); 95 | this.tbSessionName.Location = new System.Drawing.Point(100, 12); 96 | this.tbSessionName.Name = "tbSessionName"; 97 | this.tbSessionName.Size = new System.Drawing.Size(180, 20); 98 | this.tbSessionName.TabIndex = 1; 99 | this.tbSessionName.TextChanged += new System.EventHandler(this.tbSessionName_TextChanged); 100 | // 101 | // btnHelp 102 | // 103 | this.btnHelp.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left))); 104 | this.btnHelp.Location = new System.Drawing.Point(12, 79); 105 | this.btnHelp.Name = "btnHelp"; 106 | this.btnHelp.Size = new System.Drawing.Size(75, 23); 107 | this.btnHelp.TabIndex = 4; 108 | this.btnHelp.Text = "&Help"; 109 | this.btnHelp.UseVisualStyleBackColor = true; 110 | this.btnHelp.Click += new System.EventHandler(this.btnHelp_Click); 111 | // 112 | // frmRename 113 | // 114 | this.AcceptButton = this.btnOK; 115 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); 116 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 117 | this.CancelButton = this.btnCancel; 118 | this.ClientSize = new System.Drawing.Size(292, 113); 119 | this.Controls.Add(this.btnHelp); 120 | this.Controls.Add(this.tbSessionName); 121 | this.Controls.Add(this.tbFileName); 122 | this.Controls.Add(this.label2); 123 | this.Controls.Add(this.label1); 124 | this.Controls.Add(this.btnCancel); 125 | this.Controls.Add(this.btnOK); 126 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); 127 | this.MinimumSize = new System.Drawing.Size(300, 140); 128 | this.Name = "frmRename"; 129 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; 130 | this.Text = "File rename"; 131 | this.HelpRequested += new System.Windows.Forms.HelpEventHandler(this.frmRename_HelpRequested); 132 | this.ResumeLayout(false); 133 | this.PerformLayout(); 134 | 135 | } 136 | 137 | #endregion 138 | 139 | private System.Windows.Forms.Button btnOK; 140 | private System.Windows.Forms.Button btnCancel; 141 | private System.Windows.Forms.Label label1; 142 | private System.Windows.Forms.Label label2; 143 | private System.Windows.Forms.TextBox tbFileName; 144 | private System.Windows.Forms.TextBox tbSessionName; 145 | private System.Windows.Forms.Button btnHelp; 146 | } 147 | } -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmRename.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Drawing; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Windows.Forms; 6 | 7 | namespace SatisfactorySaveEditor 8 | { 9 | public partial class frmRename : Form 10 | { 11 | public string RenameSessionName 12 | { 13 | get 14 | { 15 | return IsDisposed ? null : tbSessionName.Text; 16 | } 17 | } 18 | public string RenameFileName 19 | { 20 | get 21 | { 22 | return IsDisposed ? null : tbFileName.Text; 23 | } 24 | } 25 | 26 | public frmRename(string SessionName, string FileName) 27 | { 28 | InitializeComponent(); 29 | MaximumSize = new Size(int.MaxValue, MinimumSize.Height); 30 | tbSessionName.Text = SessionName; 31 | tbFileName.Text = FileName; 32 | SetBtn(); 33 | Tools.SetupKeyHandlers(this); 34 | } 35 | 36 | private void SetBtn() 37 | { 38 | var BaseState = true; 39 | if (BaseState && string.IsNullOrWhiteSpace(tbSessionName.Text)) 40 | { 41 | BaseState = false; 42 | } 43 | if (BaseState && string.IsNullOrWhiteSpace(tbFileName.Text)) 44 | { 45 | BaseState = false; 46 | } 47 | if (BaseState && Path.GetInvalidFileNameChars().Any(m => tbSessionName.Text.Contains(m))) 48 | { 49 | BaseState = false; 50 | } 51 | if (BaseState && Path.GetInvalidFileNameChars().Any(m => tbFileName.Text.Contains(m))) 52 | { 53 | BaseState = false; 54 | } 55 | if (btnOK.Enabled != BaseState) 56 | { 57 | btnOK.Enabled = BaseState; 58 | } 59 | } 60 | 61 | private void tbSessionName_TextChanged(object sender, EventArgs e) 62 | { 63 | SetBtn(); 64 | } 65 | 66 | private void tbFileName_TextChanged(object sender, EventArgs e) 67 | { 68 | SetBtn(); 69 | } 70 | 71 | private void btnHelp_Click(object sender, EventArgs e) 72 | { 73 | Tools.ShowHelp(GetType().Name); 74 | } 75 | 76 | private void frmRename_HelpRequested(object sender, HelpEventArgs hlpevent) 77 | { 78 | Tools.ShowHelp(GetType().Name); 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/UI/frmSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Forms; 3 | 4 | namespace SatisfactorySaveEditor 5 | { 6 | public partial class frmSettings : Form 7 | { 8 | private Settings CurrentSettings; 9 | 10 | public frmSettings(Settings S) 11 | { 12 | CurrentSettings = S; 13 | InitializeComponent(); 14 | Tools.SetupKeyHandlers(this); 15 | SetUiValues(); 16 | } 17 | 18 | /// 19 | /// Sets the UI values to match the current settings 20 | /// 21 | private void SetUiValues() 22 | { 23 | cbAutostartManager.Checked = CurrentSettings.AutostartManager; 24 | cbAutoUpdate.Checked = CurrentSettings.AutoUpdate; 25 | cbShowChangeLog.Checked = CurrentSettings.ShowChangelog; 26 | cbRandom.Checked = CurrentSettings.UseRandomId; 27 | cbStopReporting.Checked = CurrentSettings.DisableUsageReport; 28 | lblId.Text = CurrentSettings.DisableUsageReport ? Guid.Empty.ToString() : CurrentSettings.ReportId.ToString(); 29 | } 30 | 31 | private void frmSettings_HelpRequested(object sender, HelpEventArgs hlpevent) 32 | { 33 | Tools.ShowHelp(GetType().Name); 34 | } 35 | 36 | #region Settings 37 | 38 | private void btnMessageHide_Click(object sender, EventArgs e) 39 | { 40 | CurrentSettings.MarkDialogsRead(true); 41 | } 42 | 43 | private void btnMessageShow_Click(object sender, EventArgs e) 44 | { 45 | CurrentSettings.MarkDialogsRead(false); 46 | } 47 | 48 | private void cbAutoUpdate_CheckedChanged(object sender, EventArgs e) 49 | { 50 | CurrentSettings.AutoUpdate = cbAutoUpdate.Checked; 51 | } 52 | 53 | private void cbShowChangeLog_CheckedChanged(object sender, EventArgs e) 54 | { 55 | CurrentSettings.ShowChangelog = cbShowChangeLog.Checked; 56 | } 57 | 58 | private void cbStopReporting_CheckedChanged(object sender, EventArgs e) 59 | { 60 | cbRandom.Enabled = !cbStopReporting.Checked; 61 | } 62 | 63 | private void cbAutostartManager_CheckedChanged(object sender, EventArgs e) 64 | { 65 | CurrentSettings.AutostartManager = cbAutostartManager.Checked; 66 | } 67 | 68 | private void lblId_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 69 | { 70 | if (!CurrentSettings.DisableUsageReport) 71 | { 72 | if (CurrentSettings.UseRandomId) 73 | { 74 | Tools.E("You can't view your usage report if you enable the random id feature because there will not be a report yet for the displayed id", "Usage Report"); 75 | } 76 | else 77 | { 78 | if (e.Button == MouseButtons.Left) 79 | { 80 | System.Diagnostics.Process.Start(FeatureReport.REPORT_URL + CurrentSettings.ReportId.ToString()); 81 | } 82 | if (e.Button == MouseButtons.Right) 83 | { 84 | if (MessageBox.Show(@"You will lose access to your old statistics if you change the id without writing the old one down first. 85 | 86 | Really change your Id?", "New Id", MessageBoxButtons.YesNo, MessageBoxIcon.Exclamation, MessageBoxDefaultButton.Button2) == DialogResult.Yes) 87 | { 88 | FeatureReport.Id = CurrentSettings.ReportId = Guid.NewGuid(); 89 | SetUiValues(); 90 | } 91 | } 92 | } 93 | } 94 | else 95 | { 96 | Tools.E("Usage reporting is currently disabled", "Usage Report"); 97 | } 98 | } 99 | 100 | private void btnClearKey_Click(object sender, EventArgs e) 101 | { 102 | if (CurrentSettings.ApiKey != SMRAPI.API.API_ANONYMOUS_KEY) 103 | { 104 | if (MessageBox.Show("Really clear the API key? This will disable the API but will not delete any maps stored on it.", "Clear API key", MessageBoxButtons.YesNo, MessageBoxIcon.Information, MessageBoxDefaultButton.Button2) == DialogResult.Yes) 105 | { 106 | CurrentSettings.ApiKey = SMRAPI.API.ApiKey = SMRAPI.API.API_ANONYMOUS_KEY; 107 | } 108 | } 109 | else 110 | { 111 | Tools.I("There is no key set at the moment.", "API Key", this); 112 | } 113 | } 114 | 115 | private void lnkOpenRepository_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) 116 | { 117 | try 118 | { 119 | System.Diagnostics.Process.Start(SMRAPI.API.API_BASE); 120 | } 121 | catch (Exception ex) 122 | { 123 | Tools.E($"Unable to launch your browser. You can go manually to {SMRAPI.API.API_BASE}.\r\nDetails: " + ex.Message, "SMR API", this); 124 | } 125 | } 126 | 127 | #endregion 128 | 129 | private void btnClose_Click(object sender, EventArgs e) 130 | { 131 | //Disregard user random Id setting if reports are disabled 132 | CurrentSettings.UseRandomId = cbRandom.Checked && !cbStopReporting.Checked; 133 | //User disabled reports 134 | if (!CurrentSettings.DisableUsageReport && cbStopReporting.Checked) 135 | { 136 | FeatureReport.Used(FeatureReport.Feature.DisableReport); 137 | FeatureReport.Report(); 138 | CurrentSettings.ReportId = FeatureReport.Id = Guid.Empty; 139 | } 140 | else if (CurrentSettings.ReportId == Guid.Empty && !cbStopReporting.Checked) 141 | { 142 | //Generate a new Id if reports are enabled and no Id is present 143 | CurrentSettings.ReportId = FeatureReport.Id = Guid.NewGuid(); 144 | } 145 | Close(); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /SatisfactorySaveEditor/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | --------------------------------------------------------------------------------