diff --git a/QForegroundViewer/QForegroundViewer/QForegroundViewer.csproj b/QForegroundViewer/QForegroundViewer/QForegroundViewer.csproj index 6edcf43..c11eba8 100644 --- a/QForegroundViewer/QForegroundViewer/QForegroundViewer.csproj +++ b/QForegroundViewer/QForegroundViewer/QForegroundViewer.csproj @@ -50,6 +50,12 @@ + + Form + + + ToolboxForm.cs + Form @@ -65,6 +71,9 @@ ScreenshotForm.cs + + ToolboxForm.cs + ViewerMainForm.cs diff --git a/QForegroundViewer/QForegroundViewer/ScreenshotForm.Designer.cs b/QForegroundViewer/QForegroundViewer/ScreenshotForm.Designer.cs index 83541fc..52b11c1 100644 --- a/QForegroundViewer/QForegroundViewer/ScreenshotForm.Designer.cs +++ b/QForegroundViewer/QForegroundViewer/ScreenshotForm.Designer.cs @@ -35,6 +35,7 @@ this.FormatList = new System.Windows.Forms.ComboBox(); this.SaveButton = new System.Windows.Forms.Button(); this.RefreshButton = new System.Windows.Forms.Button(); + this.ClipboardButton = new System.Windows.Forms.Button(); this.groupBox1.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)(this.ScreenshotBox)).BeginInit(); this.groupBox2.SuspendLayout(); @@ -98,7 +99,7 @@ // // SaveButton // - this.SaveButton.Location = new System.Drawing.Point(12, 188); + this.SaveButton.Location = new System.Drawing.Point(12, 229); this.SaveButton.Name = "SaveButton"; this.SaveButton.Size = new System.Drawing.Size(153, 55); this.SaveButton.TabIndex = 4; @@ -116,11 +117,22 @@ this.RefreshButton.UseVisualStyleBackColor = true; this.RefreshButton.Click += new System.EventHandler(this.RefreshButton_Click); // + // ClipboardButton + // + this.ClipboardButton.Location = new System.Drawing.Point(13, 189); + this.ClipboardButton.Name = "ClipboardButton"; + this.ClipboardButton.Size = new System.Drawing.Size(171, 29); + this.ClipboardButton.TabIndex = 6; + this.ClipboardButton.Text = "Copy to clipboard"; + this.ClipboardButton.UseVisualStyleBackColor = true; + this.ClipboardButton.Click += new System.EventHandler(this.ClipboardButton_Click); + // // ScreenshotForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(800, 333); + this.Controls.Add(this.ClipboardButton); this.Controls.Add(this.RefreshButton); this.Controls.Add(this.SaveButton); this.Controls.Add(this.groupBox2); @@ -144,5 +156,6 @@ private System.Windows.Forms.Button SaveButton; private System.Windows.Forms.Button RefreshButton; private System.Windows.Forms.ComboBox ScreenshotList; + private System.Windows.Forms.Button ClipboardButton; } } \ No newline at end of file diff --git a/QForegroundViewer/QForegroundViewer/ScreenshotForm.cs b/QForegroundViewer/QForegroundViewer/ScreenshotForm.cs index 1701638..8ac7c9e 100644 --- a/QForegroundViewer/QForegroundViewer/ScreenshotForm.cs +++ b/QForegroundViewer/QForegroundViewer/ScreenshotForm.cs @@ -94,5 +94,11 @@ namespace QForegroundViewer MessageBox.Show("Screenshot successfully saved!"); } } + + private void ClipboardButton_Click(object sender, EventArgs e) + { + Clipboard.SetImage(ScreenshotBox.Image); + MessageBox.Show("Selected screenshot copied to clipboard."); + } } } diff --git a/QForegroundViewer/QForegroundViewer/ToolboxForm.Designer.cs b/QForegroundViewer/QForegroundViewer/ToolboxForm.Designer.cs new file mode 100644 index 0000000..eaba819 --- /dev/null +++ b/QForegroundViewer/QForegroundViewer/ToolboxForm.Designer.cs @@ -0,0 +1,75 @@ +namespace QForegroundViewer +{ + partial class ToolboxForm + { + /// + /// 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.ScreenshotButton = new System.Windows.Forms.Button(); + this.AboutButton = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // ScreenshotButton + // + this.ScreenshotButton.Location = new System.Drawing.Point(12, 74); + this.ScreenshotButton.Name = "ScreenshotButton"; + this.ScreenshotButton.Size = new System.Drawing.Size(219, 23); + this.ScreenshotButton.TabIndex = 0; + this.ScreenshotButton.Text = "Save screenshot"; + this.ScreenshotButton.UseVisualStyleBackColor = true; + this.ScreenshotButton.Click += new System.EventHandler(this.ScreenshotButton_Click); + // + // AboutButton + // + this.AboutButton.Location = new System.Drawing.Point(12, 12); + this.AboutButton.Name = "AboutButton"; + this.AboutButton.Size = new System.Drawing.Size(222, 45); + this.AboutButton.TabIndex = 1; + this.AboutButton.Text = "About {qlaunch-reimpl}"; + this.AboutButton.UseVisualStyleBackColor = true; + this.AboutButton.Click += new System.EventHandler(this.AboutButton_Click); + // + // ToolboxForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(246, 450); + this.Controls.Add(this.AboutButton); + this.Controls.Add(this.ScreenshotButton); + this.Cursor = System.Windows.Forms.Cursors.Default; + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; + this.Name = "ToolboxForm"; + this.Text = "Toolbox"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button ScreenshotButton; + private System.Windows.Forms.Button AboutButton; + } +} \ No newline at end of file diff --git a/QForegroundViewer/QForegroundViewer/ToolboxForm.cs b/QForegroundViewer/QForegroundViewer/ToolboxForm.cs new file mode 100644 index 0000000..79502ec --- /dev/null +++ b/QForegroundViewer/QForegroundViewer/ToolboxForm.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace QForegroundViewer +{ + public partial class ToolboxForm : Form + { + private ViewerMainForm main; + + public ToolboxForm(ViewerMainForm Main) + { + InitializeComponent(); + main = Main; + } + + private void AboutButton_Click(object sender, EventArgs e) + { + // TODO: change this before release + Process.Start("https://github.com/XorTroll/unnamed-qlaunch-reimpl"); + } + + private void ScreenshotButton_Click(object sender, EventArgs e) + { + new ScreenshotForm(main).ShowDialog(); + } + } +} diff --git a/QForegroundViewer/QForegroundViewer/ToolboxForm.resx b/QForegroundViewer/QForegroundViewer/ToolboxForm.resx new file mode 100644 index 0000000..29dcb1b --- /dev/null +++ b/QForegroundViewer/QForegroundViewer/ToolboxForm.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/QForegroundViewer/QForegroundViewer/ViewerMainForm.Designer.cs b/QForegroundViewer/QForegroundViewer/ViewerMainForm.Designer.cs index 771803d..680e9f1 100644 --- a/QForegroundViewer/QForegroundViewer/ViewerMainForm.Designer.cs +++ b/QForegroundViewer/QForegroundViewer/ViewerMainForm.Designer.cs @@ -29,10 +29,7 @@ private void InitializeComponent() { this.CaptureBox = new System.Windows.Forms.PictureBox(); - this.groupBox1 = new System.Windows.Forms.GroupBox(); - this.ScreenshotButton = new System.Windows.Forms.Button(); ((System.ComponentModel.ISupportInitialize)(this.CaptureBox)).BeginInit(); - this.groupBox1.SuspendLayout(); this.SuspendLayout(); // // CaptureBox @@ -42,42 +39,20 @@ this.CaptureBox.Location = new System.Drawing.Point(0, 0); this.CaptureBox.Margin = new System.Windows.Forms.Padding(0); this.CaptureBox.Name = "CaptureBox"; - this.CaptureBox.Size = new System.Drawing.Size(812, 331); + this.CaptureBox.Size = new System.Drawing.Size(624, 321); this.CaptureBox.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; this.CaptureBox.TabIndex = 0; this.CaptureBox.TabStop = false; // - // groupBox1 - // - this.groupBox1.Controls.Add(this.ScreenshotButton); - this.groupBox1.Location = new System.Drawing.Point(12, 12); - this.groupBox1.Name = "groupBox1"; - this.groupBox1.Size = new System.Drawing.Size(200, 282); - this.groupBox1.TabIndex = 1; - this.groupBox1.TabStop = false; - this.groupBox1.Text = "Display options"; - // - // ScreenshotButton - // - this.ScreenshotButton.Location = new System.Drawing.Point(20, 19); - this.ScreenshotButton.Name = "ScreenshotButton"; - this.ScreenshotButton.Size = new System.Drawing.Size(160, 23); - this.ScreenshotButton.TabIndex = 0; - this.ScreenshotButton.Text = "Save screenshot"; - this.ScreenshotButton.UseVisualStyleBackColor = true; - this.ScreenshotButton.Click += new System.EventHandler(this.ScreenshotButton_Click); - // // ViewerMainForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(812, 331); - this.Controls.Add(this.groupBox1); + this.ClientSize = new System.Drawing.Size(624, 321); this.Controls.Add(this.CaptureBox); this.Name = "ViewerMainForm"; this.Text = "{qlaunch-reimpl} - Foreground display viewer"; ((System.ComponentModel.ISupportInitialize)(this.CaptureBox)).EndInit(); - this.groupBox1.ResumeLayout(false); this.ResumeLayout(false); } @@ -85,8 +60,6 @@ #endregion private System.Windows.Forms.PictureBox CaptureBox; - private System.Windows.Forms.GroupBox groupBox1; - private System.Windows.Forms.Button ScreenshotButton; } } diff --git a/QForegroundViewer/QForegroundViewer/ViewerMainForm.cs b/QForegroundViewer/QForegroundViewer/ViewerMainForm.cs index 2e31bb7..3fe0b30 100644 --- a/QForegroundViewer/QForegroundViewer/ViewerMainForm.cs +++ b/QForegroundViewer/QForegroundViewer/ViewerMainForm.cs @@ -45,6 +45,8 @@ namespace QForegroundViewer Close(); } + new ToolboxForm(this).Show(); + USBThread = new Thread(new ThreadStart(USBThreadMain)); USBThread.Start(); } @@ -79,11 +81,6 @@ namespace QForegroundViewer return true; } - private void ScreenshotButton_Click(object sender, EventArgs e) - { - new ScreenshotForm(this).ShowDialog(); - } - public static void InitializePictureBox(PictureBox Box) { int w = 1280; diff --git a/qlaunch.md b/qlaunch.md new file mode 100644 index 0000000..431d6f4 --- /dev/null +++ b/qlaunch.md @@ -0,0 +1,81 @@ +# qlaunch + +qlaunch is the name of the process officially known as **HOME menu**. + +## Official + +- qlaunch uses ~56MB memory from applet pool. + +- qlaunch is a **system applet**, a special and unique type of applet. + + - There are 3 types of applets: **system applet** (qlaunch), **overlay applet** (overlay, another special title), and the rest, which are **library applets** (software keyboard, user selector, web applets...) + + - Development kits or Kiosk units may have different system and overlay titles: for instance, development consoles' overlay is a different one, some can launch DevMenu instead of normal qlaunch, and Kiosk units have special menus instead of qlaunch (known as Retail Interactive Display Menu) + + - Both qlaunch and overlayDisp are special, since they take care of unique and essential functionality, thus they have more access and privileges than any other applet/application. + + - Some of the privileges qlaunch has: title management (is the only one who can directly launch, suspend and close titles), special applet messages (like HOME button press, any other applet or application has these messages but qlaunch has special ones), the general channel (a channel where any applet/application can communicate to qlaunch to request things via storages and "SAMS" messages), foreground control... + +- According to RE and left strings, it is qlaunch who handles periodical play report telemetry, aka **is the one who makes play reports and sends them to Nintendo**. Then, **prepo** system service is the one who queues the reports and uploads them when possible. + + - Those play reports contain **a lot** of information about the console, but it is worth to mention that, among them, there is the list of launched titles. If N detects that there is any kind of irregulatity with that list (custom installed titles, for instance), it will likely result in a guaranteed ban. + + - Note that reports are apparently saved even if there is no connection, so that they will be uploaded to N's servers as soon as connection is established. Thus, blocking connection to N's servers (via 90DNS, for example) is, as long as you did ban-baity stuff, a way to delay one's fate. Plain homebrew, aka not touching games or doing suspicious stuff, should not risk bans, but the actual range those have is still unclear. + +- qlaunch doesn't just contain the main menu, it does also contain: **news**, **console settings**, **lock screen**... (did I forgot any?) + +- Surprisingly, qlaunch's basic functionality (launching titles, detecting HOME press, systems other applets use to interact with it) hasn't (apparently) changed, at least not since 5.1.0 (oldest firmware I did tests related to qlaunch) + +## Custom implementation: {qlaunch-reimpl} + +### Homebrew limitations + +Problems arose when starting to work on a proper, serious custom qlaunch reimplementation project, which started by the temporary name **Project Home**, and later got renamed to **eQlipse**. + +Homebrew libraries' base GPU libraries (mesa) require **a lot** of memory. Since normal homebrew is expected to run as a library applet or an application, it has never supposed any issues (by default, homebrew libraries tend to reserve as much mem as they can, which tends to be a few hundreds of MBs). + +Speaking of a custom type of applet which must use ~50-60MB to not leave the applet pool dry, this supposes a problem. + +In fact, this is why the idea of a qlaunch reimplementation was almost abandoned: + +- eQlipse used ~180MB, which was the minimum to make the UI work (still sometimes struggling to render properly). + +- After updating to latest SDL2 and Plutonium, UI wouldn't even work with more than that (and I was already using more than 3 times the memory I should use!) + +- In addition to that, eQlipse's code was really bad organised, and changing a small thing could be really tedious. + +### Workaround and re-start + +I started to work on a new, well organised and optimized reimplementation before memory issues became unbearable, and this rewrite, after finding a workaround for the memory issue, would become the current project. + +The final workaround for the reimplementation was, simply, to **separate the UI from the actual qlaunch functionality** in different processes: + +- The actual qlaunch process would be **a daemon process, a backend**, which would make no use of anything UI-related (thus saving **A LOT** of memory, now using even way less than official qlaunch), and which would -**just perform what it is asked to do**, since it is the one with privileges. + +- The menu the user would interact with would be a **separate library applet**. Instead of having it always opened, the applet is closed when an application or an applet is launched, and reopened when pressing HOME. + +*Result*: using as much memory I want from the applet pool for the UI without being a problem, and the actual qlaunch process wasting ~40MB less memory than usual! + +In fact, the fact that the menu gets closed and re-opened very often is helpful in order to update records, since normal qlaunch would have to listen to an event to see if any new one was added or removed, while this way there is no need to do that. + +### Homebrew and forwarding + +A common practice to be able to easily launch homebrew, from the main menu and as application, is to take advantage of the *title override* feature most CFWs provide, or as a last resource, to install forwarder NSPs to target homebrew located at the SD card. + +Nevertheless, the latter is ban-baity idea, not to mention the disapproval of a big part of homebrew developers due to its tight relation to piracy. + +Since eQlipse, I've taken advantage of a lost gem in the switch's system memory: **flog**. + +"flog" easter egg was a special application consisting on a NES emulator with a hardcoded ROM of the NES Golf game. Hackers discovered it and people were really impressed of the easter egg (related to Iwata by the way it had to be accessed), but N quickly spoiled the fun of the discovery, leaving it **stubbed** on 4.0.0. + +It is important to note that it was stubbed, not removed. That means that the title is there, in your console's memory (on any version), but no longer launchable via the HOME menu trick, and the code would do nothing if it was launched. + +However, the fact that it is still there means that **every console has an application title built-in in their systems**. In fact, flog has a unique title category: it is a **system application**, which is completely similar to a normal application, but the difference is that these kind of titles come within the system. + +Theorerically, flog isn't the only system application, since **starter** (that special menu shown on the initial configuration of the console) seems to be one too. + +The idea of a custom way to target homebrew started in eQlipse, where there were two custom titles: a library applet and a system application, both wrappers of nx-hbloader, which took advantage of library applets' argument storages to target homebrew. + +For the rewrite I basically ported both, with some minor fixes and improvements, but the idea and functionality was the same. + +Both of them replace certain console titles (eShop applet and flog system application) so launching those with certain input arguments can be used as an improved hbloader. \ No newline at end of file