diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3a88943 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vs +Epoc/bin +Epoc/obj +zPakr/bin +zPakr/obj diff --git a/Epoc.sln b/Epoc.sln new file mode 100644 index 0000000..685188e --- /dev/null +++ b/Epoc.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.12.35707.178 d17.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Epoc", "Epoc\Epoc.csproj", "{E1C880E8-A830-45B5-9466-99CBEDD05031}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "zPakr", "zPakr\zPakr.csproj", "{5DA149CE-856E-4940-AFC2-D19F9C02F718}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E1C880E8-A830-45B5-9466-99CBEDD05031}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E1C880E8-A830-45B5-9466-99CBEDD05031}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E1C880E8-A830-45B5-9466-99CBEDD05031}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E1C880E8-A830-45B5-9466-99CBEDD05031}.Release|Any CPU.Build.0 = Release|Any CPU + {5DA149CE-856E-4940-AFC2-D19F9C02F718}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DA149CE-856E-4940-AFC2-D19F9C02F718}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DA149CE-856E-4940-AFC2-D19F9C02F718}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DA149CE-856E-4940-AFC2-D19F9C02F718}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Epoc/Epoc-icon.ico b/Epoc/Epoc-icon.ico new file mode 100644 index 0000000..90b8d55 --- /dev/null +++ b/Epoc/Epoc-icon.ico Binary files differ diff --git a/Epoc/Epoc.csproj b/Epoc/Epoc.csproj new file mode 100644 index 0000000..6554baf --- /dev/null +++ b/Epoc/Epoc.csproj @@ -0,0 +1,35 @@ + + + + WinExe + net9.0-windows + enable + true + enable + Epoc-icon.ico + + + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + \ No newline at end of file diff --git a/Epoc/Epoc.csproj.user b/Epoc/Epoc.csproj.user new file mode 100644 index 0000000..5bac621 --- /dev/null +++ b/Epoc/Epoc.csproj.user @@ -0,0 +1,11 @@ + + + + <_LastSelectedProfileId>C:\Users\IvanDominguez\source\repos\Epoc\Epoc\Properties\PublishProfiles\FolderProfile.pubxml + + + + Form + + + \ No newline at end of file diff --git a/Epoc/EpocRunner.cs b/Epoc/EpocRunner.cs new file mode 100644 index 0000000..d4b1e4d --- /dev/null +++ b/Epoc/EpocRunner.cs @@ -0,0 +1,267 @@ +using System.Diagnostics; + +namespace Epoc +{ + public class EpocRunner + { + private const string EPOC_BASE = @"C:\Epoc32"; + private const string EPOC_EMUL = $"{EPOC_BASE}\\Rel\\Epoc.exe"; + private const string EPOC_CONF = $"{EPOC_BASE}\\Data\\Epoc.ini"; + private const string EPOC_HELP = $"{EPOC_BASE}\\Rel\\Help\\oepoc.chm"; + + private const int DEF_ScreenHeight = 240; + private const int DEF_ScreenWidth = 640; + private const int DEF_PhysicalScreenHeight = 2858; + private const int DEF_PhysicalScreenWidth = 7620; + private const int DEF_ScreenOffsetX = 90; + private const int DEF_ScreenOffsetY = 51; + private const int DEF_LedGap = 5; + private const int DEF_LedOffsetX = 36; + private const int DEF_LedOffsetY = 45; + private const int DEF_LedSize = 14; + private const EpocPointerType DEF_PointerType = EpocPointerType.Pen; + private const int DEF_MegabytesOfFreeMemory = 64; + + /// + /// Check if Epoc emulator is correctly set. + /// + /// Ture or false + public static bool CheckLocation() + { + // This version of epoc emulator needs to be on C:\Epoc32 directory. + if (!File.Exists(EPOC_EMUL)) + return false; + return true; + } + + #region Load / Save + public void LoadConf() + { + if (!File.Exists(EPOC_CONF)) + return; + foreach (var line in File.ReadLines(EPOC_CONF)) + { + var eqp = line.IndexOf(' '); + string key, value; + if (eqp == -1) + { + key = line; + value = ""; + } + else + { + key = line[0..eqp].Trim(); + value = line[(eqp + 1)..].Trim(); + } + switch (key) + { + case "ScreenHeight": + ScreenHeight = Parse(value, DEF_ScreenHeight); + break; + case "ScreenWidth": + ScreenWidth = Parse(value, DEF_ScreenWidth); + break; + case "PhysicalScreenHeight": + PhysicalScreenHeight = Parse(value, DEF_PhysicalScreenHeight); + break; + case "PhysicalScreenWidth": + PhysicalScreenWidth = Parse(value, DEF_PhysicalScreenWidth); + break; + case "ScreenOffsetX": + ScreenOffsetX = Parse(value, DEF_ScreenOffsetX); + break; + case "ScreenOffsetY": + ScreenOffsetY = Parse(value, DEF_ScreenOffsetY); + break; + case "LedArrangeVertically": + LedArrangeVertically = true; + break; + case "LedArrangeHorizontally": + LedArrangeVertically = false; + break; + case "LedGap": + LedGap = Parse(value, DEF_LedGap); + break; + case "LedOffsetX": + LedOffsetX = Parse(value, DEF_LedOffsetX); + break; + case "LedOffsetY": + LedOffsetY = Parse(value, DEF_LedOffsetY); + break; + case "LedSize": + LedSize = Parse(value, DEF_LedSize); + break; + case "MegabytesOfFreeMemory": + MegabytesOfFreeMemory = Parse(value, DEF_MegabytesOfFreeMemory); + break; + case "PointerType": + PointerType = Parse(value, DEF_PointerType); + break; + case "_EPOC_DRIVE_C": + DriveC = value; + break; + case "_EPOC_DRIVE_D": + DriveD = value; + break; + case "_EPOC_DRIVE_E": + DriveE = value; + break; + case "_EPOC_DRIVE_F": + DriveF = value; + break; + case "_EPOC_DRIVE_G": + DriveG = value; + break; + case "_EPOC_DRIVE_Z": + DriveZ = value; + break; + } + } + } + + public void SaveConf() + { + List lines = []; + if (File.Exists(EPOC_CONF)) + lines.AddRange(File.ReadLines(EPOC_CONF)); + for (int i = 0; i < lines.Count; i++) + lines[i] = lines[i].Trim(); + SetINI(lines, "ScreenHeight", ScreenHeight, DEF_ScreenHeight); + SetINI(lines, "ScreenWidth", ScreenWidth, DEF_ScreenWidth); + SetINI(lines, "PhysicalScreenHeight", PhysicalScreenHeight, DEF_PhysicalScreenHeight); + SetINI(lines, "PhysicalScreenWidth", PhysicalScreenWidth, DEF_PhysicalScreenWidth); + SetINI(lines, "ScreenOffsetX", ScreenOffsetX, DEF_ScreenOffsetX); + SetINI(lines, "ScreenOffsetY", ScreenOffsetY, DEF_ScreenOffsetY); + SetINIFlag(lines, "LedArrangeVertically", "LedArrangeHorizontally", LedArrangeVertically, true); + SetINI(lines, "LedGap", LedGap, DEF_LedGap); + SetINI(lines, "LedOffsetX", LedOffsetX, DEF_LedOffsetX); + SetINI(lines, "LedOffsetY", LedOffsetY, DEF_LedOffsetY); + SetINI(lines, "LedSize", LedSize, DEF_LedSize); + SetINI(lines, "MegabytesOfFreeMemory", MegabytesOfFreeMemory, DEF_MegabytesOfFreeMemory); + SetINI(lines, "PointerType", PointerType, DEF_PointerType); + SetINI(lines, "_EPOC_DRIVE_C", DriveC, string.Empty); + SetINI(lines, "_EPOC_DRIVE_D", DriveD, string.Empty); + SetINI(lines, "_EPOC_DRIVE_E", DriveE, string.Empty); + SetINI(lines, "_EPOC_DRIVE_F", DriveF, string.Empty); + SetINI(lines, "_EPOC_DRIVE_G", DriveG, string.Empty); + SetINI(lines, "_EPOC_DRIVE_Z", DriveZ, string.Empty); + File.WriteAllLines(EPOC_CONF, lines); + } + + private static int Parse(string value, int def) + { + if (int.TryParse(value, out var ival)) + return ival; + return def; + } + + private static EpocPointerType Parse(string value, EpocPointerType def) + { + return value switch + { + "PEN" => EpocPointerType.Pen, + "MOUSE" => EpocPointerType.Mouse, + _ => EpocPointerType.None, + }; + } + + private static void SetINI(List lines, string key, string value, string def) + { + value = value.Trim(); + if (value == def) + return; + var newLine = $"{key} {value.Trim()}"; + var lineNum = ConfLineOf(lines, key); + if (lineNum >= 0) + lines[lineNum] = newLine; + else + lines.Add(newLine); + + } + + private static void SetINI(List lines, string key, int value, int def) + { + SetINI(lines, key, value.ToString(), def.ToString()); + } + + private static void SetINI(List lines, string key, EpocPointerType pointer, EpocPointerType def) + { + SetINI(lines, key, PointerKey(pointer), PointerKey(def)); + } + + private static void SetINIFlag(List lines, string keyOn, string keyOff, bool value, bool def) + { + int toRemove = ConfLineOf(lines, keyOn); + if (toRemove >= 0) + lines.RemoveAt(toRemove); + toRemove = ConfLineOf(lines, keyOff); + if (toRemove >= 0) + lines.RemoveAt(toRemove); + if (value != def) + lines.Add(value ? keyOn : keyOff); + } + + private static int ConfLineOf(List lines, string key) + { + key = key.Trim() + ' '; + for (int i = 0; i < lines.Count; i++) + { + var line = lines[i]; + if (line.StartsWith(key)) + return i; + } + return -1; + } + + private static string PointerKey(EpocPointerType pointer) + { + return pointer switch + { + EpocPointerType.Pen => "PEN", + EpocPointerType.Mouse => "MOUSE", + _ => "NONE", + }; + } + #endregion + + public void Run(bool save = true) + { + if (save) + SaveConf(); + Process.Start(EPOC_EMUL); + } + + public void Help() + { + Process.Start(new ProcessStartInfo(EPOC_HELP) + { + UseShellExecute = true, + }); + } + + public int ScreenHeight { get; set; } = DEF_ScreenHeight; + public int ScreenWidth { get; set; } = DEF_ScreenWidth; + public int PhysicalScreenHeight { get; set; } = DEF_PhysicalScreenHeight; + public int PhysicalScreenWidth { get; set; } = DEF_PhysicalScreenWidth; + public int ScreenOffsetX { get; set; } = DEF_ScreenOffsetX; + public int ScreenOffsetY { get; set; } = DEF_ScreenOffsetY; + + public bool LedArrangeVertically { get; set; } = true; + public int LedGap { get; set; } = DEF_LedGap; + public int LedOffsetX { get; set; } = DEF_LedOffsetX; + public int LedOffsetY { get; set; } = DEF_LedOffsetY; + public int LedSize { get; set; } = DEF_LedSize; + public int MegabytesOfFreeMemory { get; set; } = DEF_MegabytesOfFreeMemory; + public EpocPointerType PointerType { get; set; } = DEF_PointerType; + + public string DriveC { get; set; } = string.Empty; + public string DriveD { get; set; } = string.Empty; + public string DriveE { get; set; } = string.Empty; + public string DriveF { get; set; } = string.Empty; + public string DriveG { get; set; } = string.Empty; + public string DriveZ { get; set; } = string.Empty; + + } + + public enum EpocPointerType { None, Pen, Mouse }; +} diff --git a/Epoc/Form1.Designer.cs b/Epoc/Form1.Designer.cs new file mode 100644 index 0000000..658b071 --- /dev/null +++ b/Epoc/Form1.Designer.cs @@ -0,0 +1,380 @@ +namespace Epoc +{ + 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() + { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Form1)); + pbLogo = new PictureBox(); + lbMemory = new Label(); + lbDC = new Label(); + tbDC = new TextBox(); + btDC = new Button(); + cbRAM = new ComboBox(); + btDD = new Button(); + tbDD = new TextBox(); + lbDD = new Label(); + btDE = new Button(); + tbDE = new TextBox(); + lbDE = new Label(); + btDF = new Button(); + tbDF = new TextBox(); + lbDF = new Label(); + btDG = new Button(); + tbDG = new TextBox(); + lbDG = new Label(); + btDZ = new Button(); + tbDZ = new TextBox(); + lbDZ = new Label(); + btGo = new Button(); + btHelp = new Button(); + ((System.ComponentModel.ISupportInitialize)pbLogo).BeginInit(); + SuspendLayout(); + // + // pbLogo + // + pbLogo.Image = (Image)resources.GetObject("pbLogo.Image"); + pbLogo.Location = new Point(23, 29); + pbLogo.Margin = new Padding(4, 3, 4, 3); + pbLogo.Name = "pbLogo"; + pbLogo.Size = new Size(134, 159); + pbLogo.SizeMode = PictureBoxSizeMode.AutoSize; + pbLogo.TabIndex = 0; + pbLogo.TabStop = false; + // + // lbMemory + // + lbMemory.AutoSize = true; + lbMemory.Location = new Point(174, 29); + lbMemory.Margin = new Padding(4, 0, 4, 0); + lbMemory.Name = "lbMemory"; + lbMemory.Size = new Size(54, 23); + lbMemory.TabIndex = 1; + lbMemory.Text = "RAM:"; + // + // lbDC + // + lbDC.AutoSize = true; + lbDC.Location = new Point(174, 72); + lbDC.Name = "lbDC"; + lbDC.Size = new Size(26, 23); + lbDC.TabIndex = 3; + lbDC.Text = "C:"; + // + // tbDC + // + tbDC.BackColor = Color.FromArgb(64, 64, 64); + tbDC.BorderStyle = BorderStyle.None; + tbDC.Font = new Font("Segoe UI", 10.2F, FontStyle.Regular, GraphicsUnit.Point, 0); + tbDC.ForeColor = Color.White; + tbDC.Location = new Point(206, 72); + tbDC.Name = "tbDC"; + tbDC.PlaceholderText = "(Default)"; + tbDC.Size = new Size(201, 23); + tbDC.TabIndex = 4; + // + // btDC + // + btDC.FlatStyle = FlatStyle.Flat; + btDC.Font = new Font("Segoe UI", 7.8F, FontStyle.Regular, GraphicsUnit.Point, 0); + btDC.Location = new Point(413, 70); + btDC.Name = "btDC"; + btDC.Size = new Size(28, 27); + btDC.TabIndex = 5; + btDC.Text = "🔍"; + btDC.UseVisualStyleBackColor = true; + btDC.Click += DriveButton_Click; + // + // cbRAM + // + cbRAM.BackColor = Color.FromArgb(64, 64, 64); + cbRAM.DropDownStyle = ComboBoxStyle.DropDownList; + cbRAM.FlatStyle = FlatStyle.Flat; + cbRAM.ForeColor = Color.White; + cbRAM.FormattingEnabled = true; + cbRAM.Items.AddRange(new object[] { " 4mb", "8 mb", "16 mb", "32 mb", "64 mb*", "128 mb", "256 mb" }); + cbRAM.Location = new Point(235, 26); + cbRAM.Name = "cbRAM"; + cbRAM.Size = new Size(206, 31); + cbRAM.TabIndex = 2; + cbRAM.SelectedIndexChanged += cbRAM_SelectedIndexChanged; + // + // btDD + // + btDD.FlatStyle = FlatStyle.Flat; + btDD.Font = new Font("Segoe UI", 7.8F, FontStyle.Regular, GraphicsUnit.Point, 0); + btDD.Location = new Point(413, 103); + btDD.Name = "btDD"; + btDD.Size = new Size(28, 27); + btDD.TabIndex = 8; + btDD.Text = "🔍"; + btDD.UseVisualStyleBackColor = true; + btDD.Click += DriveButton_Click; + // + // tbDD + // + tbDD.BackColor = Color.FromArgb(64, 64, 64); + tbDD.BorderStyle = BorderStyle.None; + tbDD.Font = new Font("Segoe UI", 10.2F, FontStyle.Regular, GraphicsUnit.Point, 0); + tbDD.ForeColor = Color.White; + tbDD.Location = new Point(206, 105); + tbDD.Name = "tbDD"; + tbDD.Size = new Size(201, 23); + tbDD.TabIndex = 7; + // + // lbDD + // + lbDD.AutoSize = true; + lbDD.Location = new Point(174, 105); + lbDD.Name = "lbDD"; + lbDD.Size = new Size(28, 23); + lbDD.TabIndex = 6; + lbDD.Text = "D:"; + // + // btDE + // + btDE.FlatStyle = FlatStyle.Flat; + btDE.Font = new Font("Segoe UI", 7.8F, FontStyle.Regular, GraphicsUnit.Point, 0); + btDE.Location = new Point(413, 136); + btDE.Name = "btDE"; + btDE.Size = new Size(28, 27); + btDE.TabIndex = 11; + btDE.Text = "🔍"; + btDE.UseVisualStyleBackColor = true; + btDE.Click += DriveButton_Click; + // + // tbDE + // + tbDE.BackColor = Color.FromArgb(64, 64, 64); + tbDE.BorderStyle = BorderStyle.None; + tbDE.Font = new Font("Segoe UI", 10.2F, FontStyle.Regular, GraphicsUnit.Point, 0); + tbDE.ForeColor = Color.White; + tbDE.Location = new Point(206, 138); + tbDE.Name = "tbDE"; + tbDE.Size = new Size(201, 23); + tbDE.TabIndex = 10; + // + // lbDE + // + lbDE.AutoSize = true; + lbDE.Location = new Point(174, 138); + lbDE.Name = "lbDE"; + lbDE.Size = new Size(24, 23); + lbDE.TabIndex = 9; + lbDE.Text = "E:"; + // + // btDF + // + btDF.FlatStyle = FlatStyle.Flat; + btDF.Font = new Font("Segoe UI", 7.8F, FontStyle.Regular, GraphicsUnit.Point, 0); + btDF.Location = new Point(413, 169); + btDF.Name = "btDF"; + btDF.Size = new Size(28, 27); + btDF.TabIndex = 14; + btDF.Text = "🔍"; + btDF.UseVisualStyleBackColor = true; + btDF.Click += DriveButton_Click; + // + // tbDF + // + tbDF.BackColor = Color.FromArgb(64, 64, 64); + tbDF.BorderStyle = BorderStyle.None; + tbDF.Font = new Font("Segoe UI", 10.2F, FontStyle.Regular, GraphicsUnit.Point, 0); + tbDF.ForeColor = Color.White; + tbDF.Location = new Point(206, 171); + tbDF.Name = "tbDF"; + tbDF.Size = new Size(201, 23); + tbDF.TabIndex = 13; + // + // lbDF + // + lbDF.AutoSize = true; + lbDF.Location = new Point(174, 171); + lbDF.Name = "lbDF"; + lbDF.Size = new Size(24, 23); + lbDF.TabIndex = 12; + lbDF.Text = "F:"; + // + // btDG + // + btDG.FlatStyle = FlatStyle.Flat; + btDG.Font = new Font("Segoe UI", 7.8F, FontStyle.Regular, GraphicsUnit.Point, 0); + btDG.Location = new Point(413, 202); + btDG.Name = "btDG"; + btDG.Size = new Size(28, 27); + btDG.TabIndex = 17; + btDG.Text = "🔍"; + btDG.UseVisualStyleBackColor = true; + btDG.Click += DriveButton_Click; + // + // tbDG + // + tbDG.BackColor = Color.FromArgb(64, 64, 64); + tbDG.BorderStyle = BorderStyle.None; + tbDG.Font = new Font("Segoe UI", 10.2F, FontStyle.Regular, GraphicsUnit.Point, 0); + tbDG.ForeColor = Color.White; + tbDG.Location = new Point(206, 204); + tbDG.Name = "tbDG"; + tbDG.Size = new Size(201, 23); + tbDG.TabIndex = 16; + // + // lbDG + // + lbDG.AutoSize = true; + lbDG.Location = new Point(174, 204); + lbDG.Name = "lbDG"; + lbDG.Size = new Size(27, 23); + lbDG.TabIndex = 15; + lbDG.Text = "G:"; + // + // btDZ + // + btDZ.FlatStyle = FlatStyle.Flat; + btDZ.Font = new Font("Segoe UI", 7.8F, FontStyle.Regular, GraphicsUnit.Point, 0); + btDZ.Location = new Point(413, 235); + btDZ.Name = "btDZ"; + btDZ.Size = new Size(28, 27); + btDZ.TabIndex = 20; + btDZ.Text = "🔍"; + btDZ.UseVisualStyleBackColor = true; + btDZ.Click += DriveButton_Click; + // + // tbDZ + // + tbDZ.BackColor = Color.FromArgb(64, 64, 64); + tbDZ.BorderStyle = BorderStyle.None; + tbDZ.Font = new Font("Segoe UI", 10.2F, FontStyle.Regular, GraphicsUnit.Point, 0); + tbDZ.ForeColor = Color.White; + tbDZ.Location = new Point(206, 237); + tbDZ.Name = "tbDZ"; + tbDZ.PlaceholderText = "(Default)"; + tbDZ.Size = new Size(201, 23); + tbDZ.TabIndex = 19; + // + // lbDZ + // + lbDZ.AutoSize = true; + lbDZ.Location = new Point(174, 237); + lbDZ.Name = "lbDZ"; + lbDZ.Size = new Size(25, 23); + lbDZ.TabIndex = 18; + lbDZ.Text = "Z:"; + // + // btGo + // + btGo.FlatStyle = FlatStyle.Flat; + btGo.Font = new Font("Segoe UI", 7.8F, FontStyle.Regular, GraphicsUnit.Point, 0); + btGo.Location = new Point(329, 276); + btGo.Name = "btGo"; + btGo.Size = new Size(112, 27); + btGo.TabIndex = 21; + btGo.Text = "▶️ Go!"; + btGo.UseVisualStyleBackColor = true; + btGo.Click += btGo_Click; + // + // btHelp + // + btHelp.FlatStyle = FlatStyle.Flat; + btHelp.Font = new Font("Segoe UI", 7.8F, FontStyle.Regular, GraphicsUnit.Point, 0); + btHelp.Location = new Point(174, 276); + btHelp.Name = "btHelp"; + btHelp.Size = new Size(70, 27); + btHelp.TabIndex = 22; + btHelp.Text = "❓ OPL"; + btHelp.UseVisualStyleBackColor = true; + btHelp.Click += btHelp_Click; + // + // Form1 + // + AutoScaleDimensions = new SizeF(10F, 23F); + AutoScaleMode = AutoScaleMode.Font; + BackColor = Color.FromArgb(24, 29, 32); + ClientSize = new Size(456, 317); + Controls.Add(btHelp); + Controls.Add(btGo); + Controls.Add(btDZ); + Controls.Add(tbDZ); + Controls.Add(lbDZ); + Controls.Add(btDG); + Controls.Add(tbDG); + Controls.Add(lbDG); + Controls.Add(btDF); + Controls.Add(tbDF); + Controls.Add(lbDF); + Controls.Add(btDE); + Controls.Add(tbDE); + Controls.Add(lbDE); + Controls.Add(btDD); + Controls.Add(tbDD); + Controls.Add(lbDD); + Controls.Add(cbRAM); + Controls.Add(btDC); + Controls.Add(tbDC); + Controls.Add(lbDC); + Controls.Add(lbMemory); + Controls.Add(pbLogo); + Font = new Font("Segoe UI", 10.2F, FontStyle.Bold, GraphicsUnit.Point, 0); + ForeColor = Color.FromArgb(255, 255, 192); + FormBorderStyle = FormBorderStyle.FixedSingle; + Icon = (Icon)resources.GetObject("$this.Icon"); + Margin = new Padding(4, 3, 4, 3); + MaximizeBox = false; + Name = "Form1"; + StartPosition = FormStartPosition.CenterScreen; + Text = "Epoc Emulator Launcher"; + ((System.ComponentModel.ISupportInitialize)pbLogo).EndInit(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + + private PictureBox pbLogo; + private Label lbMemory; + private Label lbDC; + private TextBox tbDC; + private Button btDC; + private ComboBox cbRAM; + private Button btDD; + private TextBox tbDD; + private Label lbDD; + private Button btDE; + private TextBox tbDE; + private Label lbDE; + private Button btDF; + private TextBox tbDF; + private Label lbDF; + private Button btDG; + private TextBox tbDG; + private Label lbDG; + private Button btDZ; + private TextBox tbDZ; + private Label lbDZ; + private Button btGo; + private Button btHelp; + } +} diff --git a/Epoc/Form1.cs b/Epoc/Form1.cs new file mode 100644 index 0000000..7fadd68 --- /dev/null +++ b/Epoc/Form1.cs @@ -0,0 +1,127 @@ +using System.Reflection; +using static Epoc.DriveRow; + +namespace Epoc +{ + public partial class Form1 : Form + { + private readonly EpocRunner epoc = new(); + private readonly DriveRow driveC; + private readonly DriveRow driveD; + private readonly DriveRow driveE; + private readonly DriveRow driveF; + private readonly DriveRow driveG; + private readonly DriveRow driveZ; + + public Form1() + { + InitializeComponent(); + driveC = new DriveRow(lbDC, tbDC, btDC, () => epoc.DriveC, value => epoc.DriveC = value); + driveD = new DriveRow(lbDD, tbDD, btDD, () => epoc.DriveD, value => epoc.DriveD = value); + driveE = new DriveRow(lbDE, tbDE, btDE, () => epoc.DriveE, value => epoc.DriveE = value); + driveF = new DriveRow(lbDF, tbDF, btDF, () => epoc.DriveF, value => epoc.DriveF = value); + driveG = new DriveRow(lbDG, tbDG, btDG, () => epoc.DriveG, value => epoc.DriveG = value); + driveZ = new DriveRow(lbDZ, tbDZ, btDZ, () => epoc.DriveZ, value => epoc.DriveZ = value); + + LoadConf(); + if (!EpocRunner.CheckLocation()) + MessageBox.Show("El emulador Epoc no Est� instalado en la carpeta correcta, Actualmente Epoc s�lo se puede instalar en c:\\Epoc32.\nLa aplicaci�n no funcionar�.", "Instalaci�n", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + + private void LoadConf() + { + epoc.LoadConf(); + int[] ctt = [4, 8, 16, 32, 64, 128, 256, int.MaxValue]; + for (int i = 0; i < ctt.Length; i++) + { + if (ctt[i] > epoc.MegabytesOfFreeMemory) + { + cbRAM.SelectedIndex = Math.Max(i - 1, 0); + break; + } + } + foreach (var row in Rows) + row.Read(); + } + + private DriveRow RowOf(Control c) + { + foreach (var row in Rows) + { + if (c == row.Label || c == row.TextBox || c == row.Button) + return row; + } + throw new Exception("Not a row control"); + } + + private DriveRow[] Rows => [driveC, driveD, driveE, driveF, driveG, driveZ]; + + private void DriveButton_Click(object sender, EventArgs e) + { + var row = RowOf(sender as Control ?? throw new Exception("Not a control")); + var path = row.TextBox.Text; + if (string.IsNullOrWhiteSpace(path)) + path = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + FolderBrowserDialog fbd = new() + { + SelectedPath = path + }; + if (fbd.ShowDialog() == DialogResult.OK) + row.TextBox.Text = fbd.SelectedPath; + } + + private void btHelp_Click(object sender, EventArgs e) + { + epoc.Help(); + } + + private void btGo_Click(object sender, EventArgs e) + { + epoc.Run(); + } + + private void cbRAM_SelectedIndexChanged(object sender, EventArgs e) + { + int[] ctt = [4, 8, 16, 32, 64, 128, 256, int.MaxValue]; + epoc.MegabytesOfFreeMemory = ctt[cbRAM.SelectedIndex]; + } + } + + public class DriveRow + { + public delegate string PropertyGetter(); + public delegate void PropertySetter(string path); + + public DriveRow(Label label, TextBox textBox, Button button, PropertyGetter get, PropertySetter set) + { + Label = label; + TextBox = textBox; + Button = button; + Get = get; + Set = set; + + TextBox.TextChanged += TextChanged; + } + + private void TextChanged(object? sender, EventArgs e) + { + Write(); + } + + public void Read() + { + TextBox.Text = Get(); + } + + public void Write() + { + Set(TextBox.Text); + } + + public Label Label { get; } + public TextBox TextBox { get; } + public Button Button { get; } + public PropertyGetter Get { get; } + public PropertySetter Set { get; } + } +} diff --git a/Epoc/Form1.resx b/Epoc/Form1.resx new file mode 100644 index 0000000..29a1091 --- /dev/null +++ b/Epoc/Form1.resx @@ -0,0 +1,547 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + + + 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 + 17MhkbaVI5W7mCWexqEWaoZ+J6TNqCCLr5EAAABtAAAAbR+Iq44npM6oHYOliyio0q0BFx9uAAAAbRRi + fXsbe5uGAAAAbQAAAG0AAABtAAAAXQAAAGwAAABtAAAAbQAAAG0moculIpS6mBhwjoEbepqGJ6TNqCah + yaQgjbGSKarVsQAAAG0AAABtJ6PMpx+KrZAmoMijIpG3lQAAAG0AAABtIY+0kyekzagdg6WLKavWsQIb + JW4AAABtF26MgCGQtZQAAABtAAAAbQAAAG0AAABsAAAAbQAAAG0AAABtAAAAbRJfeXokmL+cIpS6mCKU + upglncWhF26MgBVng30lncWgIY+zkySZwJwjlryZC0dbdBNifHsjmL6bI5W7mCKTuZckm8OeDU1idQMf + Km4hj7STI5W8mSKSuJYlnMSgGneWhAAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0NTGF1HYKkiiOXvZomoMmkJ6TNqCai + y6YmocqkJJnBnSGPtJMaepmFDk5kdgAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0HN0dxI5a8mSms17Iqrtq2K7LevCy1 + 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 + yaQqrtq2H4irjgAAAG0IOktyLLXjwiqu2rYgjLCRAAAAbQAAAG0YcY6BK7DduSmr1rIAAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0cgKKJK7Pgvh+K + rpAAAABtEl53eiy25MMprNezC0VZdAAAAG0fiq6QK7Heuyqt2bUZdZSDAAAAbQAAAG0AAABtAh8pbgAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAH + DG0steLBJ6TNqAACA20AAABtKKjSrSqu2rYgjLCRAAAAbQAAAG0pqdSvKq7atiim0KsLRlp0AAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABsAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbSmq1bEqrdm1AAYKbQAAAG0dgaKKLLXiwSip064GMkFwAAAAbQIdJ24rst+9Kq7atiCN + sZIAAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbAAAAF0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtElx1eSagyKMAAQJtAAAAbQAEB20qr9u3Kq7athhxj4EAAABtAAAAbQxI + XXQstOLAKqzYtAAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABdAAAAMwAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbRZphX4ss+C+HoapjQAA + AG0AAABtAAAAbQMhLG8UZH58AAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAADMAAAADAAAAUAAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbRdt + ioAOUGZ2AAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABQAAAAAwAA + AAAAAAADAAAAMwAAAF0AAABsAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAGwAAABdAAAAMwAA + AAMAAAAAgAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAIAAAAEoAAAAMAAAAGAAAAABACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAADwAAADsAAABcAAAAawAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABrAAAAXAAAADsAAAAPAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAArAAAAaAAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABoAAAAKwAAAAAAAAAAAAAAAAAAACsAAABrAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtBzhIcQc4 + SHEHN0dxAAAAbQAAAG0AAABtAAECbQc3R3EHOElxAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtBzdHcQY0RHEGM0NxAhghbgAAAG0AAABtAAAAbQAAAG0AAABtBjRDcQYzQ3EGM0NxBjRDcQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAawAAACsAAAAAAAAADwAAAGgAAABtAAAAbQAAAG0AAABtAAECbR6F + qIwnosumJqLLpiagyaQoqNOtJ6TOqR+KrY8AAABtGHORgiqu2rYop9KtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbR6FqIwmosumKKbQqyWex6IoptGrKKfSrSGPtJQBFh5uAAAAbQQqOG8lnMOfKq7atiWe + xqElnsahKq7atiWcw58EKjhvAAAAbQAAAG0AAABtAAAAbQAAAGgAAAAPAAAAOwAAAG0AAABtAAAAbQAA + AG0AAABtH4isjyqu2rYik7mXAAECbQAAAG0BDRRtII2xkiaiy6YYcI6BF2+MgCqu2rYpq9axH4msjx+J + rI8fiayPF22JfwAAAG0AAABtHoaojSqu2rYlncWgAAQGbQAAAG0BDRNtH4qukCqu2rYhj7SUBjREcSej + zKcqrtq2GXSTgwAAAG0AAABtHYOliyqu2rcmoMijAAAAbQAAAG0AAABtAAAAbQAAAG0AAAA7AAAAXAAA + AG0AAABtAAAAbQAAAG0AAABtJ6XPqSqu2rYnpM6oHoaojR2CpIodgqOKD1Nqdw9Ua3cAAABtF22KgCqu + 2rYpqdSvG3uchht7m4YfiayPKKnTrimp1K8MSl91JZ3GoSqv27cNTWJ1AAAAbQAAAG0AAABtC0VYcyqu + 2rYnpM6pDU1idSqv27clncWhAAAAbQAAAG0AAABtBCczbwY0RHEEKzhwAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABcAAAAawAAAG0AAABtAAAAbQAAAG0AAABtJ6XPqiqu2rYdg6WLHYKkih2CpIohkbaVJ6XOqSqu + 2rYjlr2aF22KgCqu2rYoqdOuAAAAbQAAAG0AAABtJZvDnyqu2rYVZ4J9JZvDnyqu2rcXb4yAAAAAbQAA + AG0AAABtC0RYcyqu2rYnpM6pDU1idSqv27cop9GrAQ0UbQAAAG0AAABtBCs4cBt7m4YbepqGAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABrAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtGneWhCqu2rUik7iWEVlxeAEU + HG4RWXF4JqDJpCqu2rYhj7OTF26LgCqu2rYoqNOuAAkObQEUHG4XbYqAKKbRqyqs2LMMSV50GniXhSqu + 2rYlncSgEFZueAEVHm4JQFNzJ6XPqSqt2bUXbYl/BjREcSejzKcqrtq2GXSTgwEWH24FLjxwIY6zkyqu + 2rYmn8ijAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAMEbRFZ + cXgkmcCcJqLLpSahyqUnosymJJvDnhp4mIUAAABtAAEBbR2Bo4ojlryZJqHKpSaiy6YnpM6oH4mtjxBX + bngAAABtAAAAbRVng30ik7iXJ6LLpiejzacnpM6oIZC1lBdtioAAAABtAAAAbQQrOHAdgqSKJJrCniek + zagnpM6oJJvCnh2CpIoEKzhwAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0DJjJvDU1idQ5PZXYWa4d/EVt0eQ9U + ancPUmh2CDtMcgAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0Wa4d/I5a9miagyKMpqtaxKq/btyqu + 2rcqrtq2Kq7atiqu2rYqrtq2KazXsyip064moMmjI5W8mRp4l4ULRVh0AAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAQFtF22KgCagyaQqrtq2Kq7atiqu + 2rYqrtq2Kq/btyy14sEtuejLLrroyy666MstuejLLLXiwSqv27cqrtq2Kq7atiqu2rYqrtq2JqLLpiSZ + wJwYc5GCEVlxeAAGC20AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0gjLGRKq7atiqu + 2rYqrtq2K7Pgvi2458grsd27KKnTriGRtpUhkbaVIZG2lSGRtpUhkbaVKarVsS256Movvu7WMMLz4C23 + 5cYqrtq2Kq7atiqu2rYqrtq2Kq7ZtSekzqgjlbuYGnmZhQIfKW4AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbSCM + sZEqrtq2Kq7atiqu2rYrst+9LLXjwgtGWXQAAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0EKjhvF26LgCmq1bAvv/DZMMHx2yux3rsqrtm1Kq7atiqu2rYqrtq2Kq7atht8nYcAAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtC0hcdCmr1rEqrtq2Kq7atiqu2rYhj7STAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0DIy9vHoWojCqv27cvwPDZL8Dw2i/A8NovwPDaKazXswUu + O3AAAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtJZzEnyqu2rYqrtq2Kq7ath+Iq44AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbAAA + AGwAAABsAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABsLbflxiqu2rYqrtq2Kq7atgg6S3IAAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABsFWeCfRVngn0UZYB8DUtgdQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0MS191LbjmyCqu2rYqrtq2JqLLpgAA + AG0AAABtAAAAbQAAAG0AAABtIIuvkSms17Mpq9axKq7ZtSqu2rYqrtq2Kq3ZtSms2LMjl76aBjNCcQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABsLbflxiqu + 2rYqrtq2KKnTrgQnM28AAABtAAAAbQAAAG0AAABtAREYbhdvjIAmosumLLTiwS/A8NsrsNy5Kq7atiqu + 2rYqrtq2J6PMpwQqOG8AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtLbbkxSqu2rYqrtq2Kq7athRlf3wAAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQ5R + Z3Yqrdi0LrrpzCqu2rYqrtq2Kq7atiGQtZQAAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtIZG2lS2458kqrtq2Kq7atiejzKcGM0JxAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0BDRRtLLXiwiqu2rYqrtq2Kq7ath6Fp4wAAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAQ8WbS2558orsNy5Kq7atiqu2rYnosymF22KfwAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0UZYB8KazYsyqu2rYqrtq2KarVsRFYcHgAAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQ1LYHUuu+vPK7DcuSqu + 2rYqrtq2Kq7atiahyaQfia2PGneWhBp4l4Ube5uGHoWnjCagyaQprNezKq7atiqu2rcstePCGHGPgQMi + LW8BDBJtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0NS2B1KarVsC+97NMrst+8Kq7atiqu2rYqrtq2Kq7atiqu2rYqrtq2Kq7atiqu2rYqrtq3LbjnyC66 + 6MsaeZmFAAAAbQpDVnMfiKuOEFdueAAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbRVog30stePDL77t1C6869Ett+XGLbflxi235cUstuTELrvqziy0 + 4cAqr9u4HoepjQEPFW0AAABtAAAAbQAAAG0YcY+BKqzYtBp3loQABAZtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAyYybxVngn0km8KeJJvCniSb + wp4ikreVDUtgdQEWHm4AAABtDUxidRZsiX8AAABtAAAAbQAAAG0AAABtJ6TNqCuw3bolncWgCkFTcwAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtGneWhAAA + AG0AAABtAAAAbQEPFm4be5uGAAAAbQAAAG0AAABtAAAAbSOVu5gik7mXAh4pbgAAAG0AAABtDUtgdSmq + 1K8qrtq2Kq3ZtRdui4AAAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtJJvDnhBWbngAAABtAAAAbQIbJG4stOHAG3ychwAAAG0AAABtAAAAbQ1NYnUqrtq2JqDJpAEW + Hm4AAABtAAAAbRdtioAstOHAKq7atiqt2bUkmsKeARYebgAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtJ6TNqCCNspIAAABtAAAAbQABAW0qrtq2J6PNpwYzQnEAAABtAAAAbQAA + AG0op9KsKq7ath2BoooAAABtAAAAbQAAAG0bfZ6HLLTiwCqu2rYqrtq2JqDJpAIeKW4AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtKazYsyio064DJjJvAAAAbQAAAG0jlr2aK7DcuSai + y6UAAABtAAAAbQAAAG0fiq2QLLXjwiqt2bUXbouAAAAAbQAAAG0AAABtG3ubhiy04sAqrtq2Kq7atiag + yaQEJjJvAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtJ6TOqSqu2rYdgaOKAAAAbQAA + AG0QWHB4LLbkwyqu2rUZdZSDAAAAbQAAAG0CFx9uLbjmxyqu2rcqrdm0F26LgAAAAG0AAABtAAAAbRt7 + m4YstOLAKq7atiqu2rYmoMmkAh4pbgAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtJJvCniqu + 2rYmocmkAAAAbQAAAG0AAABtLbbkxSqu2rYop9GsARYebgAAAG0AAABtF26LgC256Msqrtq2Kq3ZtRdt + iX8AAABtAAAAbQAAAG0cfZ6HMMHx3Cuw3Lkqrtq2JqDJpAEWHm4AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtHoepjS2458kqrtq2DU1idQAAAG0AAABtIZG2lSyz4L4qrtq2IY+0lAAAAG0AAABtAAEBbSin + 0awqr9u3Kq7atiqt2bUQVm54AAAAbQAAAG0AAABtDk9ldiuy37wuuunMKq7ath2Co4oAAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAh0obi666cwqrtq2H4qukAAAAG0AAABtCUBScy245scqrtq2Kq3ZtRNh + e3sAAABtAAAAbQ9SaXcvvu7VKq3ZtSqu2rYlncWgARUdbgAAAG0AAABtAAAAbQIYIG4eh6mNJqDJpAQr + OXAAAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbSy04cAqrtq2J6TOqAAAAG0AAABtAAAAbSqu + 2bUqr9u3Kq7atiWcw58AAABtAAAAbQAAAG0dg6WLLLXjwyqu2rYqrtq2JZzEnwAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbSekzqkqr9y4Kq3ZtQ1L + YHUAAABtAAAAbRNhe3stt+bHKq7atims17MUZH98AAAAbQAAAG0AAABtKq7atiuz4L4qrtq2KazXsxRl + gHwAAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbRp5 + mIUtuejLKq7ath+Iq44AAABtAAAAbQAAAG0rsd67Kq7atiqu2rYhj7SUAAAAbQAAAG0AAABtBCs4cCux + 3bsqrtq2Kq7atimr1rEAAABsAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAawAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0tt+XGKq/buB+JrI8AAABtAAAAbQAAAG0cfp+ILrzr0Cqu2rYpq9axDEpfdQAA + AG0AAABtAAAAbRNgensvvu3UKq7atiqu2rYehaiMAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABrAAAAXAAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0cf5+IL77u1RhykIIAAABtAAAAbQAAAG0ABgptLLXiwSqu + 2rYqrtq2HYGiigAAAG0AAABtAAAAbQAAAG0VZoJ9L77t1Cqu2rYmoMmjAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABcAAAAOwAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbAAAAG0AAABtAAAAbQAA + AG0AAABtEl54ei++7tUqrtq2I5W8mQAAAG0AAABtAAAAbQAAAG0AAABtFWeCfSyz4L8lnseiAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAAA7AAAADwAAAGgAAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbSOXvpostuPDI5a8mQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAGgAAAAPAAAAAAAA + ACsAAABrAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQEUHG4ik7iWGHKQggAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAawAA + ACsAAAAAAAAAAAAAAAAAAAArAAAAaAAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABoAAAAKwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwAAADsAAABcAAAAawAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAAAG0AAABtAAAAbQAA + AG0AAABrAAAAXAAAADsAAAAPAAAAAAAAAAAAAAAA4AAAAAAHAADAAAAAAAMAAIAAAAAAAQAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAAAABAADAAAAAAAMAAOAAAAAABwAAKAAAABAA + AAAgAAAAAQAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABYAAABbAAAAbQAAAG0AAABtAAAAbQAA + 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 + AAAAAElFTkSuQmCC + + + \ No newline at end of file diff --git a/Epoc/Program.cs b/Epoc/Program.cs new file mode 100644 index 0000000..daf2626 --- /dev/null +++ b/Epoc/Program.cs @@ -0,0 +1,31 @@ +using XWolf.Z; + +namespace Epoc +{ + internal static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main(string[] args) + { + if (args.Length > 0 && args[0] == "-repack") + Repack(); + else + { + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + Application.SetHighDpiMode(HighDpiMode.PerMonitorV2); + Application.Run(new Form1()); + } + } + + private static void Repack() + { + zPak z = new(); + + } + } +} \ No newline at end of file diff --git a/Epoc/Properties/PublishProfiles/FolderProfile.pubxml b/Epoc/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..16e5022 --- /dev/null +++ b/Epoc/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,18 @@ + + + + + Release + Any CPU + bin\Release\publish\ + FileSystem + <_TargetId>Folder + net9.0-windows + false + win-x86 + true + true + + \ No newline at end of file diff --git a/Epoc/Properties/PublishProfiles/FolderProfile.pubxml.user b/Epoc/Properties/PublishProfiles/FolderProfile.pubxml.user new file mode 100644 index 0000000..888326b --- /dev/null +++ b/Epoc/Properties/PublishProfiles/FolderProfile.pubxml.user @@ -0,0 +1,10 @@ + + + + + True|2025-02-12T23:00:51.0828684Z||;True|2025-02-12T23:50:49.9669136+01:00||; + + + \ No newline at end of file diff --git a/Epoc/Properties/Resources.Designer.cs b/Epoc/Properties/Resources.Designer.cs new file mode 100644 index 0000000..5269118 --- /dev/null +++ b/Epoc/Properties/Resources.Designer.cs @@ -0,0 +1,63 @@ +//------------------------------------------------------------------------------ +// +// Este código fue generado por una herramienta. +// Versión de runtime:4.0.30319.42000 +// +// Los cambios en este archivo podrían causar un comportamiento incorrecto y se perderán si +// se vuelve a generar el código. +// +//------------------------------------------------------------------------------ + +namespace Epoc.Properties { + using System; + + + /// + /// Clase de recurso fuertemente tipado, para buscar cadenas traducidas, etc. + /// + // StronglyTypedResourceBuilder generó automáticamente esta clase + // a través de una herramienta como ResGen o Visual Studio. + // Para agregar o quitar un miembro, edite el archivo .ResX y, a continuación, vuelva a ejecutar ResGen + // con la opción /str o recompile su proyecto de VS. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.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() { + } + + /// + /// Devuelve la instancia de ResourceManager almacenada en caché utilizada por esta clase. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Epoc.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Reemplaza la propiedad CurrentUICulture del subproceso actual para todas las + /// búsquedas de recursos mediante esta clase de recurso fuertemente tipado. + /// + [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/Epoc/Properties/Resources.resx b/Epoc/Properties/Resources.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/Epoc/Properties/Resources.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/Epoc/Properties/launchSettings.json b/Epoc/Properties/launchSettings.json new file mode 100644 index 0000000..fdd893e --- /dev/null +++ b/Epoc/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Epoc": { + "commandName": "Project", + "commandLineArgs": "-h" + } + } +} \ No newline at end of file diff --git a/res/Epoc-icon.ico b/res/Epoc-icon.ico new file mode 100644 index 0000000..90b8d55 --- /dev/null +++ b/res/Epoc-icon.ico Binary files differ diff --git a/res/Epoc-icon.png b/res/Epoc-icon.png new file mode 100644 index 0000000..93fc52f --- /dev/null +++ b/res/Epoc-icon.png Binary files differ diff --git a/res/Epoc-logo.png b/res/Epoc-logo.png new file mode 100644 index 0000000..7876986 --- /dev/null +++ b/res/Epoc-logo.png Binary files differ diff --git a/zPakr/Cmds.cs b/zPakr/Cmds.cs new file mode 100644 index 0000000..53c7df1 --- /dev/null +++ b/zPakr/Cmds.cs @@ -0,0 +1,59 @@ +namespace XWolf.Z +{ + internal class Cmds + { + public enum CmdAction { HELP, CREATE, ADD, LIST, EXTRACT, FILE, DUMP } + public Cmds(string[] args) + { + if (args.Length == 0) + return; + if (args[0].Length > 1 && args[0][0] != '-') + throw new ArgumentException("Parameters unknown."); + Action = args[0][1..].ToLower().Trim() switch + { + "c" => CmdAction.CREATE, + "a" => CmdAction.ADD, + "l" => CmdAction.LIST, + "x" => CmdAction.EXTRACT, + "f" => CmdAction.FILE, + "d" => CmdAction.DUMP, + "h" or "?" => CmdAction.HELP, + _ => throw new ArgumentException($"Unknown action {args[0]}."), + }; + if (Action == CmdAction.HELP) + if (args.Length > 1) + throw new ArgumentException($"Too many parameters for action {args[0]}."); + else return; + PakFile = args[1]; + switch (Action) + { + case CmdAction.CREATE: + case CmdAction.DUMP: + From = args.Length > 2 ? args[2] : throw new ArgumentException($"Missing parameters for action {args[0]}."); + if (args.Length > 3) + throw new ArgumentException($"Too many parameters for action {args[0]}."); + break; + case CmdAction.ADD: + case CmdAction.FILE: + From = args.Length > 2 ? args[2] : throw new ArgumentException($"Missing parameters for action {args[0]}."); + To = args.Length > 3 ? args[3] : string.Empty; + if (args.Length > 4) + throw new ArgumentException($"Too many parameters for action {args[0]}."); + break; + case CmdAction.LIST: + break; + case CmdAction.EXTRACT: + To = args.Length > 2 ? args[2] : string.Empty; + if (args.Length > 3) + throw new ArgumentException($"Too many parameters for action {args[0]}."); + break; + + } + } + + public CmdAction Action { get; set; } = CmdAction.HELP; + public string PakFile { get; set; } = string.Empty; + public string From { get; set; } = string.Empty; + public string To { get; set; } = string.Empty; + } +} diff --git a/zPakr/Program.cs b/zPakr/Program.cs new file mode 100644 index 0000000..9b68cfc --- /dev/null +++ b/zPakr/Program.cs @@ -0,0 +1,125 @@ +using XWolf.Z; + +Console.WriteLine("zPakr, a zPak file handler (C) XWolfOverride 2025. [https://github.com/XWolfOverride]"); +if (args.Length == 0) + DoHelp(); +else +#if !DEBUG + try + { +#else +{ +#endif + var cmds = new Cmds(args); + switch (cmds.Action) + { + case Cmds.CmdAction.CREATE: + DoCreate(cmds.PakFile, cmds.From); + break; + case Cmds.CmdAction.ADD: + DoAdd(cmds.PakFile, cmds.From, cmds.To); + break; + case Cmds.CmdAction.LIST: + DoList(cmds.PakFile); + break; + case Cmds.CmdAction.EXTRACT: + DoExtract(cmds.PakFile, cmds.To); + break; + case Cmds.CmdAction.FILE: + DoFile(cmds.PakFile, cmds.From, cmds.To); + break; + case Cmds.CmdAction.DUMP: + DoDump(cmds.PakFile, cmds.From); + break; + case Cmds.CmdAction.HELP: + DoHelp(); + break; + default: + throw new NotImplementedException(); + } + } +#if !DEBUG + catch (ArgumentException e) + { + Console.WriteLine($"[!! What?] {e.Message}"); + DoHelp(); + } + catch (NotImplementedException e) + { + Console.WriteLine($"[!! Where?] Something is missing! {e.Message}"); + } + catch (IOException e) + { + Console.WriteLine($"[!! I/O?] {e.Message}"); + } + catch (Exception e) + { + Console.WriteLine($"[!! Huh?] {e.GetType().Name}:{e.Message}."); + } +#endif + +static void DoHelp() +{ + Console.WriteLine(" usage:zPakr -c -l -x -f -d -h -?"); + Console.WriteLine(" -c ...... Create pak-file from all source-folder files."); + //Console.WriteLine(" -a [] Adds source-file to pak-file, file name is used if no pak-path defined."); + Console.WriteLine(" -l ...................... List files inside pak-file."); + Console.WriteLine(" -x [] ...... Extract all files from pak-file to dest-folder or current one."); + Console.WriteLine(" -f [] . Extract single file from pak-file to dest-path or orignal file name in current folder."); + Console.WriteLine(" -d ............... Dump contents of file in pak-file to sdtout."); + Console.WriteLine(" -h or -? ........................... This help, default if no arguments."); + Console.WriteLine(); + Console.WriteLine(" NOTE: Pak files maintains original folder structure, but does not record empty foldres."); + Console.WriteLine(" NOTE: Pak files are designed to work as stub on other files, -c can use any file with no pax-file data and appends a pak-file as stub maintaining original data."); +} + +static void DoCreate(string pakFile, string source) +{ + if (File.Exists(pakFile)) + File.Delete(pakFile); + var z = new zPak(pakFile); + //... + z.Close(); +} + +static void DoAdd(string pakFile, string source, string destPath) +{ + if (string.IsNullOrWhiteSpace(destPath)) + destPath = Path.GetFileName(source); + var z = new zPak(pakFile, false); + //... + z.Close(); + throw new NotImplementedException("Add command not supported right now."); +} + +static void DoList(string pakFile) +{ + var z = new zPak(pakFile, false); + //... + z.Close(); +} + +static void DoExtract(string pakFile, string destFolder) +{ + if (string.IsNullOrWhiteSpace(destFolder)) + destFolder = "."; + var z = new zPak(pakFile, false); + //... + z.Close(); +} + +static void DoFile(string pakFile, string file, string destPath) +{ + if (string.IsNullOrWhiteSpace(destPath)) + destPath = Path.GetFileName(file); + var z = new zPak(pakFile, false); + //... + z.Close(); +} + +static void DoDump(string pakFile, string file) +{ + var z = new zPak(pakFile, false); + //... + z.Close(); +} diff --git a/zPakr/Properties/launchSettings.json b/zPakr/Properties/launchSettings.json new file mode 100644 index 0000000..0bd63aa --- /dev/null +++ b/zPakr/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "zPakr": { + "commandName": "Project", + "commandLineArgs": "-c sample.pak ." + } + } +} \ No newline at end of file diff --git a/zPakr/zPak.cs b/zPakr/zPak.cs new file mode 100644 index 0000000..d6fef43 --- /dev/null +++ b/zPakr/zPak.cs @@ -0,0 +1,240 @@ +using System.Text; + +namespace XWolf.Z +{ + public class zPak + { + private const string ZPAK_HEADER = "zPak!\u001a"; + private const string ZPAK_FOOTER = "/zPak"; + private class zPackEntry + { + public string Name { get; set; } = ""; + public long EntryPosition { get; set; } = 0; + public long Position { get; set; } = 0; + public long Length { get; set; } = 0; + } + + private readonly List entries = []; + private string? fileName; + private Stream? stream; + private long offset = -1; + private bool writting = false; + + #region Constructors + public zPak() + { + + } + + public zPak(string name, bool autocreate = true) + { + Open(name, autocreate); + } + + public zPak(Stream stream, bool autocreate = true) + { + Open(stream, autocreate); + } + #endregion + + #region Stream / File access + public void Open(string name, bool autocreate = true) + { + fileName = name; + if (File.Exists(fileName)) + OpenForRead(autocreate); + else + if (autocreate) + OpenForWrite(); + else + throw new IOException("zPak does not exists."); + } + + private void OpenForRead(bool autocreate) + { + if (fileName == null) + throw new Exception("Can't reopen stream"); + Close(); + writting = false; + var file = new FileStream(fileName, FileMode.Open, FileAccess.Read); + OpenStream(file, autocreate); + } + + private void OpenForWrite() + { + if (fileName == null) + throw new Exception("Can't reopen stream"); + Close(); + writting = true; + var file = new FileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite); + OpenStream(file, true); + } + + public void Open(Stream stream, bool autocreate = true) + { + fileName = null; + OpenStream(stream, autocreate); + } + + private void OpenStream(Stream stream, bool autocreate) + { + this.stream = stream; + if (offset < 0) + { + ReadOffset(autocreate); + ReadDirectory(); + } + } + + public void Close() + { + if (stream == null) + return; + if (writting) + { + GetStream().Position = GetStream().Length; + WriteLong(offset); + WritePlainString(ZPAK_FOOTER); + } + if (stream != null) + { + stream.Close(); + stream = null; + } + } + private Stream GetStream(bool forWrite = false) + { + if (stream == null) + throw new IOException("Pak not opened."); + if (forWrite && !stream.CanWrite) + OpenForWrite(); + return stream; + } + #endregion + + #region Stream I/O + + private byte[] ReadBytes(int ctt) + { + byte[] buf = new byte[ctt]; + if (GetStream().Read(buf, 0, ctt) != ctt) + throw new IOException("Unexpected end of file."); + return buf; + } + + private int ReadInt() + { + return BitConverter.ToInt32(ReadBytes(4), 0); + } + + private long ReadLong() + { + return BitConverter.ToInt64(ReadBytes(8), 0); + } + + private string ReadPlainString(int len) + { + var strbuf = ReadBytes(len); + return Encoding.UTF8.GetString(strbuf, 0, strbuf.Length); + } + + private string ReadString() + { + return ReadPlainString(ReadInt()); + } + + private void WriteBytes(byte[] bytes) + { + GetStream(true).Write(bytes); + } + + private void WriteInt(int i) + { + WriteBytes(BitConverter.GetBytes(i)); + } + + private void WriteLong(long l) + { + WriteBytes(BitConverter.GetBytes(l)); + } + + private void WritePlainString(string str) + { + WriteBytes(Encoding.UTF8.GetBytes(str)); + } + + private void WriteString(string str) + { + WriteInt(str.Length); + WritePlainString(str); + } + + #endregion + + #region Pak header handling + private void ReadOffset(bool autocreate) + { + if (GetStream().Length > 0) + { + if (GetStream().Length > ZPAK_HEADER.Length + ZPAK_FOOTER.Length + 8) + { + GetStream().Position = GetStream().Length - ZPAK_FOOTER.Length; + if (ReadPlainString(8) == ZPAK_FOOTER) + { + GetStream().Position = GetStream().Length - (ZPAK_FOOTER.Length + 8); + offset = ReadLong(); + } + else + PreparePakStub(autocreate); + } + else + PreparePakStub(autocreate); + } + else + PreparePakStub(autocreate); + } + + private void PreparePakStub(bool autocreate) + { + if (!autocreate) + throw new IOException("Not a zPak."); + offset = GetStream().Position = GetStream().Length; + WritePlainString(ZPAK_HEADER); + } + + private zPackEntry ReadEntry() + { + var entry = new zPackEntry + { + EntryPosition = ZPos, + Name = ReadString(), + Position = ZPos, + Length = ReadLong() + }; + return entry; + } + + private void ReadDirectory() + { + entries.Clear(); + ZPos = 0; + if (ReadPlainString(ZPAK_HEADER.Length) != ZPAK_HEADER) + throw new Exception("Not a correct zPak file or corrupted"); + var end = GetStream().Length - (ZPAK_FOOTER.Length + 8); + while (GetStream().Position < end) + { + var ent = ReadEntry(); + GetStream().Seek(ent.Length, SeekOrigin.Current); + entries.Add(ent); + } + } + + private long ZPos + { + get { return GetStream().Position - offset; } + set { GetStream().Position = value + offset; } + } + #endregion + } + +} diff --git a/zPakr/zPakr.csproj b/zPakr/zPakr.csproj new file mode 100644 index 0000000..39e3eae --- /dev/null +++ b/zPakr/zPakr.csproj @@ -0,0 +1,11 @@ + + + + Exe + net9.0 + enable + enable + XWolf.Z + + +