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