diff --git a/.gitattributes b/.gitattributes index dfe0770..1ff0c42 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,63 @@ -# Auto detect text files and perform LF normalization +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### * text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c4efe2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,261 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/E17_CPS.sln b/E17_CPS.sln new file mode 100644 index 0000000..8f7c66d --- /dev/null +++ b/E17_CPS.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27004.2008 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "E17_CPS", "E17_CPS\E17_CPS.csproj", "{30BDE65D-F4B8-48A6-978D-EEBD02785575}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {30BDE65D-F4B8-48A6-978D-EEBD02785575}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30BDE65D-F4B8-48A6-978D-EEBD02785575}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30BDE65D-F4B8-48A6-978D-EEBD02785575}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30BDE65D-F4B8-48A6-978D-EEBD02785575}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {09FA3B18-9D77-40AA-A9A8-B4FD8709970C} + EndGlobalSection +EndGlobal diff --git a/E17_CPS/App.config b/E17_CPS/App.config new file mode 100644 index 0000000..731f6de --- /dev/null +++ b/E17_CPS/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/E17_CPS/BMP_format.cs b/E17_CPS/BMP_format.cs new file mode 100644 index 0000000..93b7d7b --- /dev/null +++ b/E17_CPS/BMP_format.cs @@ -0,0 +1,176 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Drawing; + +namespace E17_CPS +{ + class BMP_format + { + // Credits and source: + // http://www.wischik.com/lu/programmer/1bpp.html + // Quantization done with the median cut algorithm + + /// + /// Copies a bitmap into a 1bpp/8bpp bitmap of the same dimensions, fast + /// + /// original bitmap + /// 1 or 8, target bpp + /// a 1bpp copy of the bitmap + public static System.Drawing.Bitmap CopyToBpp(System.Drawing.Bitmap b, int bpp) + { + if (bpp != 1 && bpp != 8) throw new System.ArgumentException("1 or 8", "bpp"); + + // Built into Windows GDI is the ability to convert + // bitmaps from one format to another. Most of the time, this + // job is actually done by the graphics hardware accelerator card + // and so is extremely fast. The rest of the time, the job is done by + // very fast native code. + // We will call into this GDI functionality from C#. + // + // Our plan: + // (1) Convert our Bitmap into a GDI hbitmap (ie. copy unmanaged->managed) + // (2) Create an indexed GDI hbitmap + // (3) Use GDI "BitBlt" function to copy from hbitmap into monochrome (as above) + // (4) Convert the monochrone hbitmap into a Bitmap (ie. copy unmanaged->managed) + + // Step (1) + int w = b.Width, h = b.Height; + IntPtr hbm = b.GetHbitmap(); + + // Step (2): create the monochrome bitmap. + // "BITMAPINFO" is an interop-struct which we define below. + // In GDI terms, it's a BITMAPHEADERINFO followed by an array of two RGBQUADs + BITMAPINFO bmi = new BITMAPINFO(); + bmi.biSize = 40; // the size of the BITMAPHEADERINFO struct + bmi.biWidth = w; + bmi.biHeight = h; + bmi.biPlanes = 1; // "planes" are confusing. We always use just 1. Read MSDN for more info. + bmi.biBitCount = (short)bpp; // ie. 1bpp or 8bpp + bmi.biCompression = BI_RGB; // ie. the pixels in our RGBQUAD table are stored as RGBs, not palette indexes + bmi.biSizeImage = (uint)(((w + 7) & 0xFFFFFFF8) * h / 8); + bmi.biXPelsPerMeter = 1000000; // not really important + bmi.biYPelsPerMeter = 1000000; // not really important + // Now for the colour table. + uint ncols = (uint)1 << bpp; // 2 colours for 1bpp; 256 colours for 8bpp + bmi.biClrUsed = ncols; + bmi.biClrImportant = ncols; + bmi.cols = new uint[256]; // The structure always has fixed size 256, even if we end up using fewer colours + + // Create pallete + if (bpp == 1) + { + bmi.cols[0] = MAKERGB(0, 0, 0); + bmi.cols[1] = MAKERGB(255, 255, 255); + } + else { + for (int i = 0; i < ncols; i++) + bmi.cols[i] = MAKERGB(i, i, i); + } + // For 8bpp we've created an palette with just greyscale colours. + // You can set up any palette you want here. Here are some possibilities: + // greyscale: for (int i=0; i<256; i++) bmi.cols[i]=MAKERGB(i,i,i); + // rainbow: bmi.biClrUsed=216; bmi.biClrImportant=216; int[] colv=new int[6]{0,51,102,153,204,255}; + // for (int i=0; i<216; i++) bmi.cols[i]=MAKERGB(colv[i/36],colv[(i/6)%6],colv[i%6]); + // optimal: a difficult topic: http://en.wikipedia.org/wiki/Color_quantization + // + // Now create the indexed bitmap "hbm0" + IntPtr bits0; // not used for our purposes. It returns a pointer to the raw bits that make up the bitmap. + IntPtr hbm0 = CreateDIBSection(IntPtr.Zero, ref bmi, DIB_RGB_COLORS, out bits0, IntPtr.Zero, 0); + // + // Step (3): use GDI's BitBlt function to copy from original hbitmap into monocrhome bitmap + // GDI programming is kind of confusing... nb. The GDI equivalent of "Graphics" is called a "DC". + IntPtr sdc = GetDC(IntPtr.Zero); // First we obtain the DC for the screen + // Next, create a DC for the original hbitmap + IntPtr hdc = CreateCompatibleDC(sdc); SelectObject(hdc, hbm); + // and create a DC for the monochrome hbitmap + IntPtr hdc0 = CreateCompatibleDC(sdc); SelectObject(hdc0, hbm0); + // Now we can do the BitBlt: + BitBlt(hdc0, 0, 0, w, h, hdc, 0, 0, SRCCOPY); + // Step (4): convert this monochrome hbitmap back into a Bitmap: + System.Drawing.Bitmap b0 = System.Drawing.Bitmap.FromHbitmap(hbm0); + // + // Finally some cleanup. + DeleteDC(hdc); + DeleteDC(hdc0); + ReleaseDC(IntPtr.Zero, sdc); + DeleteObject(hbm); + DeleteObject(hbm0); + // + return b0; + } + + + /// + /// Draws a bitmap onto the screen. + /// + /// the bitmap to draw on the screen + /// x screen coordinate + /// y screen coordinate + static void SplashImage(System.Drawing.Bitmap b, int x, int y) + { // Drawing onto the screen is supported by GDI, but not by the Bitmap/Graphics class. + // So we use interop: + // (1) Copy the Bitmap into a GDI hbitmap + IntPtr hbm = b.GetHbitmap(); + // (2) obtain the GDI equivalent of a "Graphics" for the screen + IntPtr sdc = GetDC(IntPtr.Zero); + // (3) obtain the GDI equivalent of a "Graphics" for the hbitmap + IntPtr hdc = CreateCompatibleDC(sdc); + SelectObject(hdc, hbm); + // (4) Draw from the hbitmap's "Graphics" onto the screen's "Graphics" + BitBlt(sdc, x, y, b.Width, b.Height, hdc, 0, 0, SRCCOPY); + // and do boring GDI cleanup: + DeleteDC(hdc); + ReleaseDC(IntPtr.Zero, sdc); + DeleteObject(hbm); + } + + [System.Runtime.InteropServices.DllImport("gdi32.dll")] + public static extern bool DeleteObject(IntPtr hObject); + + [System.Runtime.InteropServices.DllImport("user32.dll")] + public static extern IntPtr GetDC(IntPtr hwnd); + + [System.Runtime.InteropServices.DllImport("gdi32.dll")] + public static extern IntPtr CreateCompatibleDC(IntPtr hdc); + + [System.Runtime.InteropServices.DllImport("user32.dll")] + public static extern int ReleaseDC(IntPtr hwnd, IntPtr hdc); + + [System.Runtime.InteropServices.DllImport("gdi32.dll")] + public static extern int DeleteDC(IntPtr hdc); + + [System.Runtime.InteropServices.DllImport("gdi32.dll")] + public static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj); + + [System.Runtime.InteropServices.DllImport("gdi32.dll")] + public static extern int BitBlt(IntPtr hdcDst, int xDst, int yDst, int w, int h, IntPtr hdcSrc, int xSrc, int ySrc, int rop); + static int SRCCOPY = 0x00CC0020; + + [System.Runtime.InteropServices.DllImport("gdi32.dll")] + static extern IntPtr CreateDIBSection(IntPtr hdc, ref BITMAPINFO bmi, uint Usage, out IntPtr bits, IntPtr hSection, uint dwOffset); + static uint BI_RGB = 0; + static uint DIB_RGB_COLORS = 0; + [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind.Sequential)] + public struct BITMAPINFO + { + public uint biSize; + public int biWidth, biHeight; + public short biPlanes, biBitCount; + public uint biCompression, biSizeImage; + public int biXPelsPerMeter, biYPelsPerMeter; + public uint biClrUsed, biClrImportant; + [System.Runtime.InteropServices.MarshalAs(System.Runtime.InteropServices.UnmanagedType.ByValArray, SizeConst = 256)] + public uint[] cols; + } + + static uint MAKERGB(int r, int g, int b) + { + return ((uint)(b & 255)) | ((uint)((r & 255) << 8)) | ((uint)((g & 255) << 16)); + } + + + } +} \ No newline at end of file diff --git a/E17_CPS/CPS_format.cs b/E17_CPS/CPS_format.cs new file mode 100644 index 0000000..baab14b --- /dev/null +++ b/E17_CPS/CPS_format.cs @@ -0,0 +1,458 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace E17_CPS +{ + class CPS_format + { + [StructLayout(LayoutKind.Explicit)] + struct CPSHeader + { + [FieldOffset(0x00)] + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)] + public string ID; + + [FieldOffset(0x04)] + public Int32 CPSFileSize; + + [FieldOffset(0x08)] + public Int16 Version; + + [FieldOffset(0x0A)] + public Int16 CompressionType; + + [FieldOffset(0x0C)] + public Int32 UnpackedFileSize; + + [FieldOffset(0x10)] + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)] + public string SubHead; + } + + public static T ByteToType(BinaryReader reader) + { + byte[] bytes = reader.ReadBytes(Marshal.SizeOf(typeof(T))); + + GCHandle handle = GCHandle.Alloc(bytes, GCHandleType.Pinned); + T theStructure = (T)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(T)); + handle.Free(); + + return theStructure; + } + + public static MemoryStream Compress(string filename, bool compress = true, string savepath = null) + { + FileStream fin_stream = new FileStream(filename, FileMode.Open); + + // Copy to a memory stream so that we can modify it as we like + MemoryStream stream = new MemoryStream(); + fin_stream.CopyTo(stream); + fin_stream.Close(); + fin_stream.Dispose(); + + MemoryStream stream_out = Compress(stream, compress, savepath); + stream.Dispose(); + + return stream_out; + } + + public static MemoryStream Compress(MemoryStream stream, bool compress = true, string savepath = null) + { + MemoryStream stream_compressed = new MemoryStream(); + + stream.Position = 0; + var prt_size = stream.Length; + var cmp_size = prt_size + 0x14 + 4; + + /* + * 0xC0 - Duplicate one byte from input many times + * 0x80 - Duplicate one byte from output many times + * 0x40 - Copy a sequence of bytes from input many times + * 0x00 - Copy input to output directly + * + * + * + * Anything with 0x20 will simply extend the amount + * of bytes to read/write + * + * eg. (3F 01) 0011 1111 0000 0001 + * Total count: (3F & 1F) + 1 + (01 << 5) + * 0001 1111 + 1 + 0010 0000 + * 0011 1111 + 1 + * 0100 0000 + */ + + + if (compress) + { + // Get the difference of bytes to find repeated sequences + byte[] byte_stream = stream.ToArray(); + byte[] byte_difference = stream.ToArray(); + for (int i = 0; i < byte_difference.Length - 1; i++) + byte_difference[i] = (byte)(byte_stream[i + 1] - byte_stream[i]); + + byte_difference[byte_difference.Length - 1] = 1; // Set last byte as a difference + + bool repeated = false; + int count = 0; + for (int i = 0; i < byte_difference.Length; i++) + { + if (byte_difference[i] == 0x00 && i != byte_difference.Length - 1) + { + if (!repeated) { repeated = true; count = 0; } + count++; + } + else + { + if (repeated) + { + // Write to stream + count--; + if (count > 0x1F) + { + byte opcode = 0xC0; + opcode += (byte)(count & 0x1F); + stream_compressed.WriteByte(opcode); + stream_compressed.WriteByte((byte)(count)); + } + + repeated = false; + } + else + { + // Copy to stream directly + stream_compressed.WriteByte(byte_stream[i]); + } + } + } + } + else + { + stream.CopyTo(stream_compressed); + } + + // Create final file + MemoryStream stream_out = new MemoryStream(); + BinaryWriter writer = new BinaryWriter(stream_out); + + writer.Seek(0, SeekOrigin.Begin); + writer.Write(("CPS" + '\0').ToCharArray()); + writer.Write((Int32)cmp_size); + if (compress) + { + writer.Write((Int16)0x0066); // Version + writer.Write((Int16)0x0101); // Compression Type + } + else + { + writer.Write((Int16)0x0066); // Version + writer.Write((Int16)0x0000); // Compression Type + } + writer.Write((Int32)prt_size); + writer.Write("bmp\0".ToCharArray()); + writer.Write(stream_compressed.ToArray()); + writer.Write((Int32)0x07534682); //Decryption setting (ie. no decryption) + + + + if (savepath != null) + { + if (File.Exists(savepath)) + File.Delete(savepath); + + FileStream fout_stream = new FileStream(savepath, FileMode.OpenOrCreate, FileAccess.Write); + stream_out.Seek(0, SeekOrigin.Begin); + stream_out.CopyTo(fout_stream); + fout_stream.Flush(); + fout_stream.Close(); + fout_stream.Dispose(); + } + + stream_compressed.Dispose(); + + stream_out.Seek(0, SeekOrigin.Begin); + return stream_out; + } + + + public static bool Decompress(string filename) + { + FileStream fin_stream = new FileStream(filename, FileMode.Open); + + // Copy to a memory stream so that we can modify it as we like + MemoryStream stream = new MemoryStream(); + fin_stream.CopyTo(stream); + fin_stream.Close(); + fin_stream.Dispose(); + + stream.Seek(0, SeekOrigin.Begin); + BinaryReader reader = new BinaryReader(stream); + BinaryWriter writer = new BinaryWriter(stream); + + CPSHeader cps_header = ByteToType(reader); + + if (cps_header.Version != 0x66 || !cps_header.ID.Equals("CPS")) + { + // Unkown file parameters + MessageBox.Show("Unsupported CPS version"); + + writer.Dispose(); + reader.Dispose(); + stream.Dispose(); + + return false; + } + + // ===== + // DECRYPTION + + // Read the last 4 bytes. Binary reader doesn't have a Seek function, so we move the underlying stream + stream.Seek(cps_header.CPSFileSize - 4, SeekOrigin.Begin); + Int32 f = reader.ReadInt32() - 0x07534682; + + if (f != 0) + { + stream.Seek(f, SeekOrigin.Begin); + Int32 b = reader.ReadInt32() + f + 0x03786425; + + int offset = 0x10; + while (offset <= cps_header.CPSFileSize - 4) + { + if (offset != f) + { + stream.Seek(offset, SeekOrigin.Begin); + int v = reader.ReadInt32(); + stream.Seek(offset, SeekOrigin.Begin); + writer.Write(v - cps_header.CPSFileSize - b); + } + + b = b * 0x41C64E6D + 0x9B06; + offset += 4; + } + } + + if (File.Exists(filename + ".decrypted")) + File.Delete(filename + ".decrypted"); + FileStream fout_stream = new FileStream(filename + ".decrypted", FileMode.OpenOrCreate, FileAccess.Write); + stream.WriteTo(fout_stream); + fout_stream.Flush(); + fout_stream.Close(); + fout_stream.Dispose(); + + + // ========= + // UNPACK + + stream.Seek(cps_header.CPSFileSize - 4, SeekOrigin.Begin); + writer.Write((int)0); + + byte[] input = stream.ToArray(); + byte[] output = new byte[cps_header.UnpackedFileSize + 0x10]; + int input_ptr = 0x13; + int output_ptr = 0; + + if ((cps_header.CompressionType & 0x01) > 0) + { + int w = 0; + Int32 z = 0, x1 = 0, y = 0, x2 = 0; + + bool done = false; + + /* + * 0xC0 - Duplicate one byte from input many times + * 0x80 - Duplicate one byte from output (seek prev offset) many times + * 0x40 - Copy a sequence of bytes from input many times + * 0x00 - Copy input to output directly + * + * + * + * Anything with 0x20 will simply extend the amount + * of bytes to read/write + * + * eg. (3F 01) 0011 1111 0000 0001 + * Total count: (3F & 1F) + 1 + (01 << 5) + * 0001 1111 + 1 + 0010 0000 + * 0011 1111 + 1 + * 0100 0000 + */ + + while (w <= cps_header.UnpackedFileSize && !done) + { + byte c = input[input_ptr]; + input_ptr++; + + if ((c & 0x80) > 0) + { + if ((c & 0x40) > 0) + { + z = c & 0x1F + 2; + + if ((c & 0x20) > 0) + { + z += input[input_ptr] << 5; + input_ptr++; + } + + c = input[input_ptr]; + input_ptr++; + x1 = 0; + + while (x1 < z) + { + if (w <= cps_header.UnpackedFileSize) + { + output[output_ptr] = c; + output_ptr++; + w++; + x1++; + } + else + { + done = true; + break; + } + } + } + + else + { + int prev_pos = output_ptr; + prev_pos -= ((c & 0x03) << 8) + input[input_ptr] + 1; + input_ptr++; + z = ((c >> 2) & 0x0F) + 2; + + x1 = 0; + while (x1 < z) + { + if (w <= cps_header.UnpackedFileSize) + { + output[output_ptr] = output[prev_pos]; + + output_ptr++; + w++; + x1++; + } + else + { + done = true; + break; + } + } + } + } + + else if ((c & 0x40) > 0) + { + x2 = 0; + z = (c & 0x3F) + 2; + y = output[output_ptr] + 1; + output_ptr++; + + while (x2 < y) + { + x1 = 0; + while (x1 < z) + { + if (w <= cps_header.UnpackedFileSize) + { + output[output_ptr] = input[input_ptr + x1]; + + output_ptr++; + w++; + x1++; + } + else + { + done = true; + break; + } + } + + x2++; + } + + input_ptr += z; + } + + else + { + z = (c & 0x1F) + 1; + if ((c & 0x20) > 0) + { + z += input[input_ptr] << 5; + input_ptr++; + } + + x1 = 0; + + while (x1 < z) + { + if (w <= cps_header.UnpackedFileSize) + { + output[output_ptr] = input[input_ptr]; + + input_ptr++; + output_ptr++; + w++; + x1++; + } + else + { + done = true; + break; + } + } + } + } + } + + + + + + + else if ((cps_header.CompressionType & 0x02) > 0) + { + MessageBox.Show("Unsupported unpacking type 2"); + + writer.Dispose(); + reader.Dispose(); + stream.Dispose(); + + return false; + } + else + { + // No unpacking needed + output = stream.ToArray(); + } + + + // Write to file + if (File.Exists(filename + ".unpacked")) + File.Delete(filename + ".unpacked"); + + FileStream funp_stream = new FileStream(filename + ".unpacked", FileMode.OpenOrCreate, FileAccess.Write); + funp_stream.Write(output, 0, output.Length); + funp_stream.Flush(); + funp_stream.Close(); + funp_stream.Dispose(); + + + // ========= + // CLEANUP + + writer.Dispose(); + reader.Dispose(); + stream.Dispose(); + + return true; + } + } +} diff --git a/E17_CPS/E17_CPS.csproj b/E17_CPS/E17_CPS.csproj new file mode 100644 index 0000000..5caef8f --- /dev/null +++ b/E17_CPS/E17_CPS.csproj @@ -0,0 +1,94 @@ + + + + + Debug + AnyCPU + {30BDE65D-F4B8-48A6-978D-EEBD02785575} + WinExe + E17_CPS + E17_CPS + v4.6.1 + 512 + true + + + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\Microsoft.WindowsAPICodePack.Core.1.1.0\lib\Microsoft.WindowsAPICodePack.dll + + + ..\packages\Microsoft.WindowsAPICodePack.Shell.1.1.0\lib\Microsoft.WindowsAPICodePack.Shell.dll + + + + + + + + + + + + + + + + + + Form + + + Form1.cs + + + + + + Form1.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + \ No newline at end of file diff --git a/E17_CPS/Form1.Designer.cs b/E17_CPS/Form1.Designer.cs new file mode 100644 index 0000000..0876b62 --- /dev/null +++ b/E17_CPS/Form1.Designer.cs @@ -0,0 +1,171 @@ +namespace E17_CPS +{ + partial class Form1 + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.buttonSaveCPS = new System.Windows.Forms.Button(); + this.label1 = new System.Windows.Forms.Label(); + this.textboxDirectoryImage = new System.Windows.Forms.TextBox(); + this.buttonBrowseImage = new System.Windows.Forms.Button(); + this.listboxFiles = new System.Windows.Forms.ListBox(); + this.buttonBatch = new System.Windows.Forms.Button(); + this.picturebox = new System.Windows.Forms.PictureBox(); + this.buttonBrowseCPS = new System.Windows.Forms.Button(); + this.textboxDirectoryCPS = new System.Windows.Forms.TextBox(); + this.label2 = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)(this.picturebox)).BeginInit(); + this.SuspendLayout(); + // + // buttonSaveCPS + // + this.buttonSaveCPS.Location = new System.Drawing.Point(431, 46); + this.buttonSaveCPS.Name = "buttonSaveCPS"; + this.buttonSaveCPS.Size = new System.Drawing.Size(110, 34); + this.buttonSaveCPS.TabIndex = 1; + this.buttonSaveCPS.Text = "Convert to CPS"; + this.buttonSaveCPS.UseVisualStyleBackColor = true; + this.buttonSaveCPS.Click += new System.EventHandler(this.buttonSaveCPS_Click); + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(13, 20); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(81, 13); + this.label1.TabIndex = 2; + this.label1.Text = "Image Directory"; + // + // textboxDirectoryImage + // + this.textboxDirectoryImage.Location = new System.Drawing.Point(100, 17); + this.textboxDirectoryImage.Name = "textboxDirectoryImage"; + this.textboxDirectoryImage.Size = new System.Drawing.Size(219, 20); + this.textboxDirectoryImage.TabIndex = 3; + this.textboxDirectoryImage.TextChanged += new System.EventHandler(this.textboxDirectory_TextChanged); + // + // buttonBrowseImage + // + this.buttonBrowseImage.Location = new System.Drawing.Point(325, 9); + this.buttonBrowseImage.Name = "buttonBrowseImage"; + this.buttonBrowseImage.Size = new System.Drawing.Size(75, 34); + this.buttonBrowseImage.TabIndex = 4; + this.buttonBrowseImage.Text = "Browse"; + this.buttonBrowseImage.UseVisualStyleBackColor = true; + this.buttonBrowseImage.Click += new System.EventHandler(this.buttonBrowse_Click); + // + // listboxFiles + // + this.listboxFiles.FormattingEnabled = true; + this.listboxFiles.Location = new System.Drawing.Point(16, 86); + this.listboxFiles.Name = "listboxFiles"; + this.listboxFiles.Size = new System.Drawing.Size(120, 303); + this.listboxFiles.TabIndex = 5; + this.listboxFiles.SelectedIndexChanged += new System.EventHandler(this.listboxFiles_SelectedIndexChanged); + // + // buttonBatch + // + this.buttonBatch.Location = new System.Drawing.Point(432, 9); + this.buttonBatch.Name = "buttonBatch"; + this.buttonBatch.Size = new System.Drawing.Size(110, 34); + this.buttonBatch.TabIndex = 6; + this.buttonBatch.Text = "Batch Convert to CPS"; + this.buttonBatch.UseVisualStyleBackColor = true; + this.buttonBatch.Click += new System.EventHandler(this.buttonBatch_Click); + // + // picturebox + // + this.picturebox.Location = new System.Drawing.Point(142, 86); + this.picturebox.Name = "picturebox"; + this.picturebox.Size = new System.Drawing.Size(400, 300); + this.picturebox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.picturebox.TabIndex = 7; + this.picturebox.TabStop = false; + // + // buttonBrowseCPS + // + this.buttonBrowseCPS.Location = new System.Drawing.Point(325, 46); + this.buttonBrowseCPS.Name = "buttonBrowseCPS"; + this.buttonBrowseCPS.Size = new System.Drawing.Size(75, 34); + this.buttonBrowseCPS.TabIndex = 10; + this.buttonBrowseCPS.Text = "Browse"; + this.buttonBrowseCPS.UseVisualStyleBackColor = true; + this.buttonBrowseCPS.Click += new System.EventHandler(this.buttonBrowseCPS_Click); + // + // textboxDirectoryCPS + // + this.textboxDirectoryCPS.Location = new System.Drawing.Point(100, 54); + this.textboxDirectoryCPS.Name = "textboxDirectoryCPS"; + this.textboxDirectoryCPS.Size = new System.Drawing.Size(219, 20); + this.textboxDirectoryCPS.TabIndex = 9; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(13, 57); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(73, 13); + this.label2.TabIndex = 8; + this.label2.Text = "CPS Directory"; + // + // Form1 + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(553, 396); + this.Controls.Add(this.buttonBrowseCPS); + this.Controls.Add(this.textboxDirectoryCPS); + this.Controls.Add(this.label2); + this.Controls.Add(this.picturebox); + this.Controls.Add(this.buttonBatch); + this.Controls.Add(this.listboxFiles); + this.Controls.Add(this.buttonBrowseImage); + this.Controls.Add(this.textboxDirectoryImage); + this.Controls.Add(this.label1); + this.Controls.Add(this.buttonSaveCPS); + this.Name = "Form1"; + this.Text = "Ever17 CPS Converter"; + ((System.ComponentModel.ISupportInitialize)(this.picturebox)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private System.Windows.Forms.Button buttonSaveCPS; + private System.Windows.Forms.Label label1; + private System.Windows.Forms.TextBox textboxDirectoryImage; + private System.Windows.Forms.Button buttonBrowseImage; + private System.Windows.Forms.ListBox listboxFiles; + private System.Windows.Forms.Button buttonBatch; + private System.Windows.Forms.PictureBox picturebox; + private System.Windows.Forms.Button buttonBrowseCPS; + private System.Windows.Forms.TextBox textboxDirectoryCPS; + private System.Windows.Forms.Label label2; + } +} + diff --git a/E17_CPS/Form1.cs b/E17_CPS/Form1.cs new file mode 100644 index 0000000..d9243d6 --- /dev/null +++ b/E17_CPS/Form1.cs @@ -0,0 +1,327 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; +using Microsoft.WindowsAPICodePack.Dialogs; + +// Ported from https://github.com/dsp2003/ae/blob/master/src/format/graphics/AG_KID_Engine.pas + +namespace E17_CPS +{ + public partial class Form1 : Form + { + // File settings + CommonOpenFileDialog openFolderDialog = new CommonOpenFileDialog(); + FileSystemWatcher folderWatcher = new FileSystemWatcher(); + private string[] image_extensions = { ".png", ".jpeg", ".jpg", ".gif", ".bmp", ".prt" }; + string imageFullPath = null; + string imageName; + Bitmap image = null; + + public Form1() + { + InitializeComponent(); + + // Set up filewatcher + folderWatcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName; + folderWatcher.Filter = "*"; + folderWatcher.Changed += new FileSystemEventHandler(OnFolderChange); + folderWatcher.Renamed += new RenamedEventHandler(OnFolderChange); + folderWatcher.Deleted += new FileSystemEventHandler(OnFolderChange); + + // Set up folder selecter + openFolderDialog.Title = "Select a folder"; + openFolderDialog.IsFolderPicker = true; + openFolderDialog.AddToMostRecentlyUsedList = false; + openFolderDialog.AllowNonFileSystemItems = false; + openFolderDialog.EnsureFileExists = true; + openFolderDialog.EnsurePathExists = true; + openFolderDialog.EnsureReadOnly = false; + openFolderDialog.EnsureValidNames = true; + openFolderDialog.Multiselect = false; + openFolderDialog.ShowPlacesList = true; + + // Set up listbox for files + listboxFiles.DisplayMember = "Message"; + listboxFiles.ValueMember = "FileInfo"; + } + + private void buttonBrowse_Click(object sender, EventArgs e) + { + var res = openFolderDialog.ShowDialog(); + if (res == CommonFileDialogResult.Ok) + { + textboxDirectoryImage.Text = openFolderDialog.FileName; + + if (textboxDirectoryCPS.Text == "") + textboxDirectoryCPS.Text = openFolderDialog.FileName; + } + } + + private void buttonBrowseCPS_Click(object sender, EventArgs e) + { + var res = openFolderDialog.ShowDialog(); + if (res == CommonFileDialogResult.Ok) + { + textboxDirectoryCPS.Text = openFolderDialog.FileName; + } + } + + private void textboxDirectory_TextChanged(object sender, EventArgs e) + { + DirectoryInfo dir = new DirectoryInfo(textboxDirectoryImage.Text); + + if (dir.Exists) + { + // Valid folder + folderWatcher.Path = textboxDirectoryImage.Text; + folderWatcher.EnableRaisingEvents = true; + PopulateFileListbox(); + //listboxFiles.SelectedIndex = 0; + } + else + { + // invalid folder + folderWatcher.EnableRaisingEvents = false; + listboxFiles.Items.Clear(); + } + } + + + private void PopulateFileListbox() + { + // Get current index + int prev_idx = listboxFiles.SelectedIndex; + var prev_entry = (listboxFiles.SelectedItem as ListboxFileEntry); + + + listboxFiles.Items.Clear(); + + // Get list of all valid files + DirectoryInfo dir = new DirectoryInfo(textboxDirectoryImage.Text); + var files = GetFilesByExtensions(dir, image_extensions); + + foreach (var f in files) + { + listboxFiles.Items.Add(new ListboxFileEntry(f.FullName, f.Name)); + } + + // Select the last item we had before + if (listboxFiles.Items.Count > 0 && prev_entry != null) + { + bool found_old_entry = false; + foreach (var fn in listboxFiles.Items) + { + ListboxFileEntry entry = (ListboxFileEntry)fn; + if (entry.FileInfo.Name == prev_entry.FileInfo.Name) + { + listboxFiles.SelectedItem = entry; + found_old_entry = true; + break; + } + } + + // Didn't find the exact entry, so let's keep the cursor in the same area + if (!found_old_entry && listboxFiles.Items.Count > 0) + listboxFiles.SelectedIndex = Math.Min(prev_idx, listboxFiles.Items.Count - 1); + } + + } + + + #region Folder Watcher + private void OnFolderChange(object sender, FileSystemEventArgs e) + { + var ext = (Path.GetExtension(e.FullPath) ?? string.Empty).ToLower(); + + if (!image_extensions.Any(ext.Equals)) + // File changed is not a valid image, ignore it + return; + + // Reload the file list + this.BeginInvoke(new MethodInvoker(delegate + { + PopulateFileListbox(); + })); + } + + private IEnumerable GetFilesByExtensions(DirectoryInfo dir, params string[] extensions) + { + if (extensions == null) + throw new ArgumentNullException("extensions"); + IEnumerable files = dir.EnumerateFiles(); + return files.Where(f => extensions.Contains(f.Extension)); + } + #endregion + + private void listboxFiles_SelectedIndexChanged(object sender, EventArgs e) + { + if (listboxFiles.SelectedItem == null) + return; + + // Display image + + // Get the currently selected item in the ListBox. + ListboxFileEntry listboxEntry = (ListboxFileEntry)listboxFiles.SelectedItem; + imageFullPath = listboxEntry.FileInfo.FullName; + imageName = listboxEntry.FileInfo.Name; + + if (listboxEntry.FileInfo.Exists) + { + //pictureboxMain.ImageLocation = fullpath; + if (image != null) + { + image.Dispose(); + image = null; + } + + var ext = (Path.GetExtension(imageName) ?? string.Empty).ToLower(); + if (ext != ".prt") + { + try + { + // Open file, make a copy of the image, then close the file + using (var bmpTemp = new Bitmap(imageFullPath)) + { + image = new Bitmap(bmpTemp); + } + } + catch (Exception ex) + { + MessageBox.Show("Error loading image " + listboxEntry.FileInfo.FullName + "\n\nReason: " + ex.Message); + return; + } + + picturebox.Image = image; + } + } + else + { + MessageBox.Show("The following file does not exist: " + imageFullPath, "Error opening file", MessageBoxButtons.OK, MessageBoxIcon.Warning); + } + } + + private void buttonSaveCPS_Click(object sender, EventArgs e) + { + if (imageFullPath == null) + return; + + try + { + CompressImage(imageFullPath); + MessageBox.Show("Converted 1 image."); + } + catch (IOException ex) + { + MessageBox.Show(ex.Message); + } + } + + private void buttonBatch_Click(object sender, EventArgs e) + { + int successful = 0; + int max_files = listboxFiles.Items.Count; + for (int i=0; i< max_files; i++) + { + try + { + string filepath = (listboxFiles.Items[i] as ListboxFileEntry).FileInfo.FullName; + CompressImage(filepath); + successful++; + } + catch (IOException ex) + { + MessageBox.Show(ex.Message); + } + } + + MessageBox.Show("Converted " + successful.ToString() + "/" + max_files.ToString() + " images."); + } + + void CompressImage(string filepath) + { + string filename = Path.GetFileName(filepath); + + int index = filename.LastIndexOf('.'); + string filename_no_ext = filename.Substring(0, index); + string ext = filename.Substring(index + 1).ToLower(); + + MemoryStream stream_bmp, stream_prt, stream_cps; + + // Convert to 8-bit indexed BMP + //Bitmap bmp = BMP_format.CopyToBpp(image, 8); + + if (ext == "prt") + { + stream_prt = new MemoryStream(); + // Use original PRT + FileStream fin_stream = new FileStream(filepath, FileMode.Open); + fin_stream.CopyTo(stream_prt); + fin_stream.Close(); + fin_stream.Dispose(); + } + else + { + stream_bmp = new MemoryStream(); + if (ext != "bmp") + { + // Convert file to BMP + // Open file, make a copy of the image, then close the file + Bitmap bmp; + using (var bmpTemp = new Bitmap(filepath)) + { + bmp = new Bitmap(bmpTemp); + Console.WriteLine(bmp.PixelFormat); + } + + bmp.Save(stream_bmp, System.Drawing.Imaging.ImageFormat.Bmp); + } + else + { + // Use original BMP + FileStream fin_stream = new FileStream(filepath, FileMode.Open); + fin_stream.CopyTo(stream_bmp); + fin_stream.Close(); + fin_stream.Dispose(); + } + + // Convert to PRT + string filename_prt = null; // Path.Combine(textboxDirectoryCPS.Text, filename_no_ext + ".prt");; + stream_prt = PRT_format.Convert(stream_bmp, false, filename_prt); + } + + + // Convert to CPS + string filename_cps = Path.Combine(textboxDirectoryCPS.Text, filename_no_ext + ".cps"); + stream_cps = CPS_format.Compress(stream_prt, false, filename_cps); + + stream_prt.Dispose(); + stream_cps.Dispose(); + } + } + + + public class ListboxFileEntry + { + public FileInfo FileInfo { get; set; } + public string Message { get; set; } + + public ListboxFileEntry(string filename, string message = null) + { + FileInfo = new FileInfo(filename); + + if (message != null) + this.Message = message; + else + this.Message = FileInfo.Name; + } + + } +} diff --git a/E17_CPS/Form1.resx b/E17_CPS/Form1.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/E17_CPS/Form1.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/E17_CPS/PRT_format.cs b/E17_CPS/PRT_format.cs new file mode 100644 index 0000000..e7abd4f --- /dev/null +++ b/E17_CPS/PRT_format.cs @@ -0,0 +1,192 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace E17_CPS +{ + class PRT_format + { + [StructLayout(LayoutKind.Explicit)] + struct PRTHeader + { + [FieldOffset(0x00)] + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)] + public string ID; + + [FieldOffset(0x04)] + public Int16 Version; + + [FieldOffset(0x06)] + public Int16 ColorDepth; + + [FieldOffset(0x08)] + public Int16 PalletteOffset; + + [FieldOffset(0x0A)] + public Int16 DataOffset; + + [FieldOffset(0x0C)] + public Int16 Width; + + [FieldOffset(0x0E)] + public Int16 Height; + + [FieldOffset(0x10)] + public Int32 Alpha; + + [FieldOffset(0x14)] + public Int32 Base1Offset; + + [FieldOffset(0x18)] + public Int32 u2; + + [FieldOffset(0x1C)] + public Int32 Width2; + + [FieldOffset(0x20)] + public Int32 Height2; + } + + public static MemoryStream Convert(string filename, bool compress = true, string savepath = null) + { + FileStream fin_stream = new FileStream(filename, FileMode.Open); + + // Copy to a memory stream so that we can modify it as we like + MemoryStream stream = new MemoryStream(); + fin_stream.CopyTo(stream); + fin_stream.Close(); + fin_stream.Dispose(); + + MemoryStream stream_out = Convert(stream, compress, savepath); + stream.Dispose(); + + return stream_out; + } + + public static MemoryStream Convert(MemoryStream stream, bool compress = true, string savepath = null) + { + // Read the BMP data + BinaryReader reader = new BinaryReader(stream); + stream.Seek(0x0A, SeekOrigin.Begin); + Int32 data_offset = reader.ReadInt32(); + Int32 header_size = reader.ReadInt32(); //Assumed to be 40 (ie. Windows format, not OS/2) + Int32 width = reader.ReadInt32(); + Int32 height = reader.ReadInt32(); + Int16 one = reader.ReadInt16(); + Int16 depth = reader.ReadInt16(); + + // 32 bit data is represented as 24 bit data + a giant block of alpha data at the end + stream.Position = 0x36; + byte[] data_block = null; + byte[] alpha_block = null; + if (depth == 32) + { + data_block = new byte[width * height * 3]; + alpha_block = new byte[width * height]; + + // Read three color bytes and one alpha byte + int i = 0; + while (stream.Position < stream.Length) + { + data_block[i*3 ] = (byte)stream.ReadByte(); + data_block[i*3+1] = (byte)stream.ReadByte(); + data_block[i*3+2] = (byte)stream.ReadByte(); + alpha_block[i] = (byte)stream.ReadByte(); + i++; + } + + // If all the alpha bytes are 0xFF, change the depth to 24 bits + bool all_opaque = true; + for (i = 0; i + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new Form1()); + } + } +} diff --git a/E17_CPS/Properties/AssemblyInfo.cs b/E17_CPS/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..89d5b81 --- /dev/null +++ b/E17_CPS/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("E17_CPS")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("HP Inc.")] +[assembly: AssemblyProduct("E17_CPS")] +[assembly: AssemblyCopyright("Copyright © HP Inc. 2018")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("30bde65d-f4b8-48a6-978d-eebd02785575")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/E17_CPS/Properties/Resources.Designer.cs b/E17_CPS/Properties/Resources.Designer.cs new file mode 100644 index 0000000..a8508f5 --- /dev/null +++ b/E17_CPS/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace E17_CPS.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("E17_CPS.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/E17_CPS/Properties/Resources.resx b/E17_CPS/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/E17_CPS/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/E17_CPS/Properties/Settings.Designer.cs b/E17_CPS/Properties/Settings.Designer.cs new file mode 100644 index 0000000..c7c7f7d --- /dev/null +++ b/E17_CPS/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace E17_CPS.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/E17_CPS/Properties/Settings.settings b/E17_CPS/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/E17_CPS/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/E17_CPS/packages.config b/E17_CPS/packages.config new file mode 100644 index 0000000..e3091da --- /dev/null +++ b/E17_CPS/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..6cd09a3 --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# Ever17 Image Converter +Convert standard image formats (PNG, BMP) to PRT, then compress it into a CPS file. For the PC version of [Ever17: The Out of Infinity](https://vndb.org/v17). + +Precompiled binaries can be found in [Releases](https://github.com/arsym-dev/Ever17-CPS-Converter/releases). + +## Usage +1. Set "Image Directory" to the folder that has all your images in standard format +2. Set "CPS Directory" to where you'd like to export the final CPS files + - In order to avoid recompiling the DAT files (eg. `bg.dat`), you can create a subdirectory in the "graph" folder with the appropriate name. (eg. `/graph/bg`). Any file placed in here will take priority over the DAT archive. +3. You can either convert one image at a time by selecting the image and pressing "Convert to CPS", or convert all at once with "Batch Convert to CPS". + +![Main UI](img/main.png) \ No newline at end of file diff --git a/img/main.png b/img/main.png new file mode 100644 index 0000000..38c8236 Binary files /dev/null and b/img/main.png differ