├── .gitignore
├── FEAT.sln
├── FEAT
├── BCH.cs
├── CTR_Textures.cs
├── DSDecmp
│ ├── Exceptions
│ │ ├── InputTooLargeException.cs
│ │ ├── NotEnoughDataException.cs
│ │ ├── StreamTooShortException.cs
│ │ └── TooMuchInputException.cs
│ ├── Formats
│ │ ├── CompositeFormat.cs
│ │ ├── CompressionFormat.cs
│ │ ├── LZOvl.cs
│ │ └── Nitro
│ │ │ ├── CompositeFormats.cs
│ │ │ ├── Huffman.cs
│ │ │ ├── LZ10.cs
│ │ │ ├── LZ11.cs
│ │ │ ├── NitroCFormat.cs
│ │ │ ├── NullCompression.cs
│ │ │ └── RLE.cs
│ └── Utils
│ │ ├── IOUtils.cs
│ │ ├── LZUtil.cs
│ │ └── SimpleReversePrioQueue.cs
├── ETC1.dll
├── FEAT Icon.ico
├── FEAT.csproj
├── Form1.Designer.cs
├── Form1.cs
├── Form1.resx
├── Program.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Resources.Designer.cs
│ ├── Resources.resx
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── ctpktool
│ ├── CTPK.cs
│ ├── CTPKEntry.cs
│ ├── Crc32.cs
│ ├── ETC.cs
│ ├── Program.cs
│ ├── Texture.cs
│ └── TextureFormat.cs
└── obj
│ └── x86
│ └── Debug
│ ├── DesignTimeResolveAssemblyReferences.cache
│ ├── DesignTimeResolveAssemblyReferencesInput.cache
│ ├── FEAT.csproj.FileListAbsolute.txt
│ ├── FEAT.csproj.GenerateResource.Cache
│ ├── FEAT.csprojResolveAssemblyReference.cache
│ ├── FEAT.exe
│ ├── FEAT.pdb
│ ├── Fire_Emblem_Awakening_Archive_Tool.Form1.resources
│ ├── Fire_Emblem_Awakening_Archive_Tool.Properties.Resources.resources
│ └── TempPE
│ └── Properties.Resources.Designer.cs.dll
├── LICENSE
├── README.md
└── example_pictures
├── Newicon.png
├── UI.png
└── UI_2.png
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # MSTest test Results
33 | [Tt]est[Rr]esult*/
34 | [Bb]uild[Ll]og.*
35 |
36 | # NUNIT
37 | *.VisualState.xml
38 | TestResult.xml
39 |
40 | # Build Results of an ATL Project
41 | [Dd]ebugPS/
42 | [Rr]eleasePS/
43 | dlldata.c
44 |
45 | # .NET Core
46 | project.lock.json
47 | project.fragment.lock.json
48 | artifacts/
49 | **/Properties/launchSettings.json
50 |
51 | *_i.c
52 | *_p.c
53 | *_i.h
54 | *.ilk
55 | *.meta
56 | *.obj
57 | *.pch
58 | *.pdb
59 | *.pgc
60 | *.pgd
61 | *.rsp
62 | *.sbr
63 | *.tlb
64 | *.tli
65 | *.tlh
66 | *.tmp
67 | *.tmp_proj
68 | *.log
69 | *.vspscc
70 | *.vssscc
71 | .builds
72 | *.pidb
73 | *.svclog
74 | *.scc
75 |
76 | # Chutzpah Test files
77 | _Chutzpah*
78 |
79 | # Visual C++ cache files
80 | ipch/
81 | *.aps
82 | *.ncb
83 | *.opendb
84 | *.opensdf
85 | *.sdf
86 | *.cachefile
87 | *.VC.db
88 | *.VC.VC.opendb
89 |
90 | # Visual Studio profiler
91 | *.psess
92 | *.vsp
93 | *.vspx
94 | *.sap
95 |
96 | # TFS 2012 Local Workspace
97 | $tf/
98 |
99 | # Guidance Automation Toolkit
100 | *.gpState
101 |
102 | # ReSharper is a .NET coding add-in
103 | _ReSharper*/
104 | *.[Rr]e[Ss]harper
105 | *.DotSettings.user
106 |
107 | # JustCode is a .NET coding add-in
108 | .JustCode
109 |
110 | # TeamCity is a build add-in
111 | _TeamCity*
112 |
113 | # DotCover is a Code Coverage Tool
114 | *.dotCover
115 |
116 | # Visual Studio code coverage results
117 | *.coverage
118 | *.coveragexml
119 |
120 | # NCrunch
121 | _NCrunch_*
122 | .*crunch*.local.xml
123 | nCrunchTemp_*
124 |
125 | # MightyMoose
126 | *.mm.*
127 | AutoTest.Net/
128 |
129 | # Web workbench (sass)
130 | .sass-cache/
131 |
132 | # Installshield output folder
133 | [Ee]xpress/
134 |
135 | # DocProject is a documentation generator add-in
136 | DocProject/buildhelp/
137 | DocProject/Help/*.HxT
138 | DocProject/Help/*.HxC
139 | DocProject/Help/*.hhc
140 | DocProject/Help/*.hhk
141 | DocProject/Help/*.hhp
142 | DocProject/Help/Html2
143 | DocProject/Help/html
144 |
145 | # Click-Once directory
146 | publish/
147 |
148 | # Publish Web Output
149 | *.[Pp]ublish.xml
150 | *.azurePubxml
151 | # TODO: Comment the next line if you want to checkin your web deploy settings
152 | # but database connection strings (with potential passwords) will be unencrypted
153 | *.pubxml
154 | *.publishproj
155 |
156 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
157 | # checkin your Azure Web App publish settings, but sensitive information contained
158 | # in these scripts will be unencrypted
159 | PublishScripts/
160 |
161 | # NuGet Packages
162 | *.nupkg
163 | # The packages folder can be ignored because of Package Restore
164 | **/packages/*
165 | # except build/, which is used as an MSBuild target.
166 | !**/packages/build/
167 | # Uncomment if necessary however generally it will be regenerated when needed
168 | #!**/packages/repositories.config
169 | # NuGet v3's project.json files produces more ignorable files
170 | *.nuget.props
171 | *.nuget.targets
172 |
173 | # Microsoft Azure Build Output
174 | csx/
175 | *.build.csdef
176 |
177 | # Microsoft Azure Emulator
178 | ecf/
179 | rcf/
180 |
181 | # Windows Store app package directories and files
182 | AppPackages/
183 | BundleArtifacts/
184 | Package.StoreAssociation.xml
185 | _pkginfo.txt
186 |
187 | # Visual Studio cache files
188 | # files ending in .cache can be ignored
189 | *.[Cc]ache
190 | # but keep track of directories ending in .cache
191 | !*.[Cc]ache/
192 |
193 | # Others
194 | ClientBin/
195 | ~$*
196 | *~
197 | *.dbmdl
198 | *.dbproj.schemaview
199 | *.jfm
200 | *.pfx
201 | *.publishsettings
202 | orleans.codegen.cs
203 |
204 | # Since there are multiple workflows, uncomment next line to ignore bower_components
205 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
206 | #bower_components/
207 |
208 | # RIA/Silverlight projects
209 | Generated_Code/
210 |
211 | # Backup & report files from converting an old project file
212 | # to a newer Visual Studio version. Backup files are not needed,
213 | # because we have git ;-)
214 | _UpgradeReport_Files/
215 | Backup*/
216 | UpgradeLog*.XML
217 | UpgradeLog*.htm
218 |
219 | # SQL Server files
220 | *.mdf
221 | *.ldf
222 | *.ndf
223 |
224 | # Business Intelligence projects
225 | *.rdl.data
226 | *.bim.layout
227 | *.bim_*.settings
228 |
229 | # Microsoft Fakes
230 | FakesAssemblies/
231 |
232 | # GhostDoc plugin setting file
233 | *.GhostDoc.xml
234 |
235 | # Node.js Tools for Visual Studio
236 | .ntvs_analysis.dat
237 | node_modules/
238 |
239 | # Typescript v1 declaration files
240 | typings/
241 |
242 | # Visual Studio 6 build log
243 | *.plg
244 |
245 | # Visual Studio 6 workspace options file
246 | *.opt
247 |
248 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
249 | *.vbw
250 |
251 | # Visual Studio LightSwitch build output
252 | **/*.HTMLClient/GeneratedArtifacts
253 | **/*.DesktopClient/GeneratedArtifacts
254 | **/*.DesktopClient/ModelManifest.xml
255 | **/*.Server/GeneratedArtifacts
256 | **/*.Server/ModelManifest.xml
257 | _Pvt_Extensions
258 |
259 | # Paket dependency manager
260 | .paket/paket.exe
261 | paket-files/
262 |
263 | # FAKE - F# Make
264 | .fake/
265 |
266 | # JetBrains Rider
267 | .idea/
268 | *.sln.iml
269 |
270 | # CodeRush
271 | .cr/
272 |
273 | # Python Tools for Visual Studio (PTVS)
274 | __pycache__/
275 | *.pyc
276 |
277 | # Cake - Uncomment if you are using it
278 | # tools/**
279 | # !tools/packages.config
280 |
281 | # Telerik's JustMock configuration file
282 | *.jmconfig
283 |
284 | # BizTalk build output
285 | *.btp.cs
286 | *.btm.cs
287 | *.odx.cs
288 | *.xsd.cs
289 |
--------------------------------------------------------------------------------
/FEAT.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 11.00
3 | # Visual Studio 2010
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FEAT", "FEAT\FEAT.csproj", "{B80067EB-C976-4A91-8E64-3C555AA16FE3}"
5 | EndProject
6 | Global
7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
8 | Debug|x86 = Debug|x86
9 | Release|x86 = Release|x86
10 | EndGlobalSection
11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
12 | {B80067EB-C976-4A91-8E64-3C555AA16FE3}.Debug|x86.ActiveCfg = Debug|x86
13 | {B80067EB-C976-4A91-8E64-3C555AA16FE3}.Debug|x86.Build.0 = Debug|x86
14 | {B80067EB-C976-4A91-8E64-3C555AA16FE3}.Release|x86.ActiveCfg = Release|x86
15 | {B80067EB-C976-4A91-8E64-3C555AA16FE3}.Release|x86.Build.0 = Release|x86
16 | EndGlobalSection
17 | GlobalSection(SolutionProperties) = preSolution
18 | HideSolutionNode = FALSE
19 | EndGlobalSection
20 | EndGlobal
21 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Exceptions/InputTooLargeException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DSDecmp
6 | {
7 | ///
8 | /// An exception indicating that the file cannot be compressed, because the decompressed size
9 | /// cannot be represented in the current compression format.
10 | ///
11 | public class InputTooLargeException : Exception
12 | {
13 | ///
14 | /// Creates a new exception that indicates that the input is too big to be compressed.
15 | ///
16 | public InputTooLargeException()
17 | : base("The compression ratio is not high enough to fit the input "
18 | + "in a single compressed file.") { }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Exceptions/NotEnoughDataException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.IO;
5 |
6 | namespace DSDecmp
7 | {
8 | ///
9 | /// An exception that is thrown by the decompression functions when there
10 | /// is not enough data available in order to properly decompress the input.
11 | ///
12 | public class NotEnoughDataException : IOException
13 | {
14 | private long currentOutSize;
15 | private long totalOutSize;
16 | ///
17 | /// Gets the actual number of written bytes.
18 | ///
19 | public long WrittenLength { get { return this.currentOutSize; } }
20 | ///
21 | /// Gets the number of bytes that was supposed to be written.
22 | ///
23 | public long DesiredLength { get { return this.totalOutSize; } }
24 |
25 | ///
26 | /// Creates a new NotEnoughDataException.
27 | ///
28 | /// The actual number of written bytes.
29 | /// The desired number of written bytes.
30 | public NotEnoughDataException(long currentOutSize, long totalOutSize)
31 | : base("Not enough data availble; 0x" + currentOutSize.ToString("X")
32 | + " of " + (totalOutSize < 0 ? "???" : ("0x" + totalOutSize.ToString("X")))
33 | + " bytes written.")
34 | {
35 | this.currentOutSize = currentOutSize;
36 | this.totalOutSize = totalOutSize;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Exceptions/StreamTooShortException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.IO;
5 |
6 | namespace DSDecmp
7 | {
8 | ///
9 | /// An exception thrown by the compression or decompression function, indicating that the
10 | /// given input length was too large for the given input stream.
11 | ///
12 | public class StreamTooShortException : EndOfStreamException
13 | {
14 | ///
15 | /// Creates a new exception that indicates that the stream was shorter than the given input length.
16 | ///
17 | public StreamTooShortException()
18 | : base("The end of the stream was reached "
19 | + "before the given amout of data was read.")
20 | { }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Exceptions/TooMuchInputException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DSDecmp
6 | {
7 | ///
8 | /// An exception indication that the input has more data than required in order
9 | /// to decompress it. This may indicate that more sub-files are present in the file.
10 | ///
11 | public class TooMuchInputException : Exception
12 | {
13 | ///
14 | /// Gets the number of bytes read by the decompressed to decompress the stream.
15 | ///
16 | public long ReadBytes { get; private set; }
17 |
18 | ///
19 | /// Creates a new exception indicating that the input has more data than necessary for
20 | /// decompressing th stream. It may indicate that other data is present after the compressed
21 | /// stream.
22 | ///
23 | /// The number of bytes read by the decompressor.
24 | /// The indicated length of the input stream.
25 | public TooMuchInputException(long readBytes, long totLength)
26 | : base("The input contains more data than necessary. Only used 0x"
27 | + readBytes.ToString("X") + " of 0x" + totLength.ToString("X") + " bytes")
28 | {
29 | this.ReadBytes = readBytes;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Formats/CompositeFormat.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.IO;
5 |
6 | namespace DSDecmp.Formats
7 | {
8 | ///
9 | /// A format that is composed of multiple formats.
10 | /// When compressing, the input is compressed using the best contained format.
11 | /// When decompressing, all contained formats will try to decompress the file, until one succeeds.
12 | ///
13 | public abstract class CompositeFormat : CompressionFormat
14 | {
15 | ///
16 | /// The actual list of formats this format is somposed of.
17 | ///
18 | private List formats;
19 |
20 |
21 | #region Constructors
22 |
23 | ///
24 | /// Creates a new composite format based on the given sequence of formats.
25 | ///
26 | protected CompositeFormat(IEnumerable formats)
27 | {
28 | this.formats = new List(formats);
29 | }
30 | ///
31 | /// Creates a new composite format based on the given formats.
32 | ///
33 | protected CompositeFormat(params CompressionFormat[] formats)
34 | {
35 | this.formats = new List(formats);
36 | }
37 |
38 | #endregion
39 |
40 |
41 | #region Method: Supports
42 | ///
43 | /// Checks if any of the contained formats supports the given input.
44 | ///
45 | public override bool Supports(System.IO.Stream stream, long inLength)
46 | {
47 | foreach (CompositeFormat fmt in this.formats)
48 | {
49 | if (fmt.Supports(stream, inLength))
50 | return true;
51 | }
52 | return false;
53 | }
54 | #endregion
55 |
56 | #region Method: Decompress
57 | ///
58 | /// Attempts to decompress the given input by letting all contained formats
59 | /// try to decompress the input.
60 | ///
61 | public override long Decompress(System.IO.Stream instream, long inLength, System.IO.Stream outstream)
62 | {
63 | byte[] inputData = new byte[instream.Length];
64 | instream.Read(inputData, 0, inputData.Length);
65 |
66 | foreach (CompressionFormat format in this.formats)
67 | {
68 | if (!format.SupportsDecompression)
69 | continue;
70 | using (MemoryStream input = new MemoryStream(inputData))
71 | {
72 | if (!format.Supports(input, inputData.Length))
73 | continue;
74 | MemoryStream output = new MemoryStream();
75 | try
76 | {
77 | long decLength = format.Decompress(input, inputData.Length, output);
78 | if (decLength > 0)
79 | {
80 | output.WriteTo(outstream);
81 | return decLength;
82 | }
83 | }
84 | catch (Exception) { continue; }
85 | }
86 | }
87 |
88 | throw new InvalidDataException("Input cannot be decompressed using the " + this.ShortFormatString + " formats.");
89 | }
90 | #endregion
91 |
92 | #region Method: Compress & Field: LastUsedCompressFormatString
93 | ///
94 | /// Gets the ShortFormatString of the last CompressionFormat that was used to compress input.
95 | ///
96 | public string LastUsedCompressFormatString { get; private set; }
97 | ///
98 | /// Compresses the given input using the contained format that yields the best results in terms of
99 | /// size reduction.
100 | ///
101 | public override int Compress(System.IO.Stream instream, long inLength, System.IO.Stream outstream)
102 | {
103 | // only read the input data once from the file.
104 | byte[] inputData = new byte[instream.Length];
105 | instream.Read(inputData, 0, inputData.Length);
106 |
107 | MemoryStream bestOutput = null;
108 | string bestFormatString = "";
109 | int minCompSize = int.MaxValue;
110 | foreach (CompressionFormat format in formats)
111 | {
112 | if (!format.SupportsCompression)
113 | continue;
114 |
115 | #region compress the file in each format, and save the best one
116 |
117 | MemoryStream currentOutput = new MemoryStream();
118 | int currentOutSize;
119 | try
120 | {
121 | using (MemoryStream input = new MemoryStream(inputData))
122 | {
123 | currentOutSize = format.Compress(input, input.Length, currentOutput);
124 | }
125 | }
126 | catch (InputTooLargeException i)
127 | {
128 | Console.WriteLine(i.Message);
129 | bestFormatString = format.ShortFormatString;
130 | return -1;
131 | }
132 | catch (Exception)
133 | {
134 | continue;
135 | }
136 | if (currentOutSize < minCompSize)
137 | {
138 | bestOutput = currentOutput;
139 | minCompSize = currentOutSize;
140 | bestFormatString = format.ShortFormatString;
141 | }
142 |
143 | #endregion
144 | }
145 |
146 | if (bestOutput == null)
147 | return -1;
148 | bestOutput.WriteTo(outstream);
149 | this.LastUsedCompressFormatString = bestFormatString;
150 | return minCompSize;
151 | }
152 | #endregion
153 |
154 | #region Method: ParseCompressionOptions(args)
155 | ///
156 | /// Handles the compression options for each of the contained compression formats.
157 | ///
158 | public override int ParseCompressionOptions(string[] args)
159 | {
160 | // try each option on each of the formats.
161 | // each pass over the formats lets them try to consume the options.
162 | // if one or more formats consume at least one option, the maximum number
163 | // of consumed options is treated as 'handled'; they are ignored in the
164 | // next pass. This continues until none of the formats consume the next
165 | // value in the options.
166 |
167 | int totalOptionCount = 0;
168 | bool usedOption = true;
169 | while (usedOption)
170 | {
171 | usedOption = false;
172 | if (args.Length <= totalOptionCount)
173 | break;
174 | int maxOptionCount = 0;
175 | string[] subArray = new string[args.Length - totalOptionCount];
176 | Array.Copy(args, totalOptionCount, subArray, 0, subArray.Length);
177 | foreach (CompressionFormat format in this.formats)
178 | {
179 | int optCount = format.ParseCompressionOptions(subArray);
180 | maxOptionCount = Math.Max(optCount, maxOptionCount);
181 | }
182 |
183 | if (maxOptionCount > 0)
184 | {
185 | totalOptionCount += maxOptionCount;
186 | usedOption = true;
187 | }
188 | }
189 | return totalOptionCount;
190 | }
191 | #endregion
192 |
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Formats/CompressionFormat.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.IO;
5 |
6 | namespace DSDecmp
7 | {
8 | ///
9 | /// Base class for all compression formats.
10 | ///
11 | public abstract class CompressionFormat
12 | {
13 | ///
14 | /// Checks if the decompressor for this format supports the given file. Assumes the
15 | /// file exists. Returns false when it is certain that the given file is not supported.
16 | /// False positives may occur, as this method should not do any decompression, and
17 | /// may mis-interpret a similar file format as compressed.
18 | ///
19 | /// The name of the file to check.
20 | /// False if the file can certainly not be decompressed using this decompressor.
21 | /// True if the file may potentially be decompressed using this decompressor.
22 | public virtual bool Supports(string file)
23 | {
24 | // open the file, and delegate to the decompressor-specific code.
25 | using (FileStream fstr = new FileStream(file, FileMode.Open))
26 | {
27 | return this.Supports(fstr, fstr.Length);
28 | }
29 | }
30 |
31 | ///
32 | /// Checks if the decompressor for this format supports the data from the given stream.
33 | /// Returns false when it is certain that the given data is not supported.
34 | /// False positives may occur, as this method should not do any decompression, and may
35 | /// mis-interpret a similar data format as compressed.
36 | ///
37 | /// The stream that may or may not contain compressed data. The
38 | /// position of this stream may change during this call, but will be returned to its
39 | /// original position when the method returns.
40 | /// The length of the input stream.
41 | /// False if the data can certainly not be decompressed using this decompressor.
42 | /// True if the data may potentially be decompressed using this decompressor.
43 | public abstract bool Supports(Stream stream, long inLength);
44 |
45 | ///
46 | /// Decompresses the given file, writing the deocmpressed data to the given output file.
47 | /// The output file will be overwritten if it already exists.
48 | /// Assumes Supports(infile)
returns true
.
49 | ///
50 | /// The file to decompress.
51 | /// The target location of the decompressed file.
52 | public virtual void Decompress(string infile, string outfile)
53 | {
54 | // make sure the output directory exists
55 | string outDirectory = Path.GetDirectoryName(outfile);
56 | if (!Directory.Exists(outDirectory))
57 | Directory.CreateDirectory(outDirectory);
58 | // open the two given files, and delegate to the format-specific code.
59 | using (FileStream inStream = new FileStream(infile, FileMode.Open),
60 | outStream = new FileStream(outfile, FileMode.Create))
61 | {
62 | this.Decompress(inStream, inStream.Length, outStream);
63 | }
64 | }
65 |
66 | ///
67 | /// Decompresses the given stream, writing the decompressed data to the given output stream.
68 | /// Assumes Supports(instream)
returns true
.
69 | /// After this call, the input stream will be positioned at the end of the compressed stream,
70 | /// or at the initial position + inLength
, whichever comes first.
71 | ///
72 | /// The stream to decompress. At the end of this method, the position
73 | /// of this stream is directly after the compressed data.
74 | /// The length of the input data. Not necessarily all of the
75 | /// input data may be read (if there is padding, for example), however never more than
76 | /// this number of bytes is read from the input stream.
77 | /// The stream to write the decompressed data to.
78 | /// The length of the output data.
79 | /// When the given length of the input data
80 | /// is not enough to properly decompress the input.
81 | public abstract long Decompress(Stream instream, long inLength, Stream outstream);
82 |
83 | ///
84 | /// Compresses the given input file, and writes the compressed data to the given
85 | /// output file.
86 | ///
87 | /// The file to compress.
88 | /// The file to write the compressed data to.
89 | /// The size of the compressed file. If -1, the file could not be compressed.
90 | public int Compress(string infile, string outfile)
91 | {
92 | // make sure the output directory exists
93 | string outDirectory = Path.GetDirectoryName(outfile);
94 | if (!Directory.Exists(outDirectory))
95 | Directory.CreateDirectory(outDirectory);
96 | // open the proper Streams, and delegate to the format-specific code.
97 | using (FileStream inStream = File.Open(infile, FileMode.Open),
98 | outStream = File.Create(outfile))
99 | {
100 | return this.Compress(inStream, inStream.Length, outStream);
101 | }
102 | }
103 |
104 | ///
105 | /// Compresses the next inLength
bytes from the input stream,
106 | /// and writes the compressed data to the given output stream.
107 | ///
108 | /// The stream to read plaintext data from.
109 | /// The length of the plaintext data.
110 | /// The stream to write the compressed data to.
111 | /// The size of the compressed stream. If -1, the file could not be compressed.
112 | public abstract int Compress(Stream instream, long inLength, Stream outstream);
113 |
114 | ///
115 | /// Gets a short string identifying this compression format.
116 | ///
117 | public abstract string ShortFormatString { get; }
118 | ///
119 | /// Gets a short description of this compression format (used in the program usage).
120 | ///
121 | public abstract string Description { get; }
122 |
123 | ///
124 | /// Gets if this format supports compressing a file.
125 | ///
126 | public abstract bool SupportsCompression { get; }
127 | ///
128 | /// Gets if this format supports decompressing a file.
129 | ///
130 | public virtual bool SupportsDecompression { get { return true; } }
131 | ///
132 | /// Gets the value that must be given on the command line in order to compress using this format.
133 | ///
134 | public abstract string CompressionFlag { get; }
135 | ///
136 | /// Parses any input specific for this format. Does nothing by default.
137 | ///
138 | /// Any arguments that may be used by the format.
139 | /// The number of input arguments consumed by this format.
140 | public virtual int ParseCompressionOptions(string[] args) { return 0; }
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Formats/Nitro/CompositeFormats.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DSDecmp.Formats.Nitro
6 | {
7 | ///
8 | /// A composite format with all formats supported natively by the GBA.
9 | ///
10 | public class CompositeGBAFormat : CompositeFormat
11 | {
12 | ///
13 | /// Creates a new instance of the format composed of all native GBA compression formats.
14 | ///
15 | public CompositeGBAFormat()
16 | : base(new Huffman4(), new Huffman8(), new LZ10()) { }
17 |
18 | ///
19 | /// Gets a short string identifying this compression format.
20 | ///
21 | public override string ShortFormatString
22 | {
23 | get { return "GBA"; }
24 | }
25 |
26 | ///
27 | /// Gets a short description of this compression format (used in the program usage).
28 | ///
29 | public override string Description
30 | {
31 | get { return "All formats natively supported by the GBA."; }
32 | }
33 |
34 | ///
35 | /// Gets if this format supports compressing a file.
36 | ///
37 | public override bool SupportsCompression
38 | {
39 | get { return true; }
40 | }
41 |
42 | ///
43 | /// Gets the value that must be given on the command line in order to compress using this format.
44 | ///
45 | public override string CompressionFlag
46 | {
47 | get { return "gba*"; }
48 | }
49 | }
50 |
51 | ///
52 | /// A composite format with all formats supported natively by the NDS (but not LZ-Overlay)
53 | ///
54 | public class CompositeNDSFormat : CompositeFormat
55 | {
56 | ///
57 | /// Creates a new instance of the format composed of all native NDS compression formats.
58 | ///
59 | public CompositeNDSFormat()
60 | : base(new Huffman4(), new Huffman8(), new LZ10(), new LZ11()) { }
61 |
62 | ///
63 | /// Gets a short string identifying this compression format.
64 | ///
65 | public override string ShortFormatString
66 | {
67 | get { return "NDS"; }
68 | }
69 |
70 | ///
71 | /// Gets a short description of this compression format (used in the program usage).
72 | ///
73 | public override string Description
74 | {
75 | get { return "All formats natively supported by the NDS."; }
76 | }
77 |
78 | ///
79 | /// Gets if this format supports compressing a file.
80 | ///
81 | public override bool SupportsCompression
82 | {
83 | get { return true; }
84 | }
85 |
86 | ///
87 | /// Gets the value that must be given on the command line in order to compress using this format.
88 | ///
89 | public override string CompressionFlag
90 | {
91 | get { return "nds*"; }
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Formats/Nitro/LZ10.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.IO;
5 | namespace DSDecmp.Formats.Nitro
6 | {
7 | ///
8 | /// Compressor and decompressor for the LZ-0x10 format used in many of the games for the
9 | /// newer Nintendo consoles and handhelds.
10 | ///
11 | public sealed class LZ10 : NitroCFormat
12 | {
13 | ///
14 | /// Gets a short string identifying this compression format.
15 | ///
16 | public override string ShortFormatString
17 | {
18 | get { return "LZ-10"; }
19 | }
20 |
21 | ///
22 | /// Gets a short description of this compression format (used in the program usage).
23 | ///
24 | public override string Description
25 | {
26 | get { return "Common LZ-type compression used in many post-GBC Nintendo games."; }
27 | }
28 |
29 | ///
30 | /// Gets the value that must be given on the command line in order to compress using this format.
31 | ///
32 | public override string CompressionFlag
33 | {
34 | get { return "lz10"; }
35 | }
36 |
37 | ///
38 | /// Gets if this format supports compressing a file.
39 | ///
40 | public override bool SupportsCompression
41 | {
42 | get { return true; }
43 | }
44 |
45 | private static bool lookAhead = false;
46 | ///
47 | /// Sets the flag that determines if 'look-ahead'/DP should be used when compressing
48 | /// with the LZ-10 format. The default is false, which is what is used in the original
49 | /// implementation.
50 | ///
51 | public static bool LookAhead
52 | {
53 | set { lookAhead = value; }
54 | }
55 |
56 | ///
57 | /// Creates a new instance of the LZ-10 compression format.
58 | ///
59 | public LZ10() : base(0x10) { }
60 |
61 | ///
62 | /// Checks if the given aguments have the '-opt' option, which makes this format
63 | /// compress using (near-)optimal compression instead of the original compression algorithm.
64 | ///
65 | public override int ParseCompressionOptions(string[] args)
66 | {
67 | if (args.Length > 0)
68 | if (args[0] == "-opt")
69 | {
70 | LookAhead = true;
71 | return 1;
72 | }
73 | return 0;
74 | }
75 |
76 | #region 'Original' Decompression method
77 | ///
78 | /// Decompress a stream that is compressed in the LZ-10 format.
79 | ///
80 | /// The compressed stream.
81 | /// The length of the input stream.
82 | /// The output stream, where the decompressed data is written to.
83 | public override long Decompress(Stream instream, long inLength,
84 | Stream outstream)
85 | {
86 | #region format definition from GBATEK/NDSTEK
87 | /* Data header (32bit)
88 | Bit 0-3 Reserved
89 | Bit 4-7 Compressed type (must be 1 for LZ77)
90 | Bit 8-31 Size of decompressed data
91 | Repeat below. Each Flag Byte followed by eight Blocks.
92 | Flag data (8bit)
93 | Bit 0-7 Type Flags for next 8 Blocks, MSB first
94 | Block Type 0 - Uncompressed - Copy 1 Byte from Source to Dest
95 | Bit 0-7 One data byte to be copied to dest
96 | Block Type 1 - Compressed - Copy N+3 Bytes from Dest-Disp-1 to Dest
97 | Bit 0-3 Disp MSBs
98 | Bit 4-7 Number of bytes to copy (minus 3)
99 | Bit 8-15 Disp LSBs
100 | */
101 | #endregion
102 |
103 | long readBytes = 0;
104 |
105 | byte type = (byte)instream.ReadByte();
106 | if (type != base.magicByte)
107 | throw new InvalidDataException("The provided stream is not a valid LZ-0x10 "
108 | + "compressed stream (invalid type 0x" + type.ToString("X") + ")");
109 | byte[] sizeBytes = new byte[3];
110 | instream.Read(sizeBytes, 0, 3);
111 | int decompressedSize = IOUtils.ToNDSu24(sizeBytes, 0);
112 | readBytes += 4;
113 | if (decompressedSize == 0)
114 | {
115 | sizeBytes = new byte[4];
116 | instream.Read(sizeBytes, 0, 4);
117 | decompressedSize = IOUtils.ToNDSs32(sizeBytes, 0);
118 | readBytes += 4;
119 | }
120 |
121 | // the maximum 'DISP-1' is 0xFFF.
122 | int bufferLength = 0x1000;
123 | byte[] buffer = new byte[bufferLength];
124 | int bufferOffset = 0;
125 |
126 |
127 | int currentOutSize = 0;
128 | int flags = 0, mask = 1;
129 | while (currentOutSize < decompressedSize)
130 | {
131 | // (throws when requested new flags byte is not available)
132 | #region Update the mask. If all flag bits have been read, get a new set.
133 | // the current mask is the mask used in the previous run. So if it masks the
134 | // last flag bit, get a new flags byte.
135 | if (mask == 1)
136 | {
137 | if (readBytes >= inLength)
138 | throw new NotEnoughDataException(currentOutSize, decompressedSize);
139 | flags = instream.ReadByte(); readBytes++;
140 | if (flags < 0)
141 | throw new StreamTooShortException();
142 | mask = 0x80;
143 | }
144 | else
145 | {
146 | mask >>= 1;
147 | }
148 | #endregion
149 |
150 | // bit = 1 <=> compressed.
151 | if ((flags & mask) > 0)
152 | {
153 | // (throws when < 2 bytes are available)
154 | #region Get length and displacement('disp') values from next 2 bytes
155 | // there are < 2 bytes available when the end is at most 1 byte away
156 | if (readBytes + 1 >= inLength)
157 | {
158 | // make sure the stream is at the end
159 | if (readBytes < inLength)
160 | {
161 | instream.ReadByte(); readBytes++;
162 | }
163 | throw new NotEnoughDataException(currentOutSize, decompressedSize);
164 | }
165 | int byte1 = instream.ReadByte(); readBytes++;
166 | int byte2 = instream.ReadByte(); readBytes++;
167 | if (byte2 < 0)
168 | throw new StreamTooShortException();
169 |
170 | // the number of bytes to copy
171 | int length = byte1 >> 4;
172 | length += 3;
173 |
174 | // from where the bytes should be copied (relatively)
175 | int disp = ((byte1 & 0x0F) << 8) | byte2;
176 | disp += 1;
177 |
178 | if (disp > currentOutSize)
179 | throw new InvalidDataException("Cannot go back more than already written. "
180 | + "DISP = 0x" + disp.ToString("X") + ", #written bytes = 0x" + currentOutSize.ToString("X")
181 | + " at 0x" + (instream.Position - 2).ToString("X"));
182 | #endregion
183 |
184 | int bufIdx = bufferOffset + bufferLength - disp;
185 | for (int i = 0; i < length; i++)
186 | {
187 | byte next = buffer[bufIdx % bufferLength];
188 | bufIdx++;
189 | outstream.WriteByte(next);
190 | buffer[bufferOffset] = next;
191 | bufferOffset = (bufferOffset + 1) % bufferLength;
192 | }
193 | currentOutSize += length;
194 | }
195 | else
196 | {
197 | if (readBytes >= inLength)
198 | throw new NotEnoughDataException(currentOutSize, decompressedSize);
199 | int next = instream.ReadByte(); readBytes++;
200 | if (next < 0)
201 | throw new StreamTooShortException();
202 |
203 | currentOutSize++;
204 | outstream.WriteByte((byte)next);
205 | buffer[bufferOffset] = (byte)next;
206 | bufferOffset = (bufferOffset + 1) % bufferLength;
207 | }
208 | outstream.Flush();
209 | }
210 |
211 | if (readBytes < inLength)
212 | {
213 | // the input may be 4-byte aligned.
214 | if ((readBytes ^ (readBytes & 3)) + 4 < inLength)
215 | throw new TooMuchInputException(readBytes, inLength);
216 | }
217 |
218 | return decompressedSize;
219 | }
220 | #endregion
221 |
222 | #region Original Compress method
223 | ///
224 | /// Compresses the input using the 'original', unoptimized compression algorithm.
225 | /// This algorithm should yield files that are the same as those found in the games.
226 | /// (delegates to the optimized method if LookAhead is set)
227 | ///
228 | public unsafe override int Compress(Stream instream, long inLength, Stream outstream)
229 | {
230 | // make sure the decompressed size fits in 3 bytes.
231 | // There should be room for four bytes, however I'm not 100% sure if that can be used
232 | // in every game, as it may not be a built-in function.
233 | if (inLength > 0xFFFFFF)
234 | throw new InputTooLargeException();
235 |
236 | // use the other method if lookahead is enabled
237 | if (lookAhead)
238 | {
239 | return CompressWithLA(instream, inLength, outstream);
240 | }
241 |
242 | // save the input data in an array to prevent having to go back and forth in a file
243 | byte[] indata = new byte[inLength];
244 | int numReadBytes = instream.Read(indata, 0, (int)inLength);
245 | if (numReadBytes != inLength)
246 | throw new StreamTooShortException();
247 |
248 | // write the compression header first
249 | outstream.WriteByte(this.magicByte);
250 | outstream.WriteByte((byte)(inLength & 0xFF));
251 | outstream.WriteByte((byte)((inLength >> 8) & 0xFF));
252 | outstream.WriteByte((byte)((inLength >> 16) & 0xFF));
253 |
254 | int compressedLength = 4;
255 |
256 | fixed (byte* instart = &indata[0])
257 | {
258 | // we do need to buffer the output, as the first byte indicates which blocks are compressed.
259 | // this version does not use a look-ahead, so we do not need to buffer more than 8 blocks at a time.
260 | byte[] outbuffer = new byte[8 * 2 + 1];
261 | outbuffer[0] = 0;
262 | int bufferlength = 1, bufferedBlocks = 0;
263 | int readBytes = 0;
264 | while (readBytes < inLength)
265 | {
266 | #region If 8 blocks are bufferd, write them and reset the buffer
267 | // we can only buffer 8 blocks at a time.
268 | if (bufferedBlocks == 8)
269 | {
270 | outstream.Write(outbuffer, 0, bufferlength);
271 | compressedLength += bufferlength;
272 | // reset the buffer
273 | outbuffer[0] = 0;
274 | bufferlength = 1;
275 | bufferedBlocks = 0;
276 | }
277 | #endregion
278 |
279 | // determine if we're dealing with a compressed or raw block.
280 | // it is a compressed block when the next 3 or more bytes can be copied from
281 | // somewhere in the set of already compressed bytes.
282 | int disp;
283 | int oldLength = Math.Min(readBytes, 0x1000);
284 | int length = LZUtil.GetOccurrenceLength(instart + readBytes, (int)Math.Min(inLength - readBytes, 0x12),
285 | instart + readBytes - oldLength, oldLength, out disp);
286 |
287 | // length not 3 or more? next byte is raw data
288 | if (length < 3)
289 | {
290 | outbuffer[bufferlength++] = *(instart + (readBytes++));
291 | }
292 | else
293 | {
294 | // 3 or more bytes can be copied? next (length) bytes will be compressed into 2 bytes
295 | readBytes += length;
296 |
297 | // mark the next block as compressed
298 | outbuffer[0] |= (byte)(1 << (7 - bufferedBlocks));
299 |
300 | outbuffer[bufferlength] = (byte)(((length - 3) << 4) & 0xF0);
301 | outbuffer[bufferlength] |= (byte)(((disp - 1) >> 8) & 0x0F);
302 | bufferlength++;
303 | outbuffer[bufferlength] = (byte)((disp - 1) & 0xFF);
304 | bufferlength++;
305 | }
306 | bufferedBlocks++;
307 | }
308 |
309 | // copy the remaining blocks to the output
310 | if (bufferedBlocks > 0)
311 | {
312 | outstream.Write(outbuffer, 0, bufferlength);
313 | compressedLength += bufferlength;
314 | /*/ make the compressed file 4-byte aligned.
315 | while ((compressedLength % 4) != 0)
316 | {
317 | outstream.WriteByte(0);
318 | compressedLength++;
319 | }/**/
320 | }
321 | }
322 |
323 | return compressedLength;
324 | }
325 | #endregion
326 |
327 | #region Dynamic Programming compression method
328 | ///
329 | /// Variation of the original compression method, making use of Dynamic Programming to 'look ahead'
330 | /// and determine the optimal 'length' values for the compressed blocks. Is not 100% optimal,
331 | /// as the flag-bytes are not taken into account.
332 | ///
333 | private unsafe int CompressWithLA(Stream instream, long inLength, Stream outstream)
334 | {
335 | // save the input data in an array to prevent having to go back and forth in a file
336 | byte[] indata = new byte[inLength];
337 | int numReadBytes = instream.Read(indata, 0, (int)inLength);
338 | if (numReadBytes != inLength)
339 | throw new StreamTooShortException();
340 |
341 | // write the compression header first
342 | outstream.WriteByte(this.magicByte);
343 | outstream.WriteByte((byte)(inLength & 0xFF));
344 | outstream.WriteByte((byte)((inLength >> 8) & 0xFF));
345 | outstream.WriteByte((byte)((inLength >> 16) & 0xFF));
346 |
347 | int compressedLength = 4;
348 |
349 | fixed (byte* instart = &indata[0])
350 | {
351 | // we do need to buffer the output, as the first byte indicates which blocks are compressed.
352 | // this version does not use a look-ahead, so we do not need to buffer more than 8 blocks at a time.
353 | byte[] outbuffer = new byte[8 * 2 + 1];
354 | outbuffer[0] = 0;
355 | int bufferlength = 1, bufferedBlocks = 0;
356 | int readBytes = 0;
357 |
358 | // get the optimal choices for len and disp
359 | int[] lengths, disps;
360 | this.GetOptimalCompressionLengths(instart, indata.Length, out lengths, out disps);
361 | while (readBytes < inLength)
362 | {
363 | // we can only buffer 8 blocks at a time.
364 | if (bufferedBlocks == 8)
365 | {
366 | outstream.Write(outbuffer, 0, bufferlength);
367 | compressedLength += bufferlength;
368 | // reset the buffer
369 | outbuffer[0] = 0;
370 | bufferlength = 1;
371 | bufferedBlocks = 0;
372 | }
373 |
374 |
375 | if (lengths[readBytes] == 1)
376 | {
377 | outbuffer[bufferlength++] = *(instart + (readBytes++));
378 | }
379 | else
380 | {
381 | // mark the next block as compressed
382 | outbuffer[0] |= (byte)(1 << (7 - bufferedBlocks));
383 |
384 | outbuffer[bufferlength] = (byte)(((lengths[readBytes] - 3) << 4) & 0xF0);
385 | outbuffer[bufferlength] |= (byte)(((disps[readBytes] - 1) >> 8) & 0x0F);
386 | bufferlength++;
387 | outbuffer[bufferlength] = (byte)((disps[readBytes] - 1) & 0xFF);
388 | bufferlength++;
389 |
390 | readBytes += lengths[readBytes];
391 | }
392 |
393 |
394 | bufferedBlocks++;
395 | }
396 |
397 | // copy the remaining blocks to the output
398 | if (bufferedBlocks > 0)
399 | {
400 | outstream.Write(outbuffer, 0, bufferlength);
401 | compressedLength += bufferlength;
402 | /*/ make the compressed file 4-byte aligned.
403 | while ((compressedLength % 4) != 0)
404 | {
405 | outstream.WriteByte(0);
406 | compressedLength++;
407 | }/**/
408 | }
409 | }
410 |
411 | return compressedLength;
412 | }
413 | #endregion
414 |
415 | #region DP compression helper method; GetOptimalCompressionLengths
416 | ///
417 | /// Gets the optimal compression lengths for each start of a compressed block using Dynamic Programming.
418 | /// This takes O(n^2) time.
419 | ///
420 | /// The data to compress.
421 | /// The length of the data to compress.
422 | /// The optimal 'length' of the compressed blocks. For each byte in the input data,
423 | /// this value is the optimal 'length' value. If it is 1, the block should not be compressed.
424 | /// The 'disp' values of the compressed blocks. May be 0, in which case the
425 | /// corresponding length will never be anything other than 1.
426 | private unsafe void GetOptimalCompressionLengths(byte* indata, int inLength, out int[] lengths, out int[] disps)
427 | {
428 | lengths = new int[inLength];
429 | disps = new int[inLength];
430 | int[] minLengths = new int[inLength];
431 |
432 | for (int i = inLength - 1; i >= 0; i--)
433 | {
434 | // first get the compression length when the next byte is not compressed
435 | minLengths[i] = int.MaxValue;
436 | lengths[i] = 1;
437 | if (i + 1 >= inLength)
438 | minLengths[i] = 1;
439 | else
440 | minLengths[i] = 1 + minLengths[i + 1];
441 | // then the optimal compressed length
442 | int oldLength = Math.Min(0x1000, i);
443 | // get the appropriate disp while at it. Takes at most O(n) time if oldLength is considered O(n)
444 | // be sure to bound the input length with 0x12, as that's the maximum length for LZ-10 compressed blocks.
445 | int maxLen = LZUtil.GetOccurrenceLength(indata + i, Math.Min(inLength - i, 0x12),
446 | indata + i - oldLength, oldLength, out disps[i]);
447 | if (disps[i] > i)
448 | throw new Exception("disp is too large");
449 | for (int j = 3; j <= maxLen; j++)
450 | {
451 | int newCompLen;
452 | if (i + j >= inLength)
453 | newCompLen = 2;
454 | else
455 | newCompLen = 2 + minLengths[i + j];
456 | if (newCompLen < minLengths[i])
457 | {
458 | lengths[i] = j;
459 | minLengths[i] = newCompLen;
460 | }
461 | }
462 | }
463 |
464 | // we could optimize this further to also optimize it with regard to the flag-bytes, but that would require 8 times
465 | // more space and time (one for each position in the block) for only a potentially tiny increase in compression ratio.
466 | }
467 | #endregion
468 | }
469 | }
470 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Formats/Nitro/NitroCFormat.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DSDecmp.Formats.Nitro
6 | {
7 | ///
8 | /// Base class for Nitro-based decompressors. Uses the 1-byte magic and 3-byte decompression
9 | /// size format.
10 | ///
11 | public abstract class NitroCFormat : CompressionFormat
12 | {
13 | ///
14 | /// If true, Nitro Decompressors will not decompress files that have a decompressed
15 | /// size (plaintext size) larger than MaxPlaintextSize.
16 | ///
17 | public static bool SkipLargePlaintexts = true;
18 | ///
19 | /// The maximum allowed size of the decompressed file (plaintext size) allowed for Nitro
20 | /// Decompressors. Only used when SkipLargePlaintexts = true.
21 | /// If the expected plaintext size is larger that this, the 'Supports' method will partially
22 | /// decompress the data to check if the file is OK.
23 | ///
24 | public static int MaxPlaintextSize = 0x180000;
25 |
26 | ///
27 | /// The first byte of every file compressed with the format for this particular
28 | /// Nitro Dcompressor instance.
29 | ///
30 | protected byte magicByte;
31 |
32 | ///
33 | /// Creates a new instance of the Nitro Compression Format base class.
34 | ///
35 | /// The expected first byte of the file for this format.
36 | protected NitroCFormat(byte magicByte)
37 | {
38 | this.magicByte = magicByte;
39 | }
40 |
41 | ///
42 | /// Checks if the first four (or eight) bytes match the format used in nitro compression formats.
43 | ///
44 | public override bool Supports(System.IO.Stream stream, long inLength)
45 | {
46 | long startPosition = stream.Position;
47 | try
48 | {
49 | int firstByte = stream.ReadByte();
50 | if (firstByte != this.magicByte)
51 | return false;
52 | // no need to read the size info as well if it's used anyway.
53 | if (!SkipLargePlaintexts)
54 | return true;
55 | byte[] sizeBytes = new byte[3];
56 | stream.Read(sizeBytes, 0, 3);
57 | int outSize = IOUtils.ToNDSu24(sizeBytes, 0);
58 | if (outSize == 0)
59 | {
60 | sizeBytes = new byte[4];
61 | stream.Read(sizeBytes, 0, 4);
62 | outSize = (int)IOUtils.ToNDSu32(sizeBytes, 0);
63 | }
64 | if (outSize <= MaxPlaintextSize)
65 | return true;
66 |
67 | try
68 | {
69 | stream.Position = startPosition;
70 | this.Decompress(stream, Math.Min(Math.Min(inLength, 0x80000), MaxPlaintextSize), new System.IO.MemoryStream());
71 | // we expect a NotEnoughDataException, since we're giving the decompressor only part of the file.
72 | return false;
73 | }
74 | catch (NotEnoughDataException)
75 | {
76 | return true;
77 | }
78 | catch (Exception)
79 | {
80 | return false;
81 | }
82 | }
83 | finally
84 | {
85 | stream.Position = startPosition;
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Formats/Nitro/NullCompression.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.IO;
5 |
6 | namespace DSDecmp.Formats.Nitro
7 | {
8 | ///
9 | /// 'Compression' format without any compression whatsoever.
10 | /// Compression using this format will only prepend 0x00 plus the original file size to the file.
11 | ///
12 | public class NullCompression : NitroCFormat
13 | {
14 | ///
15 | /// Gets a short string identifying this compression format.
16 | ///
17 | public override string ShortFormatString
18 | {
19 | get { return "NULL"; }
20 | }
21 |
22 | ///
23 | /// Gets a short description of this compression format (used in the program usage).
24 | ///
25 | public override string Description
26 | {
27 | get { return "NULL-'compression' format. Prefixes file with 0x00 and filesize."; }
28 | }
29 |
30 | ///
31 | /// Gets if this format supports compressing a file.
32 | ///
33 | public override bool SupportsCompression
34 | {
35 | get { return true; }
36 | }
37 |
38 | ///
39 | /// Gets the value that must be given on the command line in order to compress using this format.
40 | ///
41 | public override string CompressionFlag
42 | {
43 | get { return "null"; }
44 | }
45 |
46 | ///
47 | /// Creates a new instance of the NULL-compression format.
48 | ///
49 | public NullCompression()
50 | : base(0) { }
51 |
52 | ///
53 | /// Checks if the given stream is (or could be) 'compressed' using the NULL compression format.
54 | ///
55 | public override bool Supports(System.IO.Stream stream, long inLength)
56 | {
57 | long startPosition = stream.Position;
58 | try
59 | {
60 | int firstByte = stream.ReadByte();
61 | if (firstByte != 0)
62 | return false;
63 | byte[] sizeBytes = new byte[3];
64 | stream.Read(sizeBytes, 0, 3);
65 | int outSize = IOUtils.ToNDSu24(sizeBytes, 0);
66 | int headerSize = 4;
67 | if (outSize == 0)
68 | {
69 | sizeBytes = new byte[4];
70 | stream.Read(sizeBytes, 0, 4);
71 | outSize = (int)IOUtils.ToNDSu32(sizeBytes, 0);
72 | headerSize = 8;
73 | }
74 | return outSize == inLength - headerSize;
75 | }
76 | finally
77 | {
78 | stream.Position = startPosition;
79 | }
80 | }
81 |
82 | ///
83 | /// 'Decompresses' the given input stream using the NULL format.
84 | ///
85 | public override long Decompress(System.IO.Stream instream, long inLength, System.IO.Stream outstream)
86 | {
87 | long readBytes = 0;
88 |
89 | byte type = (byte)instream.ReadByte();
90 | if (type != base.magicByte)
91 | throw new InvalidDataException("The provided stream is not a valid Null "
92 | + "compressed stream (invalid type 0x" + type.ToString("X") + ")");
93 | byte[] sizeBytes = new byte[3];
94 | instream.Read(sizeBytes, 0, 3);
95 | int decompressedSize = IOUtils.ToNDSu24(sizeBytes, 0);
96 | readBytes += 4;
97 | if (decompressedSize == 0)
98 | {
99 | sizeBytes = new byte[4];
100 | instream.Read(sizeBytes, 0, 4);
101 | decompressedSize = IOUtils.ToNDSs32(sizeBytes, 0);
102 | readBytes += 4;
103 | }
104 |
105 | byte[] data = new byte[decompressedSize];
106 | int readLength = instream.Read(data, 0, decompressedSize);
107 | outstream.Write(data, 0, readLength);
108 | if (readLength < decompressedSize)
109 | throw new NotEnoughDataException(readLength, decompressedSize);
110 |
111 | return readLength;
112 | }
113 |
114 | ///
115 | /// 'Compresses' the given input stream using the NULL format.
116 | ///
117 | public override int Compress(System.IO.Stream instream, long inLength, System.IO.Stream outstream)
118 | {
119 | if (inLength > 0xFFFFFFFF)
120 | throw new InputTooLargeException();
121 |
122 | long outSize = 4;
123 |
124 | outstream.WriteByte(0);
125 | if (inLength <= 0xFFFFFF)
126 | {
127 | outstream.WriteByte((byte)(inLength & 0xFF));
128 | outstream.WriteByte((byte)((inLength >> 8) & 0xFF));
129 | outstream.WriteByte((byte)((inLength >> 16) & 0xFF));
130 | }
131 | else
132 | {
133 | outstream.WriteByte(0);
134 | outstream.WriteByte(0);
135 | outstream.WriteByte(0);
136 | outstream.WriteByte((byte)(inLength & 0xFF));
137 | outstream.WriteByte((byte)((inLength >> 8) & 0xFF));
138 | outstream.WriteByte((byte)((inLength >> 16) & 0xFF));
139 | outstream.WriteByte((byte)((inLength >> 24) & 0xFF));
140 | outSize = 8;
141 | }
142 |
143 | byte[] buffer = new byte[Math.Min(int.MaxValue, inLength)];
144 | long remaining = inLength;
145 | while (remaining > 0)
146 | {
147 | int readLength = instream.Read(buffer, 0, (int)Math.Min(buffer.Length, remaining));
148 | if (readLength == 0)
149 | throw new StreamTooShortException();
150 | remaining -= readLength;
151 | outstream.Write(buffer, 0, readLength);
152 | outSize += readLength;
153 | }
154 |
155 | return (int)Math.Min(int.MaxValue, outSize);
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Formats/Nitro/RLE.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.IO;
5 |
6 | namespace DSDecmp.Formats.Nitro
7 | {
8 | ///
9 | /// Compressor and decompressor for the RLE format used in several of the games for the
10 | /// newer Nintendo consoles and handhelds.
11 | ///
12 | public sealed class RLE : NitroCFormat
13 | {
14 | ///
15 | /// Gets a short string identifying this compression format.
16 | ///
17 | public override string ShortFormatString
18 | {
19 | get { return "RLE"; }
20 | }
21 |
22 | ///
23 | /// Gets a short description of this compression format (used in the program usage).
24 | ///
25 | public override string Description
26 | {
27 | get { return "Run-Length Encoding used in some modern Nintendo games."; }
28 | }
29 |
30 | ///
31 | /// Gets the value that must be given on the command line in order to compress using this format.
32 | ///
33 | public override string CompressionFlag
34 | {
35 | get { return "rle"; }
36 | }
37 |
38 | ///
39 | /// Gets if this format supports compressing a file.
40 | ///
41 | public override bool SupportsCompression
42 | {
43 | get { return true; }
44 | }
45 |
46 | ///
47 | /// Creates a new instance of the RLE compression format.
48 | ///
49 | public RLE() : base(0x30) { }
50 |
51 | #region Method: Decompress
52 | ///
53 | /// Decompresses the input using the RLE compression scheme.
54 | ///
55 | public override long Decompress(Stream instream, long inLength, Stream outstream)
56 | {
57 | /*
58 | Data header (32bit)
59 | Bit 0-3 Reserved
60 | Bit 4-7 Compressed type (must be 3 for run-length)
61 | Bit 8-31 Size of decompressed data
62 | Repeat below. Each Flag Byte followed by one or more Data Bytes.
63 | Flag data (8bit)
64 | Bit 0-6 Expanded Data Length (uncompressed N-1, compressed N-3)
65 | Bit 7 Flag (0=uncompressed, 1=compressed)
66 | Data Byte(s) - N uncompressed bytes, or 1 byte repeated N times
67 | */
68 |
69 | long readBytes = 0;
70 |
71 | byte type = (byte)instream.ReadByte();
72 | if (type != base.magicByte)
73 | throw new InvalidDataException("The provided stream is not a valid RLE "
74 | + "compressed stream (invalid type 0x" + type.ToString("X") + ")");
75 | byte[] sizeBytes = new byte[3];
76 | instream.Read(sizeBytes, 0, 3);
77 | int decompressedSize = IOUtils.ToNDSu24(sizeBytes, 0);
78 | readBytes += 4;
79 | if (decompressedSize == 0)
80 | {
81 | sizeBytes = new byte[4];
82 | instream.Read(sizeBytes, 0, 4);
83 | decompressedSize = IOUtils.ToNDSs32(sizeBytes, 0);
84 | readBytes += 4;
85 | }
86 |
87 |
88 | int currentOutSize = 0;
89 | while (currentOutSize < decompressedSize)
90 | {
91 | #region (try to) get the flag byte with the length data and compressed flag
92 |
93 | if (readBytes >= inLength)
94 | throw new NotEnoughDataException(currentOutSize, decompressedSize);
95 | int flag = instream.ReadByte(); readBytes++;
96 | if (flag < 0)
97 | throw new StreamTooShortException();
98 |
99 | bool compressed = (flag & 0x80) > 0;
100 | int length = flag & 0x7F;
101 |
102 | if (compressed)
103 | length += 3;
104 | else
105 | length += 1;
106 |
107 | #endregion
108 |
109 | if (compressed)
110 | {
111 | #region compressed: write the next byte (length) times.
112 |
113 | if (readBytes >= inLength)
114 | throw new NotEnoughDataException(currentOutSize, decompressedSize);
115 | int data = instream.ReadByte(); readBytes++;
116 | if (data < 0)
117 | throw new StreamTooShortException();
118 |
119 | if (currentOutSize + length > decompressedSize)
120 | throw new InvalidDataException("The given stream is not a valid RLE stream; the "
121 | + "output length does not match the provided plaintext length.");
122 | byte bdata = (byte)data;
123 | for (int i = 0; i < length; i++)
124 | {
125 | // Stream.Write(byte[], offset, len) may also work, but only if it is a circular buffer
126 | outstream.WriteByte(bdata);
127 | currentOutSize++;
128 | }
129 |
130 | #endregion
131 | }
132 | else
133 | {
134 | #region uncompressed: copy the next (length) bytes.
135 |
136 | int tryReadLength = length;
137 | // limit the amount of bytes read by the indicated number of bytes available
138 | if (readBytes + length > inLength)
139 | tryReadLength = (int)(inLength - readBytes);
140 |
141 | byte[] data = new byte[length];
142 | int readLength = instream.Read(data, 0, (int)tryReadLength);
143 | readBytes += readLength;
144 | outstream.Write(data, 0, readLength);
145 | currentOutSize += readLength;
146 |
147 | // if the attempted number of bytes read is less than the desired number, the given input
148 | // length is too small (or there is not enough data in the stream)
149 | if (tryReadLength < length)
150 | throw new NotEnoughDataException(currentOutSize, decompressedSize);
151 | // if the actual number of read bytes is even less, it means that the end of the stream has
152 | // bee reached, thus the given input length is larger than the actual length of the input
153 | if (readLength < length)
154 | throw new StreamTooShortException();
155 |
156 | #endregion
157 | }
158 | }
159 |
160 | if (readBytes < inLength)
161 | {
162 | // the input may be 4-byte aligned.
163 | if ((readBytes ^ (readBytes & 3)) + 4 < inLength)
164 | throw new TooMuchInputException(readBytes, inLength);
165 | }
166 |
167 | return decompressedSize;
168 | }
169 | #endregion Decompress
170 |
171 | #region Method: Compress
172 | ///
173 | /// Compresses the input using the RLE compression scheme.
174 | ///
175 | public override int Compress(Stream instream, long inLength, Stream outstream)
176 | {
177 |
178 | if (inLength > 0xFFFFFF)
179 | throw new InputTooLargeException();
180 |
181 | List compressedData = new List();
182 |
183 | // at most 0x7F+3=130 bytes are compressed into a single block.
184 | // (and at most 0x7F+1=128 in an uncompressed block, however we need to read 2
185 | // more, since the last byte may be part of a repetition).
186 | byte[] dataBlock = new byte[130];
187 | // the length of the valid content in the current data block
188 | int currentBlockLength = 0;
189 |
190 | int readLength = 0;
191 | int nextByte;
192 | int repCount = 1;
193 | while (readLength < inLength)
194 | {
195 | bool foundRepetition = false;
196 |
197 | while (currentBlockLength < dataBlock.Length && readLength < inLength)
198 | {
199 | nextByte = instream.ReadByte();
200 | if (nextByte < 0)
201 | throw new StreamTooShortException();
202 | readLength++;
203 |
204 | dataBlock[currentBlockLength++] = (byte)nextByte;
205 | if (currentBlockLength > 1)
206 | {
207 | if (nextByte == dataBlock[currentBlockLength - 2])
208 | repCount++;
209 | else
210 | repCount = 1;
211 | }
212 |
213 | foundRepetition = repCount > 2;
214 | if (foundRepetition)
215 | break;
216 | }
217 |
218 |
219 | int numUncompToCopy = 0;
220 | if (foundRepetition)
221 | {
222 | // if a repetition was found, copy block size - 3 bytes as compressed data
223 | numUncompToCopy = currentBlockLength - 3;
224 | }
225 | else
226 | {
227 | // if no repetition was found, copy min(block size, max block size - 2) bytes as uncompressed data.
228 | numUncompToCopy = Math.Min(currentBlockLength, dataBlock.Length - 2);
229 | }
230 |
231 | #region insert uncompressed block
232 | if (numUncompToCopy > 0)
233 | {
234 | byte flag = (byte)(numUncompToCopy - 1);
235 | compressedData.Add(flag);
236 | for (int i = 0; i < numUncompToCopy; i++)
237 | compressedData.Add(dataBlock[i]);
238 | // shift some possibly remaining bytes to the start
239 | for (int i = numUncompToCopy; i < currentBlockLength; i++)
240 | dataBlock[i - numUncompToCopy] = dataBlock[i];
241 | currentBlockLength -= numUncompToCopy;
242 | }
243 | #endregion
244 |
245 | if (foundRepetition)
246 | {
247 | // if a repetition was found, continue until the first different byte
248 | // (or until the buffer is full)
249 | while (currentBlockLength < dataBlock.Length && readLength < inLength)
250 | {
251 | nextByte = instream.ReadByte();
252 | if (nextByte < 0)
253 | throw new StreamTooShortException();
254 | readLength++;
255 |
256 | dataBlock[currentBlockLength++] = (byte)nextByte;
257 |
258 | if (nextByte != dataBlock[0])
259 | break;
260 | else
261 | repCount++;
262 | }
263 |
264 | // the next repCount bytes are the same.
265 | #region insert compressed block
266 | byte flag = (byte)(0x80 | (repCount - 3));
267 | compressedData.Add(flag);
268 | compressedData.Add(dataBlock[0]);
269 | // make sure to shift the possible extra byte to the start
270 | if (repCount != currentBlockLength)
271 | dataBlock[0] = dataBlock[currentBlockLength - 1];
272 | currentBlockLength -= repCount;
273 | #endregion
274 | }
275 | }
276 |
277 | // write any reamaining bytes as uncompressed
278 | if (currentBlockLength > 0)
279 | {
280 | byte flag = (byte)(currentBlockLength - 1);
281 | compressedData.Add(flag);
282 | for (int i = 0; i < currentBlockLength; i++)
283 | compressedData.Add(dataBlock[i]);
284 | currentBlockLength = 0;
285 | }
286 |
287 | // write the RLE marker and the decompressed size
288 | outstream.WriteByte(0x30);
289 | int compLen = compressedData.Count;
290 | outstream.WriteByte((byte)(inLength & 0xFF));
291 | outstream.WriteByte((byte)((inLength >> 8) & 0xFF));
292 | outstream.WriteByte((byte)((inLength >> 16) & 0xFF));
293 |
294 | // write the compressed data
295 | outstream.Write(compressedData.ToArray(), 0, compLen);
296 |
297 | // the total compressed stream length is the compressed data length + the 4-byte header
298 | return compLen + 4;
299 | }
300 | #endregion Compress
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Utils/IOUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Reflection;
5 | using System.IO;
6 |
7 | namespace DSDecmp
8 | {
9 | ///
10 | /// Class for I/O-related utility methods.
11 | ///
12 | public static class IOUtils
13 | {
14 |
15 | #region byte[] <-> (u)int
16 | ///
17 | /// Returns a 4-byte unsigned integer as used on the NDS converted from four bytes
18 | /// at a specified position in a byte array.
19 | ///
20 | /// The source of the data.
21 | /// The location of the data in the source.
22 | /// The indicated 4 bytes converted to uint
23 | public static uint ToNDSu32(byte[] buffer, int offset)
24 | {
25 | return (uint)(buffer[offset]
26 | | (buffer[offset + 1] << 8)
27 | | (buffer[offset + 2] << 16)
28 | | (buffer[offset + 3] << 24));
29 | }
30 |
31 | ///
32 | /// Returns a 4-byte signed integer as used on the NDS converted from four bytes
33 | /// at a specified position in a byte array.
34 | ///
35 | /// The source of the data.
36 | /// The location of the data in the source.
37 | /// The indicated 4 bytes converted to int
38 | public static int ToNDSs32(byte[] buffer, int offset)
39 | {
40 | return (int)(buffer[offset]
41 | | (buffer[offset + 1] << 8)
42 | | (buffer[offset + 2] << 16)
43 | | (buffer[offset + 3] << 24));
44 | }
45 |
46 | ///
47 | /// Converts a u32 value into a sequence of bytes that would make ToNDSu32 return
48 | /// the given input value.
49 | ///
50 | public static byte[] FromNDSu32(uint value)
51 | {
52 | return new byte[] {
53 | (byte)(value & 0xFF),
54 | (byte)((value >> 8) & 0xFF),
55 | (byte)((value >> 16) & 0xFF),
56 | (byte)((value >> 24) & 0xFF)
57 | };
58 | }
59 |
60 | ///
61 | /// Returns a 3-byte integer as used in the built-in compression
62 | /// formats in the DS, convrted from three bytes at a specified position in a byte array,
63 | ///
64 | /// The source of the data.
65 | /// The location of the data in the source.
66 | /// The indicated 3 bytes converted to an integer.
67 | public static int ToNDSu24(byte[] buffer, int offset)
68 | {
69 | return (int)(buffer[offset]
70 | | (buffer[offset + 1] << 8)
71 | | (buffer[offset + 2] << 16));
72 | }
73 | #endregion
74 |
75 | #region Plugin loading
76 | ///
77 | /// (Attempts to) load compression formats from the given file.
78 | ///
79 | /// The dll file to load.
80 | /// If formats without an empty contrsuctor should get a print.
81 | /// A list with an instance of all compression formats found in the given dll file.
82 | /// If the given file does not exist.
83 | /// If the file could not be loaded.
84 | /// If the file is not a valid assembly, or the loaded
85 | /// assembly is compiled with a higher version of .NET.
86 | internal static IEnumerable LoadCompressionPlugin(string file, bool printFailures = false)
87 | {
88 | if (file == null)
89 | throw new FileNotFoundException("A null-path cannot be loaded.");
90 | List newFormats = new List();
91 |
92 | string fullPath = Path.GetFullPath(file);
93 |
94 | Assembly dll = Assembly.LoadFile(fullPath);
95 | foreach (Type dllType in dll.GetTypes())
96 | {
97 | if (dllType.IsSubclassOf(typeof(CompressionFormat))
98 | && !dllType.IsAbstract)
99 | {
100 | try
101 | {
102 | newFormats.Add(Activator.CreateInstance(dllType) as CompressionFormat);
103 | }
104 | catch (MissingMethodException)
105 | {
106 | if (printFailures)
107 | Console.WriteLine(dllType + " is a compression format, but does not have a parameterless constructor. Format cannot be loaded from " + fullPath + ".");
108 | }
109 | }
110 | }
111 |
112 | return newFormats;
113 | }
114 |
115 | ///
116 | /// Loads all compression formats found in the given folder.
117 | ///
118 | /// The folder to load plugins from.
119 | /// A list with an instance of all compression formats found in the given folder.
120 | internal static IEnumerable LoadCompressionPlugins(string folder)
121 | {
122 | List formats = new List();
123 |
124 | foreach (string file in Directory.GetFiles(folder))
125 | {
126 | try
127 | {
128 | formats.AddRange(LoadCompressionPlugin(file, false));
129 | }
130 | catch (Exception) { }
131 | }
132 |
133 | return formats;
134 | }
135 | #endregion
136 |
137 | ///
138 | /// Gets the full path to the parent directory of the given path.
139 | ///
140 | /// The path to get the parent directory path of.
141 | /// The full path to the parent directory of teh given path.
142 | public static string GetParent(string path)
143 | {
144 | return Directory.GetParent(path).FullName;
145 | }
146 | }
147 | }
148 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Utils/LZUtil.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DSDecmp
6 | {
7 | ///
8 | /// Utility class for compression using LZ-like compression schemes.
9 | ///
10 | public static class LZUtil
11 | {
12 | ///
13 | /// Determine the maximum size of a LZ-compressed block starting at newPtr, using the already compressed data
14 | /// starting at oldPtr. Takes O(inLength * oldLength) = O(n^2) time.
15 | ///
16 | /// The start of the data that needs to be compressed.
17 | /// The number of bytes that still need to be compressed.
18 | /// (or: the maximum number of bytes that _may_ be compressed into one block)
19 | /// The start of the raw file.
20 | /// The number of bytes already compressed.
21 | /// The offset of the start of the longest block to refer to.
22 | /// The minimum allowed value for 'disp'.
23 | /// The length of the longest sequence of bytes that can be copied from the already decompressed data.
24 | public static unsafe int GetOccurrenceLength(byte* newPtr, int newLength, byte* oldPtr, int oldLength, out int disp, int minDisp = 1)
25 | {
26 | disp = 0;
27 | if (newLength == 0)
28 | return 0;
29 | int maxLength = 0;
30 | // try every possible 'disp' value (disp = oldLength - i)
31 | for (int i = 0; i < oldLength - minDisp; i++)
32 | {
33 | // work from the start of the old data to the end, to mimic the original implementation's behaviour
34 | // (and going from start to end or from end to start does not influence the compression ratio anyway)
35 | byte* currentOldStart = oldPtr + i;
36 | int currentLength = 0;
37 | // determine the length we can copy if we go back (oldLength - i) bytes
38 | // always check the next 'newLength' bytes, and not just the available 'old' bytes,
39 | // as the copied data can also originate from what we're currently trying to compress.
40 | for (int j = 0; j < newLength; j++)
41 | {
42 | // stop when the bytes are no longer the same
43 | if (*(currentOldStart + j) != *(newPtr + j))
44 | break;
45 | currentLength++;
46 | }
47 |
48 | // update the optimal value
49 | if (currentLength > maxLength)
50 | {
51 | maxLength = currentLength;
52 | disp = oldLength - i;
53 |
54 | // if we cannot do better anyway, stop trying.
55 | if (maxLength == newLength)
56 | break;
57 | }
58 | }
59 | return maxLength;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/FEAT/DSDecmp/Utils/SimpleReversePrioQueue.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace DSDecmp
6 | {
7 | ///
8 | /// Very simplistic implementation of a priority queue that returns items with lowest priority first.
9 | /// This is not the most efficient implementation, but required the least work while using the classes
10 | /// from the .NET collections, and without requiring importing another dll or several more class files
11 | /// in order to make it work.
12 | ///
13 | /// The type of the priority values.
14 | /// The type of item to put into the queue.
15 | public class SimpleReversedPrioQueue
16 | {
17 | private SortedDictionary> items;
18 | private int itemCount;
19 |
20 | ///
21 | /// Gets the number of items in this queue.
22 | ///
23 | public int Count { get { return this.itemCount; } }
24 |
25 | ///
26 | /// Creates a new, empty reverse priority queue.
27 | ///
28 | public SimpleReversedPrioQueue()
29 | {
30 | this.items = new SortedDictionary>();
31 | this.itemCount = 0;
32 | }
33 |
34 | ///
35 | /// Enqueues the given value, using the given priority.
36 | ///
37 | /// The priority of the value.
38 | /// The value to enqueue.
39 | public void Enqueue(TPrio priority, TValue value)
40 | {
41 | if (!this.items.ContainsKey(priority))
42 | this.items.Add(priority, new LinkedList());
43 | this.items[priority].AddLast(value);
44 | this.itemCount++;
45 | }
46 |
47 | ///
48 | /// Gets the current value with the lowest priority from this queue, without dequeueing the value.
49 | ///
50 | /// The priority of the returned value.
51 | /// The current value with the lowest priority.
52 | /// If there are no items left in this queue.
53 | public TValue Peek(out TPrio priority)
54 | {
55 | if (this.itemCount == 0)
56 | throw new IndexOutOfRangeException();
57 | foreach (KeyValuePair> kvp in this.items)
58 | {
59 | priority = kvp.Key;
60 | return kvp.Value.First.Value;
61 | }
62 | throw new IndexOutOfRangeException();
63 | }
64 |
65 | ///
66 | /// Dequeues the current value at the head of thisreverse priority queue.
67 | ///
68 | /// The priority of the dequeued value.
69 | /// The dequeued value, that used to be at the head of this queue.
70 | /// If this queue does not contain any items.
71 | public TValue Dequeue(out TPrio priority)
72 | {
73 | if (this.itemCount == 0)
74 | throw new IndexOutOfRangeException();
75 | LinkedList lowestLL = null;
76 | priority = default(TPrio);
77 | foreach (KeyValuePair> kvp in this.items)
78 | {
79 | lowestLL = kvp.Value;
80 | priority = kvp.Key;
81 | break;
82 | }
83 |
84 | TValue returnValue = lowestLL.First.Value;
85 | lowestLL.RemoveFirst();
86 | // remove unused linked lists. priorities will only grow.
87 | if (lowestLL.Count == 0)
88 | {
89 | this.items.Remove(priority);
90 | }
91 | this.itemCount--;
92 | return returnValue;
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/FEAT/ETC1.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/FEAT/ETC1.dll
--------------------------------------------------------------------------------
/FEAT/FEAT Icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/FEAT/FEAT Icon.ico
--------------------------------------------------------------------------------
/FEAT/FEAT.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | x86
6 | 8.0.30703
7 | 2.0
8 | {B80067EB-C976-4A91-8E64-3C555AA16FE3}
9 | WinExe
10 | Properties
11 | Fire_Emblem_Awakening_Archive_Tool
12 | FEAT
13 | v4.0
14 | Client
15 | 512
16 |
17 |
18 | x86
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 | true
27 |
28 |
29 | x86
30 | pdbonly
31 | true
32 | bin\Release\
33 | TRACE
34 | prompt
35 | 4
36 |
37 |
38 | FEAT Icon.ico
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | Form
80 |
81 |
82 | Form1.cs
83 |
84 |
85 |
86 |
87 | Form1.cs
88 |
89 |
90 | ResXFileCodeGenerator
91 | Resources.Designer.cs
92 | Designer
93 |
94 |
95 | True
96 | Resources.resx
97 | True
98 |
99 |
100 | SettingsSingleFileGenerator
101 | Settings.Designer.cs
102 |
103 |
104 | True
105 | Settings.settings
106 | True
107 |
108 |
109 |
110 |
111 |
112 | Always
113 |
114 |
115 |
116 |
117 |
118 |
125 |
--------------------------------------------------------------------------------
/FEAT/Form1.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace Fire_Emblem_Awakening_Archive_Tool
2 | {
3 | partial class Form1
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(Form1));
32 | this.RTB_Output = new System.Windows.Forms.RichTextBox();
33 | this.TB_FilePath = new System.Windows.Forms.TextBox();
34 | this.B_Go = new System.Windows.Forms.Button();
35 | this.B_Open = new System.Windows.Forms.Button();
36 | this.SuspendLayout();
37 | //
38 | // RTB_Output
39 | //
40 | this.RTB_Output.BackColor = System.Drawing.SystemColors.Control;
41 | this.RTB_Output.Location = new System.Drawing.Point(9, 40);
42 | this.RTB_Output.Name = "RTB_Output";
43 | this.RTB_Output.ReadOnly = true;
44 | this.RTB_Output.Size = new System.Drawing.Size(450, 269);
45 | this.RTB_Output.TabIndex = 12;
46 | this.RTB_Output.Text = "Open a file, or Drag/Drop several! Click this box to clear its text.\n";
47 | this.RTB_Output.Click += new System.EventHandler(this.RTB_Output_Click);
48 | //
49 | // TB_FilePath
50 | //
51 | this.TB_FilePath.Location = new System.Drawing.Point(98, 12);
52 | this.TB_FilePath.Name = "TB_FilePath";
53 | this.TB_FilePath.ReadOnly = true;
54 | this.TB_FilePath.Size = new System.Drawing.Size(275, 20);
55 | this.TB_FilePath.TabIndex = 11;
56 | this.TB_FilePath.TextChanged += new System.EventHandler(this.TB_FilePath_TextChanged);
57 | //
58 | // B_Go
59 | //
60 | this.B_Go.Enabled = false;
61 | this.B_Go.ForeColor = System.Drawing.SystemColors.ControlText;
62 | this.B_Go.Location = new System.Drawing.Point(379, 9);
63 | this.B_Go.Name = "B_Go";
64 | this.B_Go.Size = new System.Drawing.Size(80, 25);
65 | this.B_Go.TabIndex = 10;
66 | this.B_Go.Text = "Go";
67 | this.B_Go.UseVisualStyleBackColor = true;
68 | //
69 | // B_Open
70 | //
71 | this.B_Open.Location = new System.Drawing.Point(9, 9);
72 | this.B_Open.Name = "B_Open";
73 | this.B_Open.Size = new System.Drawing.Size(84, 25);
74 | this.B_Open.TabIndex = 9;
75 | this.B_Open.Text = "Open";
76 | this.B_Open.UseVisualStyleBackColor = true;
77 | this.B_Open.Click += new System.EventHandler(this.B_Open_Click);
78 | //
79 | // Form1
80 | //
81 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
82 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
83 | this.ClientSize = new System.Drawing.Size(469, 321);
84 | this.Controls.Add(this.RTB_Output);
85 | this.Controls.Add(this.TB_FilePath);
86 | this.Controls.Add(this.B_Go);
87 | this.Controls.Add(this.B_Open);
88 | this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
89 | this.MaximumSize = new System.Drawing.Size(485, 360);
90 | this.MinimumSize = new System.Drawing.Size(485, 360);
91 | this.Name = "Form1";
92 | this.Text = "Fire Emblem Archive Tool";
93 | this.ResumeLayout(false);
94 | this.PerformLayout();
95 |
96 | }
97 |
98 | #endregion
99 |
100 | private System.Windows.Forms.RichTextBox RTB_Output;
101 | private System.Windows.Forms.TextBox TB_FilePath;
102 | private System.Windows.Forms.Button B_Go;
103 | private System.Windows.Forms.Button B_Open;
104 | }
105 | }
106 |
107 |
--------------------------------------------------------------------------------
/FEAT/Form1.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 |
123 | AAABAAQAGRoAAAEACABoBwAARgAAABkaAAABAAgAaAcAAK4HAAAZGgAAAQAIAGgHAAAWDwAAGRoAAAEA
124 | CABoBwAAfhYAACgAAAAZAAAANAAAAAEACAAAAAAA2AIAAAAAAAAAAAAAAAEAAAABAACImaoA///dAIhV
125 | AwCZiGYAZqrMAGZ3iAB3u+4AZoi7AJnM7gC7iAMAAxFEAESIqgB3qt0A3ZkDAFWZuwDMzIgA7qoDALvd
126 | 3QBVqqoAzO7/ALvd7gAzapsAqmYDAEGHxQDu7swAzN3dAJm7uwB3RAMAM2Z3AEREZgCqqncAMzNEAFVm
127 | ZgB3ZjMAzLuIAFVVEQDd3aoAZmaZAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
128 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
129 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
130 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
131 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
132 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
133 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
134 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
135 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
136 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
137 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
138 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
139 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
140 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
141 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiYmJiYm
142 | AhsbJiYCGxsmJiYmJiYmJiYmJgAAACYmJiYmJiUMGyYmJQwbJiYmJiYmJiYmJiYAAAAmJiYmJiYlCB0m
143 | JiUHHSYmJiYmJiYmJiYmAAAAJiYmJiYmJQgdJiUIDB0mJiYmJiYmJiYmJgAAACYgHx8mJiUHHSYdBx0f
144 | ICYmJiYmJiYmJiYAAAAmIAAAHyYdBR8fBQUfIBogJiYmJiYmJiYmAAAAJiYgAAAfHxkaAAICGyAaICYm
145 | JiYmJiYmJgAAACYmIAAAHx8ABQUNCRsFAB8jJiYmJiYmJiYAAAAmJiAaGiAcERESDQIbAAAdHR0mJiYm
146 | JiYmAAAAJiYdGRogGwIJCQIbHwAaJQgdIyYmJiYmJgAAAB0dBxkZHx8CCR8fHx8aGiAHJR4jIyYmJiYA
147 | AAAlCAwZGSAaBRIFGRofGhkgJCIeHh4hJiYmAAAAJiYgIBkgGQIbGxkaBRkgHiQiIh4eHiEmJgAAACYm
148 | JiYgIAINCRYbGhkgISIkIiIeIR4hJiYAAAAmJiYmJhwCEBANCRsfISIkJCQiHiEhHiEmAAAAJiYmJiYc
149 | Cx0lJR0EHCQkJCQkIh4hJiEhJgAAACYmJiYmHA4MExMHBg4cJCQkJCIeISYmISYAAAAmJiYmJhwECggI
150 | CgYOCxwkJCIiHiEmJiYmAAAAJiYmJiYcBAoHBwoGDgscJCQiHiEmJiYmJgAAACYmJiYmIxQUBhUGBCML
151 | HCQiIh4hJiYmJiYAAAAmJiYmIyMGBgQXBB4hIxwiISIeISYmJiYmAAAAJiYmJiMhHgYEBAMBDyMjAyEi
152 | ISYmJiYmJgAAACYmJiYhIg8hBgQjASQDIyEiISYmJiYmJiYAAAAmJiYmHgEjIxwcHCMBDyMhISYmJiYm
153 | JiYmAAAAJiYmJiEkIyYmJiYmIwEjJiYmJiYmJiYmJgAAACYmJiYhIyYmJiYmJiYjIyYmJiYmJiYmJiYA
154 | AAD8Y/+A/GP/gPxj/4D8Q/+AjEH/gIQA/4DAAP+AwAB/gMAAP4DAAB+AAAAHgAAAA4DAAAGA8AABgPgA
155 | AID4AASA+AAGgPgAB4D4AA+A+AAPgPAAD4DwAB+A8AA/gPAAf4Dx8f+A8/n/gCgAAAAZAAAANAAAAAEA
156 | CAAAAAAA2AIAAAAAAAAAAAAAAAEAAAABAACImaoA///dAIhVAwCZiGYAZqrMAGZ3iAB3u+4AZoi7AJnM
157 | 7gC7iAMAAxFEAESIqgB3qt0A3ZkDAFWZuwDMzIgA7qoDALvd3QBVqqoAzO7/ALvd7gAzapsAqmYDAEGH
158 | xQDu7swAzN3dAJm7uwB3RAMAM2Z3AEREZgCqqncAMzNEAFVmZgB3ZjMAzLuIAFVVEQDd3aoAZmaZAP//
159 | /wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
160 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
161 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
162 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
163 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
164 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
165 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
166 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
167 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
168 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
169 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
170 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
171 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
172 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
173 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiYmJiYmAhsbJiYCGxsmJiYmJiYmJiYmJgAAACYm
174 | JiYmJiUMGyYmJQwbJiYmJiYmJiYmJiYAAAAmJiYmJiYlCB0mJiUHHSYmJiYmJiYmJiYmAAAAJiYmJiYm
175 | JQgdJiUIDB0mJiYmJiYmJiYmJgAAACYmJiAfHyUHHSYdBx0fICAmJiYmJiYmJiYAAAAmJiAAAB8dBR8f
176 | BQUfHwAaICYmJiYmJiYmAAAAJiYgAAAfHxkaAAICGyAAGiAmJiYmJiYmJgAAACYmIBoAHx8ABQUNCRsF
177 | AB8jJiYmJiYmJiYAAAAmJiAaGiAcERESDQIbAAAdHR0mJiYmJiYmAAAAJiYgGRogGwIJCQIbHwAaJQgd
178 | IyYmJiYmJgAAACYmHRkZHx8CCR8fHx8aGiAHJR4jIyMjJiYAAAAmHQcZGSAaBRIFGRofGhkgJCIeHh4e
179 | AyMmAAAAHQgMGRkgGQIbGxkaBRkgJB4kIh4hIx4eIwAAACYlJSAgIAINCRYbGhkgJCIDJCIiHiEhISMA
180 | AAAmJiYmJhwCEBANCRsfAwMhIiQkIh4eISYmAAAAJiYmJiYcCx0lJR0EHCYmAyIkJCIiIiEmJgAAACYm
181 | JiYmHA4MExMHBg4cJgMkJCQkIiIeISYAAAAmJiYmJhwECggICgYOCxwkJCQkJCIiHiEmAAAAJiYmJiYc
182 | BAoHBwoGDgscJCQkJCQiIh4hJgAAACYmJiYmIxQUBhUGBCMLHCQkJCQiAyIeISYAAAAmJiYmIyMGBgQX
183 | BB4hIxwkJCQiIiEiHiEmAAAAJiYmJiMhHgYEBAMBDyMjJCQiAwMhIiEmJgAAACYmJiYhIg8hBgQjASQD
184 | IwMDAyYhIiEmJiYAAAAmJiYmHgEjIxwcHCMBDyMmJiYmISEmJiYmAAAAJiYmJiEkIyYmJiYmIwEjJiYm
185 | JiYmJiYmJgAAACYmJiYhIyYmJiYmJiYjIyYmJiYmJiYmJiYAAAD8Y/+A/GP/gPxj/4D8Q/+A4ED/gMAA
186 | f4DAAH+AwAB/gMAAP4DAAB+AwAABgIAAAIAAAAAAgAAAAPgAAYD4BgGA+AIAgPgAAID4AACA+AAAgPAA
187 | AIDwAAGA8AAjgPAB54Dx8f+A8/n/gCgAAAAZAAAANAAAAAEACAAAAAAA2AIAAAAAAAAAAAAAAAEAAAAB
188 | AACImaoA///dAIhVAwCZiGYAZqrMAGZ3iAB3u+4AZoi7AJnM7gC7iAMAAxFEAESIqgB3qt0A3ZkDAFWZ
189 | uwDMzIgA7qoDALvd3QBVqqoAzO7/ALvd7gAzapsAqmYDAEGHxQDu7swAzN3dAJm7uwB3RAMAM2Z3AERE
190 | ZgCqqncAMzNEAFVmZgB3ZjMAzLuIAFVVEQDd3aoAZmaZAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
191 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
192 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
193 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
194 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
195 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
196 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
197 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
198 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
199 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
200 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
201 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
202 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
203 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
204 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
205 | AAAAAAAAJiYmJiYmAhsbJiYCGxsmJiYmJiYmJiYmJgAAACYmJiYmJiUHGyYmHSUbJiYmJiYmJiYmJiYA
206 | AAAmJiYmJiYlCB0mJQgMHSYmJiYmJiYmJiYmAAAAJiYmJiAfJQcdJh0HHSYfICAmJiYmJiYmJgAAACYm
207 | JiAAAB0FHx8FBR8fABogJiYmJiYmJiYAAAAmJiYgAAAfGRoAAgIbIAAaICYmJiYmJiYmAAAAJiYgAAAf
208 | HwAFBQ0JGwUAHyMmJiYmJiYmJgAAACYmIBoaIBwRERINAhsAAB0dHSYmJiYmJiYAAAAmJh0ZGiAbAgkJ
209 | AhsfABolCB0jJiYjIyEmAAAAHR0HGRkfHwIJHx8fHxoaIAclHiMjHh4hJgAAACUIDBkZIBoFEgUZGh8a
210 | GSAkJCIeHh4hJiYAAAAmJiAgGSAZAhsbGRoFGSAkJCQkIh4hIyYmAAAAJiYmJiAgAg0JFhsaGSAYJCIe
211 | JCIiHiEmJgAAACYmJiYcHAIQEA0JGx8DAwMhIiQkIh4eISYAAAAmJiYmHAscHSUlHRwEAyYmAyQkJCIi
212 | HiEmAAAAJiYmJiYcDgwTEwcGDhwDAyQkJCQiIiIeIQAAACYmJiYmHAQKCAgKBg4LHCQiJCQkJCIiHiEA
213 | AAAmJiYmJhwECgcHCgYOCxwiIiQkJCQDIh4hAAAAJiYmJiYjFBQGFQYEIwscIiQkJCQkISIeIQAAACYm
214 | JiYjIwYGBBcEHiEjHB4BASQkAyYhIiEAAAAmJiYmIyEeBgQEAwEPIyMBAQEBHiYmISIhAAAAJiYmJiEi
215 | DyEGBCMBJAMjAQEBIiYmJiEhJgAAACYmJiYeASMjHBwcIwEPIwEiIiYmJiYhJiYAAAAmJiYmISQjJiYm
216 | JiIjASMiJiYmJiYmJiYmAAAAJiYmJiEjJiYmJiYmJiMjJiYmJiYmJiYmJgAAACYmJiYmJiYmJiYmJiYm
217 | JiYmJiYmJiYmJiYAAAD8Y/+A/GP/gPxD/4DwRH+A4AB/gOAAf4DAAH+AwAA/gMAAGIAAAACAAAABgMAA
218 | AYDwAAGA8AAAgPADAID4AAAA+AAAAPgAAAD4AAAA8AAEAPAADADwAByA8AA9gPHg/4Dz+f+A////gCgA
219 | AAAZAAAANAAAAAEACAAAAAAA2AIAAAAAAAAAAAAAAAEAAAABAACImaoA///dAIhVAwCZiGYAZqrMAGZ3
220 | iAB3u+4AZoi7AJnM7gC7iAMAAxFEAESIqgB3qt0A3ZkDAFWZuwDMzIgA7qoDALvd3QBVqqoAzO7/ALvd
221 | 7gAzapsAqmYDAEGHxQDu7swAzN3dAJm7uwB3RAMAM2Z3AEREZgCqqncAMzNEAFVmZgB3ZjMAzLuIAFVV
222 | EQDd3aoAZmaZAP///wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
223 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
224 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
225 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
226 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
227 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
228 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
229 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
230 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
231 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
232 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
233 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
234 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
235 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
236 | AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAJiYmJiYmAhsbJiYCGxsmJiYm
237 | JiYmJiYmJgAAACYmJiYmJiUHGyYmHSUbJiYmJiYmJiYmJiYAAAAmJiYmJiYlCB0mJQgMHSYmJiYmJiYm
238 | JiYmAAAAJiAfHyYmJQcdJh0HHR8gICYmJiYmJiYmJgAAACYgAAAfJh0FHx8FBR8gGiAmJiYmJiYmJiYA
239 | AAAmIAAAHyYfGRoAAgIbIBogJiYmJiYmJiYmAAAAJiYgAAAfHwAFBQ0JGwUAHyMjJiYmJiYjJgAAACYm
240 | IBoaIBwRERINAhsAAB0dHSMmJiMjIyYAAAAmJh0ZGiAbAgkJAhsfABolCB0eIyMeHiMmAAAAHR0HGRkf
241 | HwIJHx8fHxoaIAclHh4eHiEmJgAAACUIDBkZIBoFEgUZGh8aGSAkIh4eISMmJiYAAAAmJiAgGSAZAhsb
242 | GRoFGSAkJCQiHh4hJiYmAAAAJiYmJiAgAg0JFhsaGSAkJCQkIiIeHiEmJgAAACYmJiYcHAIQEA0JGx8D
243 | AyIkJCQiIiIhJiYAAAAmJiYmHAscHSUlHRwEAyEkJCQkIiIiHiEmAAAAJiYmJhwOJQwTEwclBhwiJCQk
244 | JCQiIh4hJgAAACYmJiYmHAQKCAgKBg4LHCQkJCQkISIiISYAAAAmJiYmJhwECgcHCgYOCxwiJCQkJCIh
245 | IiEmAAAAJiYmJiYjFBQGFQYEIwscJCQkJCIDJiEhJgAAACYmJiYjIwYGBBcEHiEjHCQBAQEiAyYmISYA
246 | AAAmJiYmIyEeBgQEAwEPIyMBAQEBAyYmJiYmAAAAJiYmJiEiDyEGBCMBJAMjAQEBAQMmJiYmJgAAACYm
247 | JiYeASMjHBwcIwEPIyIBAQEBAyYmJiYAAAAmJiYmISQjJiYmJiYjASMmIiIBAQEDAyYmAAAAJiYmJiEj
248 | JiYmJiYmJiMjJiYmIiIiIiYmJgAAACYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYAAAD8Y/+A/GP/gPxD
249 | /4CMQP+AhAD/gIQA/4DAAD6AwAAYgMAAAIAAAAGAAAADgMAAA4DwAAGA8AABgPAAAIDwAACA+AAAgPgA
250 | AID4AASA8AAGgPAAD4DwAA+A8AAHgPHxAYDz+cOA////gA==
251 |
252 |
253 |
--------------------------------------------------------------------------------
/FEAT/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Windows.Forms;
5 |
6 | namespace Fire_Emblem_Awakening_Archive_Tool
7 | {
8 | static class Program
9 | {
10 | ///
11 | /// The main entry point for the application.
12 | ///
13 | [STAThread]
14 | static void Main()
15 | {
16 | Application.EnableVisualStyles();
17 | Application.SetCompatibleTextRenderingDefault(false);
18 | Application.Run(new Form1());
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/FEAT/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("Fire Emblem Awakening Archive Tool")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("Fire Emblem Awakening Archive Tool")]
13 | [assembly: AssemblyCopyright("Copyright © 2015")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("378e6952-1ee5-4939-a214-8321a013f82d")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("1.0.0.0")]
36 | [assembly: AssemblyFileVersion("1.0.0.0")]
37 |
--------------------------------------------------------------------------------
/FEAT/Properties/Resources.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.34209
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace Fire_Emblem_Awakening_Archive_Tool.Properties {
12 | using System;
13 |
14 |
15 | ///
16 | /// A strongly-typed resource class, for looking up localized strings, etc.
17 | ///
18 | // This class was auto-generated by the StronglyTypedResourceBuilder
19 | // class via a tool like ResGen or Visual Studio.
20 | // To add or remove a member, edit your .ResX file then rerun ResGen
21 | // with the /str option, or rebuild your VS project.
22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
25 | internal class Resources {
26 |
27 | private static global::System.Resources.ResourceManager resourceMan;
28 |
29 | private static global::System.Globalization.CultureInfo resourceCulture;
30 |
31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
32 | internal Resources() {
33 | }
34 |
35 | ///
36 | /// Returns the cached ResourceManager instance used by this class.
37 | ///
38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
39 | internal static global::System.Resources.ResourceManager ResourceManager {
40 | get {
41 | if (object.ReferenceEquals(resourceMan, null)) {
42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Fire_Emblem_Awakening_Archive_Tool.Properties.Resources", typeof(Resources).Assembly);
43 | resourceMan = temp;
44 | }
45 | return resourceMan;
46 | }
47 | }
48 |
49 | ///
50 | /// Overrides the current thread's CurrentUICulture property for all
51 | /// resource lookups using this strongly typed resource class.
52 | ///
53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
54 | internal static global::System.Globalization.CultureInfo Culture {
55 | get {
56 | return resourceCulture;
57 | }
58 | set {
59 | resourceCulture = value;
60 | }
61 | }
62 |
63 | ///
64 | /// Looks up a localized resource of type System.Byte[].
65 | ///
66 | internal static byte[] ETC1 {
67 | get {
68 | object obj = ResourceManager.GetObject("ETC1", resourceCulture);
69 | return ((byte[])(obj));
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/FEAT/Properties/Resources.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 |
122 | ..\etc1.dll;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
123 |
124 |
--------------------------------------------------------------------------------
/FEAT/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.34209
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace Fire_Emblem_Awakening_Archive_Tool.Properties
12 | {
13 |
14 |
15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "10.0.0.0")]
17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase
18 | {
19 |
20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
21 |
22 | public static Settings Default
23 | {
24 | get
25 | {
26 | return defaultInstance;
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/FEAT/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/FEAT/ctpktool/CTPK.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 |
6 | namespace ctpktool
7 | {
8 | class Ctpk
9 | {
10 | private const int Magic = 0x4B505443; // 'CTPK'
11 |
12 | public ushort Version; // ?
13 | public ushort NumberOfTextures;
14 | public uint TextureSectionOffset;
15 | public uint TextureSectionSize;
16 | public uint HashSectionOffset;
17 | public uint TextureInfoSection; // ??
18 |
19 | private readonly List _entries;
20 |
21 | public Ctpk()
22 | {
23 | _entries = new List();
24 | Version = 1;
25 | }
26 |
27 | public void Write(BinaryWriter writer)
28 | {
29 | writer.Write(new byte[0x20]); // Write 0x20 bytes of blank data so we can come back to it later
30 |
31 | // Section 1
32 | foreach (var entry in _entries)
33 | {
34 | entry.Write(writer);
35 | }
36 |
37 | // Section 2
38 | foreach (var entry in _entries)
39 | {
40 | writer.Write(entry.Info);
41 | }
42 |
43 | // Section 3
44 | for (int i = 0; i < _entries.Count; i++)
45 | {
46 | var entry = _entries[i];
47 | var curOffset = writer.BaseStream.Position;
48 |
49 | // Fix filename offset in section 1
50 | writer.BaseStream.Seek(0x20*(i+1), SeekOrigin.Begin);
51 | writer.Write((uint) curOffset);
52 | writer.BaseStream.Seek(curOffset, SeekOrigin.Begin);
53 |
54 | writer.Write(Encoding.GetEncoding(932).GetBytes(entry.InternalFilePath));
55 | writer.Write((byte)0); // Null terminated
56 | }
57 |
58 | writer.Write(new byte[4 - writer.BaseStream.Length%4]); // Pad the filename section to the nearest 4th byte
59 |
60 | // Section 4
61 | HashSectionOffset = (uint)writer.BaseStream.Length;
62 | for (int i = 0; i < _entries.Count; i++)
63 | {
64 | var entry = _entries[i];
65 | writer.Write(entry.FilenameHash);
66 | writer.Write(i);
67 | }
68 |
69 | // Section 5
70 | TextureInfoSection = (uint)writer.BaseStream.Length;
71 | foreach (var entry in _entries)
72 | {
73 | writer.Write(entry.Info2);
74 | }
75 |
76 | writer.Write(new byte[0x80 - writer.BaseStream.Length % 0x80]); // Pad the filename section to the nearest 0x80th byte
77 |
78 | // Section 6
79 | TextureSectionOffset = (uint)writer.BaseStream.Length;
80 | TextureSectionSize = 0;
81 | for (int i = 0; i < _entries.Count; i++)
82 | {
83 | var entry = _entries[i];
84 | var curOffset = writer.BaseStream.Position;
85 |
86 | // Fix texture data offset in section 1
87 | writer.BaseStream.Seek(0x20 * (i+1) + 0x08, SeekOrigin.Begin);
88 | writer.Write(TextureSectionSize);
89 | writer.BaseStream.Seek(curOffset, SeekOrigin.Begin);
90 |
91 | writer.Write(entry.TextureRawData);
92 |
93 | TextureSectionSize += (uint)entry.TextureRawData.Length;
94 | }
95 |
96 | NumberOfTextures = (ushort) _entries.Count;
97 |
98 | writer.BaseStream.Seek(0, SeekOrigin.Begin);
99 | writer.Write(Magic);
100 | writer.Write(Version);
101 | writer.Write(NumberOfTextures);
102 | writer.Write(TextureSectionOffset);
103 | writer.Write(TextureSectionSize);
104 | writer.Write(HashSectionOffset);
105 | writer.Write(TextureInfoSection);
106 | }
107 |
108 | public static Ctpk Create(string folder)
109 | {
110 | Ctpk file = new Ctpk();
111 |
112 | // Look for all xml definition files in the folder
113 | var files = Directory.GetFiles(folder, "*.xml", SearchOption.AllDirectories);
114 | foreach (var xmlFilename in files)
115 | {
116 | CTPKEntry entry = CTPKEntry.FromFile(xmlFilename, folder);
117 | file._entries.Add(entry);
118 | }
119 |
120 | for (int i = 0; i < file._entries.Count; i++)
121 | {
122 | file._entries[i].BitmapSizeOffset = (uint)((file._entries.Count + 1)*8 + (i*8));
123 | }
124 |
125 | var outputFilename = folder + ".ctpk";
126 | using (BinaryWriter writer = new BinaryWriter(File.Open(outputFilename, FileMode.Create)))
127 | {
128 | file.Write(writer);
129 | }
130 |
131 | Console.WriteLine("Finished! Saved to {0}", outputFilename);
132 |
133 | return file;
134 | }
135 |
136 | public static Ctpk Read(string filename)
137 | {
138 | using (BinaryReader reader = new BinaryReader(File.Open(filename, FileMode.Open)))
139 | {
140 | var data = new byte[reader.BaseStream.Length];
141 | reader.Read(data, 0, data.Length);
142 | return Read(data, filename);
143 | }
144 | }
145 |
146 | public static Ctpk Read(byte[] data, string filename)
147 | {
148 | Ctpk file = new Ctpk();
149 |
150 | using(MemoryStream dataStream = new MemoryStream(data))
151 | using (BinaryReader reader = new BinaryReader(dataStream))
152 | {
153 | if (reader.ReadUInt32() != Magic)
154 | {
155 | Console.WriteLine("ERROR: Not a valid CTPK file.");
156 | }
157 |
158 | file.Version = reader.ReadUInt16();
159 | file.NumberOfTextures = reader.ReadUInt16();
160 | file.TextureSectionOffset = reader.ReadUInt32();
161 | file.TextureSectionSize = reader.ReadUInt32();
162 | file.HashSectionOffset = reader.ReadUInt32();
163 | file.TextureInfoSection = reader.ReadUInt32();
164 |
165 | // Section 1 + 3
166 | for (int i = 0; i < file.NumberOfTextures; i++)
167 | {
168 | reader.BaseStream.Seek(0x20 * (i + 1), SeekOrigin.Begin);
169 |
170 | CTPKEntry entry = CTPKEntry.Read(reader);
171 | file._entries.Add(entry);
172 | }
173 |
174 | // Section 2
175 | for (int i = 0; i < file.NumberOfTextures; i++)
176 | {
177 | file._entries[i].Info = reader.ReadUInt32();
178 | }
179 |
180 | // Section 4
181 | for (int i = 0; i < file.NumberOfTextures; i++)
182 | {
183 | file._entries[i].FilenameHash = reader.ReadUInt32();
184 | }
185 |
186 | // Section 5
187 | reader.BaseStream.Seek(file.TextureInfoSection, SeekOrigin.Begin);
188 | for (int i = 0; i < file.NumberOfTextures; i++)
189 | {
190 | file._entries[i].Info2 = reader.ReadUInt32();
191 | }
192 |
193 | // Section 6
194 | for (int i = 0; i < file.NumberOfTextures; i++)
195 | {
196 | reader.BaseStream.Seek(file.TextureSectionOffset + file._entries[i].TextureOffset, SeekOrigin.Begin);
197 | file._entries[i].TextureRawData = new byte[file._entries[i].TextureSize];
198 | reader.Read(file._entries[i].TextureRawData, 0, (int)file._entries[i].TextureSize);
199 | }
200 |
201 | string basePath = Path.GetDirectoryName(filename);
202 | string baseFilename = Path.GetFileNameWithoutExtension(filename);
203 |
204 | if (!String.IsNullOrWhiteSpace(basePath))
205 | {
206 | baseFilename = Path.Combine(basePath, baseFilename);
207 | }
208 |
209 | for (int i = 0; i < file.NumberOfTextures; i++)
210 | {
211 | Console.WriteLine("Converting {0}...", file._entries[i].InternalFilePath);
212 | file._entries[i].ToFile(baseFilename);
213 | }
214 | }
215 |
216 | return file;
217 | }
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/FEAT/ctpktool/CTPKEntry.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Xml;
6 | using System.Xml.Serialization;
7 | using System.Drawing;
8 | using System.Drawing.Imaging;
9 |
10 | namespace ctpktool
11 | {
12 | [XmlRoot("Entry")]
13 | public class CTPKEntry
14 | {
15 | [XmlElement("InternalFilePath")]
16 | public string InternalFilePath;
17 | [XmlElement("RealFilePath")]
18 | public string FilePath;
19 | [XmlElement("TextureSize")]
20 | [XmlIgnore]
21 | public uint TextureSize;
22 | [XmlElement("TextureOffset")]
23 | [XmlIgnore]
24 | public uint TextureOffset;
25 | [XmlElement("Format")]
26 | public uint Format;
27 | [XmlIgnore]
28 | [XmlElement("Width")]
29 | public ushort Width;
30 | [XmlIgnore]
31 | [XmlElement("Height")]
32 | public ushort Height;
33 | [XmlElement("MipLevel")]
34 | public byte MipLevel;
35 | [XmlElement("Type")]
36 | public byte Type; // 0 = cube? 1=1D? 2=2D?
37 | [XmlElement("Unknown")]
38 | public ushort Unknown;
39 | [XmlElement("BitmapSizeOffset")]
40 | public uint BitmapSizeOffset;
41 | [XmlIgnore]
42 | public uint FileTime; // Generate this on our own, so don't export or import it
43 |
44 | [XmlElement("Info")]
45 | public uint Info; // ??
46 |
47 | [XmlElement("Info2")]
48 | public uint Info2; // ??
49 |
50 | [XmlIgnore]
51 | public uint FilenameHash;
52 |
53 | [XmlIgnore]
54 | public byte[] TextureRawData;
55 |
56 | [XmlIgnore]
57 | private Texture _textureData;
58 |
59 | [XmlElement("HasAlpha")]
60 | public bool HasAlpha;
61 |
62 | public Bitmap GetBitmap()
63 | {
64 | if (TextureRawData.Length == 0)
65 | {
66 | return new Bitmap(0, 0);
67 | }
68 |
69 | _textureData = new Texture(Width, Height, (TextureFormat)Format, TextureRawData);
70 | return _textureData.GetBitmap();
71 | }
72 |
73 | public void Write(BinaryWriter writer)
74 | {
75 | writer.Write(0); // Temporary. Will come back to write correct data later when filenames have been written into the data
76 | writer.Write(TextureRawData.Length);
77 | writer.Write(0); // Temporary
78 | writer.Write(Format);
79 | writer.Write(Width);
80 | writer.Write(Height);
81 | writer.Write(MipLevel);
82 | writer.Write(Type);
83 | writer.Write(Unknown);
84 | writer.Write(BitmapSizeOffset); // What is this exactly?
85 | writer.Write(FileTime);
86 | }
87 |
88 | public static CTPKEntry Read(BinaryReader reader)
89 | {
90 | CTPKEntry entry = new CTPKEntry();
91 |
92 | var pathOffset = reader.ReadInt32();
93 | entry.TextureSize = reader.ReadUInt32();
94 | entry.TextureOffset = reader.ReadUInt32();
95 | entry.Format = reader.ReadUInt32();
96 | entry.Width = reader.ReadUInt16();
97 | entry.Height = reader.ReadUInt16();
98 | entry.MipLevel = reader.ReadByte();
99 | entry.Type = reader.ReadByte();
100 | entry.Unknown = reader.ReadUInt16();
101 | entry.BitmapSizeOffset = reader.ReadUInt32();
102 | entry.FileTime = reader.ReadUInt32();
103 |
104 | #region Read path string
105 | var curOffset = reader.BaseStream.Position;
106 | reader.BaseStream.Seek(pathOffset, SeekOrigin.Begin);
107 |
108 | List temp = new List();
109 | byte c = 0;
110 | while ((c = reader.ReadByte()) != 0)
111 | {
112 | temp.Add(c);
113 | }
114 |
115 | entry.InternalFilePath = Encoding.GetEncoding(932).GetString(temp.ToArray()); // 932 = Shift-JIS
116 |
117 | reader.BaseStream.Seek(curOffset, SeekOrigin.Begin);
118 | #endregion
119 |
120 | switch ((TextureFormat)entry.Format)
121 | {
122 | case TextureFormat.A4:
123 | case TextureFormat.A8:
124 | case TextureFormat.La4:
125 | case TextureFormat.Rgb5551:
126 | case TextureFormat.Rgba4:
127 | case TextureFormat.Rgba8:
128 | case TextureFormat.Etc1A4:
129 | entry.HasAlpha = true;
130 | break;
131 |
132 | default:
133 | entry.HasAlpha = false;
134 | break;
135 | }
136 |
137 | return entry;
138 | }
139 |
140 | public void ToFile(string outputFolder)
141 | {
142 | string dir = Path.GetDirectoryName(InternalFilePath);
143 | string filename = Path.GetFileNameWithoutExtension(InternalFilePath);
144 |
145 | if (!String.IsNullOrWhiteSpace(outputFolder) && !Directory.Exists(outputFolder))
146 | {
147 | Directory.CreateDirectory(outputFolder);
148 | }
149 |
150 | if (!String.IsNullOrWhiteSpace(dir))
151 | {
152 | if (!String.IsNullOrWhiteSpace(outputFolder))
153 | dir = Path.Combine(outputFolder, dir);
154 |
155 | filename = Path.Combine(dir, filename);
156 |
157 | if (!Directory.Exists(dir))
158 | Directory.CreateDirectory(dir);
159 | }
160 |
161 | FilePath = filename + ".png";
162 |
163 | var outputPath = filename;
164 | if (!String.IsNullOrWhiteSpace(outputFolder))
165 | outputPath = Path.Combine(outputFolder, outputPath);
166 |
167 | using (TextWriter writer = new StreamWriter(outputPath + ".xml"))
168 | {
169 | XmlSerializer serializer = new XmlSerializer(typeof(CTPKEntry));
170 | serializer.Serialize(writer, this);
171 | }
172 |
173 | // Export image data to file here?
174 | GetBitmap().Save(outputPath + ".png");
175 | }
176 |
177 | public static CTPKEntry FromFile(string filename, string foldername)
178 | {
179 | if (!File.Exists(filename))
180 | return new CTPKEntry();
181 |
182 | using (XmlTextReader reader = new XmlTextReader(filename))
183 | {
184 | reader.WhitespaceHandling = WhitespaceHandling.All;
185 |
186 | XmlSerializer serializer = new XmlSerializer(typeof(CTPKEntry));
187 |
188 | CTPKEntry entry = (CTPKEntry)serializer.Deserialize(reader);
189 |
190 | Console.WriteLine("Reading {0}...", entry.FilePath);
191 |
192 | // Import image file
193 | entry._textureData = new Texture();
194 |
195 | var path = entry.FilePath;
196 |
197 | if (!String.IsNullOrWhiteSpace(foldername))
198 | path = Path.Combine(foldername, path);
199 |
200 | var origbmp = Bitmap.FromFile(path);
201 |
202 | var pixelSize = 3;
203 | var bmpPixelFormat = PixelFormat.Format24bppRgb;
204 | entry.Format = (int)TextureFormat.Rgb8;
205 |
206 | entry.HasAlpha = true;
207 | if (entry.HasAlpha)
208 | {
209 | bmpPixelFormat = PixelFormat.Format32bppArgb;
210 | entry.Format = (int)TextureFormat.Rgba8;
211 | pixelSize = 4;
212 | }
213 |
214 | var bmp = new Bitmap(origbmp.Width, origbmp.Height, bmpPixelFormat);
215 | using (Graphics gr = Graphics.FromImage(bmp))
216 | {
217 | gr.DrawImage(origbmp, new Rectangle(0, 0, bmp.Width, bmp.Height));
218 | }
219 |
220 | entry.Width = (ushort)bmp.Width;
221 | entry.Height = (ushort)bmp.Height;
222 |
223 | var scramble = new Texture().GetScrambledTextureData(bmp);
224 | var dataSize = bmp.Width * bmp.Height * pixelSize;
225 |
226 | entry.TextureRawData = new byte[dataSize];
227 | entry.TextureSize = (uint)dataSize;
228 | Array.Copy(scramble, entry.TextureRawData, dataSize);
229 |
230 | entry.FileTime = (uint)File.GetLastWriteTime(path).Ticks; // This is right exactly? Not sure, don't think it matters either
231 |
232 | var filenameData = Encoding.GetEncoding(932).GetBytes(entry.InternalFilePath);
233 | entry.FilenameHash = Crc32.Calculate(filenameData, filenameData.Length);
234 |
235 | return entry;
236 | }
237 | }
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/FEAT/ctpktool/Crc32.cs:
--------------------------------------------------------------------------------
1 | namespace ctpktool
2 | {
3 | static unsafe class Crc32
4 | {
5 | private static readonly uint[] Crc32Tab = {
6 | 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f,
7 | 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
8 | 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2,
9 | 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
10 | 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
11 | 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
12 | 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c,
13 | 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
14 | 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423,
15 | 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
16 | 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106,
17 | 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
18 | 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d,
19 | 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
20 | 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
21 | 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
22 | 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7,
23 | 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
24 | 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa,
25 | 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
26 | 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81,
27 | 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
28 | 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84,
29 | 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
30 | 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
31 | 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
32 | 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e,
33 | 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
34 | 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55,
35 | 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
36 | 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28,
37 | 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
38 | 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f,
39 | 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
40 | 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
41 | 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
42 | 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69,
43 | 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
44 | 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc,
45 | 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
46 | 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693,
47 | 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
48 | 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
49 | };
50 |
51 | public static uint Calculate(byte* buf, int size)
52 | {
53 | uint i;
54 | byte* m = buf;
55 | uint crc = 0xffffffff;
56 |
57 | for (i = 0; i < size; i++)
58 | {
59 | crc = (crc >> 8) ^ Crc32Tab[(crc & 0xFF) ^ *m];
60 | m++;
61 | }
62 |
63 | return crc ^ 0xffffffff;
64 | }
65 |
66 | static public uint Calculate(byte[] data, int size)
67 | {
68 | fixed (byte* ptr = &data[0])
69 | {
70 | return Calculate(ptr, size);
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/FEAT/ctpktool/ETC.cs:
--------------------------------------------------------------------------------
1 | // Code from Normmatt's texturipper (with permission)
2 | using System;
3 | using System.Drawing;
4 |
5 | namespace ctpktool
6 | {
7 | internal class Etc
8 | {
9 | private static readonly int[,] CompressParams =
10 | {
11 | {
12 | -8, -2, 2, 8
13 | },
14 | {
15 | -17, -5, 5, 17
16 | },
17 | {
18 | -29, -9, 9, 29
19 | },
20 | {
21 | -42, -13, 13, 42
22 | },
23 | {
24 | -60, -18, 18, 60
25 | },
26 | {
27 | -80, -24, 24, 80
28 | },
29 | {
30 | -106, -33, 33, 106
31 | },
32 | {
33 | -183, -47, 47, 183
34 | }
35 | };
36 |
37 | private static readonly uint[] Unscramble =
38 | {
39 | 2U, 3U, 1U, 0U
40 | };
41 |
42 | static Etc()
43 | {
44 | }
45 |
46 | private static int GetEtc1BlockStart(Size size, int x, int y, bool hasAlpha)
47 | {
48 | int num1 = x / 4;
49 | int num2 = y / 4;
50 | return ((x / 8 + y / 8 * (size.Width / 8)) * 4 + (num1 & 1) + (num2 & 1) * 2) * (hasAlpha ? 16 : 8);
51 | }
52 |
53 | public static int GetEtc1Length(Size size, bool hasAlpha, int levels = 1)
54 | {
55 | int num1 = 0;
56 | int num2 = hasAlpha ? 16 : 8;
57 | int num3;
58 | if (size.Width % 4 != 0)
59 | num3 = 0;
60 | else if (size.Height % 4 != 0)
61 | {
62 | num3 = 0;
63 | }
64 | else
65 | {
66 | for (int index = 0; index < levels; ++index)
67 | {
68 | int num4 = size.Height >> index;
69 | if (size.Width >> index >= 4 && num4 >= 4)
70 | num1 += num2 * num4 / 4 * (size.Width >> index) / 4;
71 | else
72 | break;
73 | }
74 | num3 = num1;
75 | }
76 | return num3;
77 | }
78 |
79 | public static void GetEtc1RasterData(byte[] imageData, Size size, int y, bool hasAlpha, byte[] output, int outputOffset)
80 | {
81 | int width = size.Width;
82 | int num1 = hasAlpha ? 8 : 0;
83 | Color[,] colorArray = new Color[4, 4];
84 | int x = 0;
85 | int num2 = 0;
86 | while (x != width)
87 | {
88 | int index1 = y & 3;
89 | int etc1BlockStart = GetEtc1BlockStart(size, x, y, hasAlpha);
90 | const ulong num3 = 0UL;
91 | ulong num4 = !hasAlpha ? ulong.MaxValue : num3 | imageData[etc1BlockStart] | (ulong) imageData[etc1BlockStart + 1] << 8 | (ulong) imageData[etc1BlockStart + 2] << 16 | (ulong) imageData[etc1BlockStart + 3] << 24 | (ulong) imageData[etc1BlockStart + 4] << 32 | (ulong) imageData[etc1BlockStart + 5] << 40 | (ulong) imageData[etc1BlockStart + 6] << 48 | (ulong) imageData[etc1BlockStart + 7] << 56;
92 | ulong num5 = 0UL | imageData[etc1BlockStart + num1] | (ulong) imageData[etc1BlockStart + num1 + 1] << 8 | (ulong) imageData[etc1BlockStart + num1 + 2] << 16 | (ulong) imageData[etc1BlockStart + num1 + 3] << 24 | (ulong) imageData[etc1BlockStart + num1 + 4] << 32 | (ulong) imageData[etc1BlockStart + num1 + 5] << 40 | (ulong) imageData[etc1BlockStart + num1 + 6] << 48 | (ulong) imageData[etc1BlockStart + num1 + 7] << 56;
93 | bool flag1 = ((long) num5 & 4294967296L) != 0L;
94 | bool flag2 = ((long) num5 & 8589934592L) != 0L;
95 | uint num6 = (uint) (num5 >> 37 & 7UL);
96 | uint num7 = (uint) (num5 >> 34 & 7UL);
97 | int num8;
98 | int num9;
99 | int num10;
100 | int num11;
101 | int num12;
102 | int num13;
103 | if (flag2)
104 | {
105 | sbyte num14 = (sbyte) ((long) (num5 >> 56) & 7L);
106 | sbyte num15 = (sbyte) ((long) (num5 >> 48) & 7L);
107 | sbyte num16 = (sbyte) ((long) (num5 >> 40) & 7L);
108 | sbyte num17 = (sbyte) (num14 << 5);
109 | sbyte num18 = (sbyte) (num15 << 5);
110 | sbyte num19 = (sbyte) (num16 << 5);
111 | sbyte num20 = (sbyte) (num17 >> 5);
112 | sbyte num21 = (sbyte) (num18 >> 5);
113 | sbyte num22 = (sbyte) (num19 >> 5);
114 | int num23 = (int) (num5 >> 59) & 31;
115 | int num24 = (int) (num5 >> 51) & 31;
116 | int num25 = (int) (num5 >> 43) & 31;
117 | int num26 = num23 + num20;
118 | int num27 = num24 + num21;
119 | int num28 = num25 + num22;
120 | num8 = num23 * byte.MaxValue / 31;
121 | num9 = num24 * byte.MaxValue / 31;
122 | num10 = num25 * byte.MaxValue / 31;
123 | num11 = num26 * byte.MaxValue / 31;
124 | num12 = num27 * byte.MaxValue / 31;
125 | num13 = num28 * byte.MaxValue / 31;
126 | }
127 | else
128 | {
129 | num8 = (int) ((num5 >> 60 & 15UL) * byte.MaxValue / 15UL);
130 | num11 = (int) ((num5 >> 56 & 15UL) * byte.MaxValue / 15UL);
131 | num9 = (int) ((num5 >> 52 & 15UL) * byte.MaxValue / 15UL);
132 | num12 = (int) ((num5 >> 48 & 15UL) * byte.MaxValue / 15UL);
133 | num10 = (int) ((num5 >> 44 & 15UL) * byte.MaxValue / 15UL);
134 | num13 = (int) ((num5 >> 40 & 15UL) * byte.MaxValue / 15UL);
135 | }
136 | uint num29 = (uint) (num5 >> 16 & ushort.MaxValue);
137 | uint num30 = (uint) (num5 & ushort.MaxValue);
138 | int num31 = flag1 ? 4 : 2;
139 | int num32 = flag1 ? 2 : 4;
140 | int num33 = 0;
141 | for (int index2 = 0; index2 != num31; ++index2)
142 | {
143 | int index3 = 0;
144 | while (index3 != num32)
145 | {
146 | if (index3 == index1)
147 | {
148 | uint num14 = (uint) (((int) (num29 >> num33) & 1) << 1 | (int) (num30 >> num33) & 1);
149 | uint num15 = Unscramble[num14];
150 | int num16 = num8 + CompressParams[(int) (IntPtr) num6, (int) (IntPtr) num15];
151 | int num17 = num9 + CompressParams[(int) (IntPtr) num6, (int) (IntPtr) num15];
152 | int num18 = num10 + CompressParams[(int) (IntPtr) num6, (int) (IntPtr) num15];
153 | int red = Clamp(num16, 0, byte.MaxValue);
154 | int green = Clamp(num17, 0, byte.MaxValue);
155 | int blue = Clamp(num18, 0, byte.MaxValue);
156 | int num19 = index2 * 4 + index3;
157 | int alpha = (int) ((long) (num4 >> num19 * 4) & 15L) * byte.MaxValue / 15;
158 | colorArray[index3, index2] = Color.FromArgb(alpha, red, green, blue);
159 | }
160 | ++index3;
161 | ++num33;
162 | }
163 | if (flag1)
164 | num33 += 2;
165 | }
166 | int num34 = flag1 ? 0 : 2;
167 | int num35 = flag1 ? 2 : 0;
168 | int num36 = flag1 ? 2 : 8;
169 | for (int index2 = num34; index2 != 4; ++index2)
170 | {
171 | int index3 = num35;
172 | while (index3 != 4)
173 | {
174 | if (index3 == index1)
175 | {
176 | uint num14 = (uint) (((int) (num29 >> num36) & 1) << 1 | (int) (num30 >> num36) & 1);
177 | uint num15 = Unscramble[num14];
178 | int num16 = num11 + CompressParams[(int) (IntPtr) num7, (int) (IntPtr) num15];
179 | int num17 = num12 + CompressParams[(int) (IntPtr) num7, (int) (IntPtr) num15];
180 | int num18 = num13 + CompressParams[(int) (IntPtr) num7, (int) (IntPtr) num15];
181 | int red = Clamp(num16, 0, byte.MaxValue);
182 | int green = Clamp(num17, 0, byte.MaxValue);
183 | int blue = Clamp(num18, 0, byte.MaxValue);
184 | int num19 = index2 * 4 + index3;
185 | int alpha = (int) ((long) (num4 >> num19 * 4) & 15L) * byte.MaxValue / 15;
186 | colorArray[index3, index2] = Color.FromArgb(alpha, red, green, blue);
187 | }
188 | ++index3;
189 | ++num36;
190 | }
191 | if (flag1)
192 | num36 += 2;
193 | }
194 | int index4 = num2;
195 | int index5 = num2 + 4;
196 | int index6 = num2 + 8;
197 | int index7 = num2 + 12;
198 | output[index4] = colorArray[index1, 0].A;
199 | output[index4 + 1] = colorArray[index1, 0].R;
200 | output[index4 + 2] = colorArray[index1, 0].G;
201 | output[index4 + 3] = colorArray[index1, 0].B;
202 | output[index5] = colorArray[index1, 1].A;
203 | output[index5 + 1] = colorArray[index1, 1].R;
204 | output[index5 + 2] = colorArray[index1, 1].G;
205 | output[index5 + 3] = colorArray[index1, 1].B;
206 | output[index6] = colorArray[index1, 2].A;
207 | output[index6 + 1] = colorArray[index1, 2].R;
208 | output[index6 + 2] = colorArray[index1, 2].G;
209 | output[index6 + 3] = colorArray[index1, 2].B;
210 | output[index7] = colorArray[index1, 3].A;
211 | output[index7 + 1] = colorArray[index1, 3].R;
212 | output[index7 + 2] = colorArray[index1, 3].G;
213 | output[index7 + 3] = colorArray[index1, 3].B;
214 | x += 4;
215 | num2 += 16;
216 | }
217 | }
218 |
219 | public static void GetEtc1RasterData(byte[] imageData, Size size, int y, bool hasAlpha, Bitmap bmp, int outputOffset)
220 | {
221 | int width = size.Width;
222 | int num1 = hasAlpha ? 8 : 0;
223 | Color[,] colorArray = new Color[4, 4];
224 | int x = 0;
225 | while (x != width)
226 | {
227 | int index1 = y & 3;
228 | int etc1BlockStart = GetEtc1BlockStart(size, x, y, hasAlpha);
229 | const ulong num2 = 0UL;
230 | ulong num3 = !hasAlpha ? ulong.MaxValue : num2 | imageData[etc1BlockStart] | (ulong) imageData[etc1BlockStart + 1] << 8 | (ulong) imageData[etc1BlockStart + 2] << 16 | (ulong) imageData[etc1BlockStart + 3] << 24 | (ulong) imageData[etc1BlockStart + 4] << 32 | (ulong) imageData[etc1BlockStart + 5] << 40 | (ulong) imageData[etc1BlockStart + 6] << 48 | (ulong) imageData[etc1BlockStart + 7] << 56;
231 | ulong num4 = 0UL | imageData[etc1BlockStart + num1] | (ulong) imageData[etc1BlockStart + num1 + 1] << 8 | (ulong) imageData[etc1BlockStart + num1 + 2] << 16 | (ulong) imageData[etc1BlockStart + num1 + 3] << 24 | (ulong) imageData[etc1BlockStart + num1 + 4] << 32 | (ulong) imageData[etc1BlockStart + num1 + 5] << 40 | (ulong) imageData[etc1BlockStart + num1 + 6] << 48 | (ulong) imageData[etc1BlockStart + num1 + 7] << 56;
232 | bool flag1 = ((long) num4 & 4294967296L) != 0L;
233 | bool flag2 = ((long) num4 & 8589934592L) != 0L;
234 | uint num5 = (uint) (num4 >> 37 & 7UL);
235 | uint num6 = (uint) (num4 >> 34 & 7UL);
236 | int num7;
237 | int num8;
238 | int num9;
239 | int num10;
240 | int num11;
241 | int num12;
242 | if (flag2)
243 | {
244 | sbyte num13 = (sbyte) ((long) (num4 >> 56) & 7L);
245 | sbyte num14 = (sbyte) ((long) (num4 >> 48) & 7L);
246 | sbyte num15 = (sbyte) ((long) (num4 >> 40) & 7L);
247 | sbyte num16 = (sbyte) (num13 << 5);
248 | sbyte num17 = (sbyte) (num14 << 5);
249 | sbyte num18 = (sbyte) (num15 << 5);
250 | sbyte num19 = (sbyte) (num16 >> 5);
251 | sbyte num20 = (sbyte) (num17 >> 5);
252 | sbyte num21 = (sbyte) (num18 >> 5);
253 | int num22 = (int) (num4 >> 59) & 31;
254 | int num23 = (int) (num4 >> 51) & 31;
255 | int num24 = (int) (num4 >> 43) & 31;
256 | int num25 = num22 + num19;
257 | int num26 = num23 + num20;
258 | int num27 = num24 + num21;
259 | num7 = num22 * byte.MaxValue / 31;
260 | num8 = num23 * byte.MaxValue / 31;
261 | num9 = num24 * byte.MaxValue / 31;
262 | num10 = num25 * byte.MaxValue / 31;
263 | num11 = num26 * byte.MaxValue / 31;
264 | num12 = num27 * byte.MaxValue / 31;
265 | }
266 | else
267 | {
268 | num7 = (int) ((num4 >> 60 & 15UL) * byte.MaxValue / 15UL);
269 | num10 = (int) ((num4 >> 56 & 15UL) * byte.MaxValue / 15UL);
270 | num8 = (int) ((num4 >> 52 & 15UL) * byte.MaxValue / 15UL);
271 | num11 = (int) ((num4 >> 48 & 15UL) * byte.MaxValue / 15UL);
272 | num9 = (int) ((num4 >> 44 & 15UL) * byte.MaxValue / 15UL);
273 | num12 = (int) ((num4 >> 40 & 15UL) * byte.MaxValue / 15UL);
274 | }
275 | uint num28 = (uint) (num4 >> 16 & ushort.MaxValue);
276 | uint num29 = (uint) (num4 & ushort.MaxValue);
277 | int num30 = flag1 ? 4 : 2;
278 | int num31 = flag1 ? 2 : 4;
279 | int num32 = 0;
280 | for (int index2 = 0; index2 != num30; ++index2)
281 | {
282 | int index3 = 0;
283 | while (index3 != num31)
284 | {
285 | if (index3 == index1)
286 | {
287 | uint num13 = (uint) (((int) (num28 >> num32) & 1) << 1 | (int) (num29 >> num32) & 1);
288 | uint num14 = Unscramble[num13];
289 | int num15 = num7 + CompressParams[(int) (IntPtr) num5, (int) (IntPtr) num14];
290 | int num16 = num8 + CompressParams[(int) (IntPtr) num5, (int) (IntPtr) num14];
291 | int num17 = num9 + CompressParams[(int) (IntPtr) num5, (int) (IntPtr) num14];
292 | int red = Clamp(num15, 0, byte.MaxValue);
293 | int green = Clamp(num16, 0, byte.MaxValue);
294 | int blue = Clamp(num17, 0, byte.MaxValue);
295 | int num18 = index2 * 4 + index3;
296 | int alpha = (int) ((long) (num3 >> num18 * 4) & 15L) * byte.MaxValue / 15;
297 | colorArray[index3, index2] = Color.FromArgb(alpha, red, green, blue);
298 | }
299 | ++index3;
300 | ++num32;
301 | }
302 | if (flag1)
303 | num32 += 2;
304 | }
305 | int num33 = flag1 ? 0 : 2;
306 | int num34 = flag1 ? 2 : 0;
307 | int num35 = flag1 ? 2 : 8;
308 | for (int index2 = num33; index2 != 4; ++index2)
309 | {
310 | int index3 = num34;
311 | while (index3 != 4)
312 | {
313 | if (index3 == index1)
314 | {
315 | uint num13 = (uint) (((int) (num28 >> num35) & 1) << 1 | (int) (num29 >> num35) & 1);
316 | uint num14 = Unscramble[num13];
317 | int num15 = num10 + CompressParams[(int) (IntPtr) num6, (int) (IntPtr) num14];
318 | int num16 = num11 + CompressParams[(int) (IntPtr) num6, (int) (IntPtr) num14];
319 | int num17 = num12 + CompressParams[(int) (IntPtr) num6, (int) (IntPtr) num14];
320 | int red = Clamp(num15, 0, byte.MaxValue);
321 | int green = Clamp(num16, 0, byte.MaxValue);
322 | int blue = Clamp(num17, 0, byte.MaxValue);
323 | int num18 = index2 * 4 + index3;
324 | int alpha = (int) ((long) (num3 >> num18 * 4) & 15L) * byte.MaxValue / 15;
325 | colorArray[index3, index2] = Color.FromArgb(alpha, red, green, blue);
326 | }
327 | ++index3;
328 | ++num35;
329 | }
330 | if (flag1)
331 | num35 += 2;
332 | }
333 | bmp.SetPixel(x, y, colorArray[index1, 0]);
334 | bmp.SetPixel(x + 1, y, colorArray[index1, 1]);
335 | bmp.SetPixel(x + 2, y, colorArray[index1, 2]);
336 | bmp.SetPixel(x + 3, y, colorArray[index1, 3]);
337 | x += 4;
338 | }
339 | }
340 |
341 | public static int Clamp(int value, int min, int max)
342 | {
343 | if (value < min)
344 | return min;
345 | if (max < value)
346 | return max;
347 | return value;
348 | }
349 | }
350 | }
351 |
--------------------------------------------------------------------------------
/FEAT/ctpktool/Program.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 |
4 | namespace ctpktool
5 | {
6 | class Program
7 | {
8 | static void Main(string[] args)
9 | {
10 | if (args.Length != 1)
11 | {
12 | Console.WriteLine("usage: {0} input.ctpk/input_folder", AppDomain.CurrentDomain.FriendlyName);
13 | Environment.Exit(0);
14 | }
15 |
16 | if (Directory.Exists(args[0]))
17 | {
18 | Ctpk.Create(args[0]);
19 | }
20 | else if (File.Exists(args[0]))
21 | {
22 | Ctpk.Read(args[0]);
23 | }
24 | else
25 | {
26 | Console.WriteLine("Could not find path or file '{0}'", args[0]);
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/FEAT/ctpktool/Texture.cs:
--------------------------------------------------------------------------------
1 | // Code from Normmatt's texturipper (with permission)
2 | using System;
3 | using System.Drawing;
4 | using System.Drawing.Imaging;
5 | using System.IO;
6 |
7 | namespace ctpktool
8 | {
9 | public class Texture
10 | {
11 | public int Width;
12 | public int Height;
13 | public bool HasAlpha;
14 | public byte[] Data;
15 | public TextureFormat TextureFormat;
16 |
17 | private readonly int[] _tileOrder =
18 | {
19 | 0, 1, 8, 9, 2, 3, 10, 11, 16, 17, 24, 25, 18, 19, 26, 27, 4, 5, 12, 13, 6, 7, 14, 15,
20 | 20, 21, 28, 29, 22, 23, 30, 31, 32, 33, 40, 41, 34, 35, 42, 43, 48, 49, 56, 57, 50, 51, 58, 59, 36, 37, 44,
21 | 45, 38, 39, 46, 47, 52, 53, 60, 61, 54, 55, 62, 63
22 | };
23 |
24 | public Texture()
25 | {
26 | // Stub
27 | }
28 |
29 | public Texture(int width, int height, TextureFormat textureFormat, byte[] data)
30 | {
31 | Width = IsPowerOfTwo(width) ? width : nlpo2(width);
32 | Height = IsPowerOfTwo(height) ? height : nlpo2(height);
33 | Data = data;
34 | TextureFormat = textureFormat;
35 |
36 | switch (textureFormat)
37 | {
38 | case TextureFormat.A4:
39 | case TextureFormat.A8:
40 | case TextureFormat.La4:
41 | case TextureFormat.Rgb5551:
42 | case TextureFormat.Rgba4:
43 | case TextureFormat.Rgba8:
44 | case TextureFormat.Etc1A4:
45 | HasAlpha = true;
46 | break;
47 |
48 | default:
49 | HasAlpha = false;
50 | break;
51 | }
52 | }
53 |
54 | public static uint GetPixelSize(TextureFormat textureFormat, int width, int height)
55 | {
56 | switch (textureFormat)
57 | {
58 | case TextureFormat.Rgba8:
59 | return (uint)(4 * width * height);
60 | case TextureFormat.Rgb8:
61 | return (uint)(3 * width * height);
62 | case TextureFormat.Rgb5551:
63 | case TextureFormat.Rgb565:
64 | case TextureFormat.Rgba4:
65 | case TextureFormat.La8:
66 | case TextureFormat.Hilo8:
67 | return (uint)(2 * width * height);
68 | case TextureFormat.L8:
69 | case TextureFormat.A8:
70 | case TextureFormat.La4:
71 | return (uint)(1 * width * height);
72 | case TextureFormat.L4: //TODO: Verify this is correct
73 | case TextureFormat.A4:
74 | return (uint)((1 * width * height) / 2);
75 | case TextureFormat.Etc1:
76 | return (uint)Etc.GetEtc1Length(new Size(width, height), false);
77 | case TextureFormat.Etc1A4:
78 | return (uint)Etc.GetEtc1Length(new Size(width, height), true);
79 | default:
80 | throw new Exception("Unsupported Texture Format " + (int)textureFormat);
81 | }
82 | }
83 |
84 | private Color GetPixel(ref int ofs, out decimal adjustment)
85 | {
86 | var col = new Color();
87 | int pixel = 0;
88 |
89 | //Exit early if trying to read out of bounds
90 | if (ofs >= Data.Length)
91 | {
92 | adjustment = 0;
93 | return col;
94 | }
95 |
96 | switch (TextureFormat)
97 | {
98 | case TextureFormat.Rgba8:
99 | col = Color.FromArgb(Data[ofs], Data[ofs + 3], Data[ofs + 2], Data[ofs + 1]);
100 | adjustment = 4;
101 | break;
102 | case TextureFormat.Rgb8:
103 | col = Color.FromArgb(255, Data[ofs + 2], Data[ofs + 1], Data[ofs]);
104 | adjustment = 3;
105 | break;
106 | case TextureFormat.Rgb5551:
107 | pixel = BitConverter.ToInt16(Data, ofs);
108 | col = Color.FromArgb(((pixel & 1) == 1) ? 255 : 0, ((pixel >> 11) & 0x1F) * 8, ((pixel >> 6) & 0x1F) * 8, ((pixel >> 1) & 0x1F) * 8);
109 | adjustment = 2;
110 | break;
111 | case TextureFormat.Rgb565:
112 | pixel = BitConverter.ToInt16(Data, ofs);
113 | col = Color.FromArgb(255, ((pixel >> 11) & 0x1F) * 8, ((pixel >> 5) & 0x3F) * 4, ((pixel) & 0x1F) * 8);
114 | adjustment = 2;
115 | break;
116 | case TextureFormat.Rgba4:
117 | pixel = BitConverter.ToInt16(Data, ofs);
118 | col = Color.FromArgb((pixel & 0xF) * 16, ((pixel >> 12) & 0xF) * 16, ((pixel >> 8) & 0xF) * 16, ((pixel >> 4) & 0xF) * 16);
119 | adjustment = 2;
120 | break;
121 | case TextureFormat.La8:
122 | pixel = Data[ofs + 1];
123 | col = Color.FromArgb(Data[ofs], pixel, pixel, pixel);
124 | adjustment = 2;
125 | break;
126 | case TextureFormat.Hilo8:
127 | col = Color.FromArgb(255, Data[ofs], Data[ofs + 1], 0);
128 | adjustment = 2;
129 | break;
130 | case TextureFormat.L8:
131 | pixel = Data[ofs];
132 | col = Color.FromArgb(255, pixel, pixel, pixel);
133 | adjustment = 1;
134 | break;
135 | case TextureFormat.A8:
136 | col = Color.FromArgb(Data[ofs], 0, 0, 0);
137 | adjustment = 1;
138 | break;
139 | case TextureFormat.La4:
140 | pixel = Data[ofs];
141 | col = Color.FromArgb((pixel & 0xF) * 16, ((pixel >> 4) & 0xF) * 16, ((pixel >> 4) & 0xF) * 16, ((pixel >> 4) & 0xF) * 16);
142 | adjustment = 1;
143 | break;
144 | case TextureFormat.L4: //TODO: Verify this is correct
145 | col = Color.FromArgb(255, (Data[ofs] & 0xF) * 16, (Data[ofs] & 0xF) * 16, (Data[ofs] & 0xF) * 16);
146 | Data[ofs] >>= 4; //Hacky
147 | adjustment = 0.5M;
148 | break;
149 | case TextureFormat.A4:
150 | col = Color.FromArgb((Data[ofs] & 0xF) * 16, 0, 0, 0);
151 | Data[ofs] >>= 4; //Hacky
152 | adjustment = 0.5M;
153 | break;
154 | default:
155 | throw new Exception("Unsupported Texture Format " + TextureFormat);
156 | }
157 |
158 | return col;
159 | }
160 |
161 | private void GetPlainRasterData(Bitmap bmp)
162 | {
163 | var ofs = 0;
164 | decimal adjustment = 0;
165 | for (int y = 0; y < Height; y += 8)
166 | {
167 | for (int x = 0; x < Width; x += 8)
168 | {
169 | for (int k = 0; k < 8 * 8; k++)
170 | {
171 | var i = _tileOrder[k] % 8;
172 | var j = (_tileOrder[k] - i) / 8;
173 | var nosub = adjustment == 0.5M;
174 | var pix = GetPixel(ref ofs, out adjustment);
175 | bmp.SetPixel(Math.Min(x + i, Width), Math.Min(y + j, Height), pix);
176 |
177 | ofs += Convert.ToInt32(Math.Ceiling(adjustment));
178 |
179 | if (adjustment == 0.5M && !nosub)
180 | {
181 | //Hacky I know
182 | ofs--;
183 | }
184 | else
185 | {
186 | adjustment = 0;
187 | }
188 | }
189 | }
190 | }
191 | }
192 |
193 | private void GetPlainRasterData(ref byte[] data)
194 | {
195 | var ofs = 0;
196 | decimal adjustment = 0;
197 | for (int y = 0; y < Height; y += 8)
198 | {
199 | for (int x = 0; x < Width; x += 8)
200 | {
201 | for (int k = 0; k < 8 * 8; k++)
202 | {
203 | var i = _tileOrder[k] % 8;
204 | var j = (_tileOrder[k] - i) / 8;
205 | //bmp.SetPixel(Math.Min(x + i, Width), Math.Min(y + j, Height), GetPixel(ref ofs));
206 | var nosub = adjustment == 0.5M;
207 | var pix = GetPixel(ref ofs, out adjustment);
208 | var argb = pix.ToArgb();
209 | var bytes = BitConverter.GetBytes(argb);
210 | Array.Copy(bytes, 0, data, ((x + i) + (y + j) * Width) * 4, bytes.Length);
211 |
212 | ofs += Convert.ToInt32(Math.Ceiling(adjustment));
213 |
214 | if (adjustment == 0.5M && !nosub)
215 | {
216 | //Hacky I know
217 | ofs--;
218 | }
219 | else
220 | {
221 | adjustment = 0;
222 | }
223 | }
224 | }
225 | }
226 | }
227 |
228 | public byte[] GetRGBA()
229 | {
230 | var data = new byte[Width * Height * 4];
231 |
232 | switch (TextureFormat)
233 | {
234 | case TextureFormat.Invalid:
235 | break;
236 | case TextureFormat.Etc1:
237 | case TextureFormat.Etc1A4:
238 | for (int y = 0; y < Height; y++)
239 | {
240 | Etc.GetEtc1RasterData(Data, new Size(Width, Height), y, HasAlpha, data, 0);
241 | }
242 | break;
243 | default:
244 | GetPlainRasterData(ref data);
245 | break;
246 | }
247 | return data;
248 | }
249 |
250 | public byte[] GetScrambledTextureData(Bitmap bmp)
251 | {
252 | Width = IsPowerOfTwo(bmp.Width) ? bmp.Width : nlpo2(bmp.Width);
253 | Height = IsPowerOfTwo(bmp.Height) ? bmp.Height : nlpo2(bmp.Height);
254 |
255 | MemoryStream stream = new MemoryStream();
256 | using (BinaryWriter output = new BinaryWriter(stream))
257 | {
258 | for (int y = 0; y < Height; y += 8)
259 | {
260 | for (int x = 0; x < Width; x += 8)
261 | {
262 | for (int k = 0; k < 8 * 8; k++)
263 | {
264 | var i = _tileOrder[k] % 8;
265 | var j = (_tileOrder[k] - i) / 8;
266 | var pix = bmp.GetPixel(Math.Min(x + i, Width), Math.Min(y + j, Height));
267 |
268 | if (bmp.PixelFormat == PixelFormat.Format32bppArgb)
269 | {
270 | output.Write(pix.A);
271 | output.Write(pix.B);
272 | output.Write(pix.G);
273 | output.Write(pix.R);
274 | }
275 | else
276 | {
277 | output.Write(pix.B);
278 | output.Write(pix.G);
279 | output.Write(pix.R);
280 | }
281 | }
282 | }
283 | }
284 | }
285 |
286 | return stream.GetBuffer();
287 | }
288 |
289 | public Bitmap GetBitmap()
290 | {
291 | var bmp = new Bitmap(Width, Height);
292 |
293 | switch (TextureFormat)
294 | {
295 | case TextureFormat.Invalid:
296 | break;
297 | case TextureFormat.Etc1:
298 | case TextureFormat.Etc1A4:
299 | for (int y = 0; y < Height; y++)
300 | {
301 | Etc.GetEtc1RasterData(Data, new Size(Width, Height), y, HasAlpha, bmp, 0);
302 | }
303 | break;
304 | default:
305 | GetPlainRasterData(bmp);
306 | break;
307 | }
308 | return bmp;
309 | }
310 |
311 | static bool IsPowerOfTwo(int x)
312 | {
313 | return (x != 0) && ((x & (x - 1)) == 0);
314 | }
315 |
316 | int nlpo2(int x)
317 | {
318 | x--; // comment out to always take the next biggest power of two, even if x is already a power of two
319 | x |= (x >> 1);
320 | x |= (x >> 2);
321 | x |= (x >> 4);
322 | x |= (x >> 8);
323 | x |= (x >> 16);
324 | return (x + 1);
325 | }
326 | }
327 | }
--------------------------------------------------------------------------------
/FEAT/ctpktool/TextureFormat.cs:
--------------------------------------------------------------------------------
1 | // Code from Normmatt's texturipper (with permission)
2 | namespace ctpktool
3 | {
4 | public enum TextureFormat
5 | {
6 | Invalid = -1,
7 | Rgba8 = 0,
8 | Rgb8 = 1,
9 | Rgb5551 = 2,
10 | Rgb565 = 3,
11 | Rgba4 = 4,
12 | La8 = 5,
13 | Hilo8 = 6,
14 | L8 = 7,
15 | A8 = 8,
16 | La4 = 9,
17 | L4 = 10,
18 | A4 = 11,
19 | Etc1 = 12,
20 | Etc1A4 = 13
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/FEAT/obj/x86/Debug/DesignTimeResolveAssemblyReferences.cache:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/FEAT/obj/x86/Debug/DesignTimeResolveAssemblyReferences.cache
--------------------------------------------------------------------------------
/FEAT/obj/x86/Debug/DesignTimeResolveAssemblyReferencesInput.cache:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/FEAT/obj/x86/Debug/DesignTimeResolveAssemblyReferencesInput.cache
--------------------------------------------------------------------------------
/FEAT/obj/x86/Debug/FEAT.csproj.FileListAbsolute.txt:
--------------------------------------------------------------------------------
1 | C:\Users\robot\Desktop\FEAT-master\FEAT\obj\x86\Debug\FEAT.csprojResolveAssemblyReference.cache
2 | C:\Users\robot\Desktop\FEAT-master\FEAT\obj\x86\Debug\Fire_Emblem_Awakening_Archive_Tool.Form1.resources
3 | C:\Users\robot\Desktop\FEAT-master\FEAT\obj\x86\Debug\Fire_Emblem_Awakening_Archive_Tool.Properties.Resources.resources
4 | C:\Users\robot\Desktop\FEAT-master\FEAT\obj\x86\Debug\FEAT.csproj.GenerateResource.Cache
5 | C:\Users\robot\Desktop\FEAT-master\FEAT\bin\Debug\ETC1.dll
6 | C:\Users\robot\Desktop\FEAT-master\FEAT\bin\Debug\FEAT.exe
7 | C:\Users\robot\Desktop\FEAT-master\FEAT\bin\Debug\FEAT.pdb
8 | C:\Users\robot\Desktop\FEAT-master\FEAT\obj\x86\Debug\FEAT.exe
9 | C:\Users\robot\Desktop\FEAT-master\FEAT\obj\x86\Debug\FEAT.pdb
10 |
--------------------------------------------------------------------------------
/FEAT/obj/x86/Debug/FEAT.csproj.GenerateResource.Cache:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/FEAT/obj/x86/Debug/FEAT.csproj.GenerateResource.Cache
--------------------------------------------------------------------------------
/FEAT/obj/x86/Debug/FEAT.csprojResolveAssemblyReference.cache:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/FEAT/obj/x86/Debug/FEAT.csprojResolveAssemblyReference.cache
--------------------------------------------------------------------------------
/FEAT/obj/x86/Debug/FEAT.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/FEAT/obj/x86/Debug/FEAT.exe
--------------------------------------------------------------------------------
/FEAT/obj/x86/Debug/FEAT.pdb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/FEAT/obj/x86/Debug/FEAT.pdb
--------------------------------------------------------------------------------
/FEAT/obj/x86/Debug/Fire_Emblem_Awakening_Archive_Tool.Form1.resources:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/FEAT/obj/x86/Debug/Fire_Emblem_Awakening_Archive_Tool.Form1.resources
--------------------------------------------------------------------------------
/FEAT/obj/x86/Debug/Fire_Emblem_Awakening_Archive_Tool.Properties.Resources.resources:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/FEAT/obj/x86/Debug/Fire_Emblem_Awakening_Archive_Tool.Properties.Resources.resources
--------------------------------------------------------------------------------
/FEAT/obj/x86/Debug/TempPE/Properties.Resources.Designer.cs.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/FEAT/obj/x86/Debug/TempPE/Properties.Resources.Designer.cs.dll
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FEAT
2 |
3 | 
4 |
5 | Fire Emblem Archive Tool (A tool to automatically extract data from 3DS Fire Emblem archives)
6 |
7 | Credit to [ctpktool](https://github.com/polaris-/ctpktool) and [DSDecmp](https://github.com/einstein95/dsdecmp), from which code is used for ctpk unpacking and LZ decompression, respectively.
8 |
9 | Forked v1.1:
10 |
11 | - Removed prompt to rebuild text files.
12 |
13 | - Updated cptktool files so it would work instead of crashing and doing nothing.
14 |
15 | - Added a program icon.
16 |
17 | Forked v1.2:
18 |
19 | - Removed check that made certain Fates models crash. It would make sure the first four bytes of the input file were valid, but the value was different on certain Fates models and failed. There's no reason to have this check, as the program doesn't actually do anything if input the file actually IS invalid.
20 |
21 | - Reformatted README.md to be easier to read.
22 |
23 | - Embedded pictures instead of using Imgur for hosting.
24 |
25 |
26 |
27 | 
28 |
29 |
30 | 
31 |
--------------------------------------------------------------------------------
/example_pictures/Newicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/example_pictures/Newicon.png
--------------------------------------------------------------------------------
/example_pictures/UI.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/example_pictures/UI.png
--------------------------------------------------------------------------------
/example_pictures/UI_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SciresM/FEAT/66ba34be648006874df9d103160b2c0b1478f8df/example_pictures/UI_2.png
--------------------------------------------------------------------------------