├── .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 | ![UI](example_pictures/UI.png) 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 | ![newicon](example_pictures/newicon.png) 28 | 29 | 30 | ![icon](example_pictures/UI_2.png) 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 --------------------------------------------------------------------------------