QForegroundViewer -> uViewer, many other changes

This commit is contained in:
XorTroll 2019-11-22 19:50:32 +01:00
parent 8210c85202
commit b1de9bd10e
70 changed files with 98968 additions and 577 deletions

View file

@ -56,9 +56,16 @@ namespace cfg
ThemeManifest manifest; ThemeManifest manifest;
}; };
struct RecordStrings
{
std::string name;
std::string author;
std::string version;
};
struct RecordInformation struct RecordInformation
{ {
NacpStruct nacp; RecordStrings strings;
std::string icon_path; std::string icon_path;
}; };
@ -82,7 +89,6 @@ namespace cfg
std::vector<TitleRecord> QueryAllHomebrew(std::string base = "sdmc:/switch"); std::vector<TitleRecord> QueryAllHomebrew(std::string base = "sdmc:/switch");
std::string GetRecordIconPath(TitleRecord record); std::string GetRecordIconPath(TitleRecord record);
RecordInformation GetRecordInformation(TitleRecord record); RecordInformation GetRecordInformation(TitleRecord record);
NacpLanguageEntry *GetRecordInformationLanguageEntry(RecordInformation &info);
Theme LoadTheme(std::string base_name); Theme LoadTheme(std::string base_name);
std::vector<Theme> LoadThemes(); std::vector<Theme> LoadThemes();

View file

@ -78,6 +78,27 @@ namespace cfg
return icon; return icon;
} }
static void ProcessStringsFromNACP(RecordStrings &strs, NacpStruct *nacp)
{
NacpLanguageEntry *lent = NULL;
nacpGetLanguageEntry(nacp, &lent);
if(lent == NULL)
{
for(u32 i = 0; i < 16; i++)
{
lent = &nacp->lang[i];
if(strlen(lent->name) && strlen(lent->author)) break;
lent = NULL;
}
}
if(lent != NULL)
{
strs.name = lent->name;
strs.author = lent->author;
strs.version = nacp->version;
}
}
RecordInformation GetRecordInformation(TitleRecord record) RecordInformation GetRecordInformation(TitleRecord record)
{ {
RecordInformation info = {}; RecordInformation info = {};
@ -99,8 +120,12 @@ namespace cfg
{ {
if(ahdr.nacp.size > 0) if(ahdr.nacp.size > 0)
{ {
NacpStruct nacp = {};
fseek(f, hdr.size + ahdr.nacp.offset, SEEK_SET); fseek(f, hdr.size + ahdr.nacp.offset, SEEK_SET);
fread(&info.nacp, 1, ahdr.nacp.size, f); fread(&nacp, 1, ahdr.nacp.size, f);
ProcessStringsFromNACP(info.strings, &nacp);
} }
} }
} }
@ -112,36 +137,14 @@ namespace cfg
{ {
NsApplicationControlData cdata = {}; NsApplicationControlData cdata = {};
nsGetApplicationControlData(1, record.app_id, &cdata, sizeof(cdata), NULL); nsGetApplicationControlData(1, record.app_id, &cdata, sizeof(cdata), NULL);
memcpy(&info.nacp, &cdata.nacp, sizeof(cdata.nacp)); ProcessStringsFromNACP(info.strings, &cdata.nacp);
} }
if(!record.name.empty()) if(!record.name.empty()) info.strings.name = record.name;
{ if(!record.author.empty()) info.strings.author = record.author;
for(u32 i = 0; i < 0x10; i++) strcpy(info.nacp.lang[i].name, record.name.c_str()); if(!record.version.empty()) info.strings.version = record.version;
}
if(!record.author.empty())
{
for(u32 i = 0; i < 0x10; i++) strcpy(info.nacp.lang[i].author, record.author.c_str());
}
if(!record.version.empty()) strcpy(info.nacp.version, record.version.c_str());
return info; return info;
} }
NacpLanguageEntry *GetRecordInformationLanguageEntry(RecordInformation &info)
{
NacpLanguageEntry *lent = NULL;
nacpGetLanguageEntry(&info.nacp, &lent);
if(lent == NULL)
{
for(u32 i = 0; i < 16; i++)
{
lent = &info.nacp.lang[i];
if(strlen(lent->name) && strlen(lent->author)) break;
lent = NULL;
}
}
return lent;
}
Theme LoadTheme(std::string base_name) Theme LoadTheme(std::string base_name)
{ {
Theme theme = {}; Theme theme = {};

View file

@ -0,0 +1,17 @@
#include <os/os_HomeMenu.hpp>
namespace os
{
Result PushSystemAppletMessage(SystemAppletMessage msg)
{
AppletStorage st;
auto rc = appletCreateStorage(&st, sizeof(msg));
if(R_SUCCEEDED(rc))
{
rc = appletStorageWrite(&st, 0, &msg, sizeof(msg));
if(R_SUCCEEDED(rc)) appletPushToGeneralChannel(&st);
appletStorageClose(&st);
}
return rc;
}
}

View file

@ -94,7 +94,7 @@
"set_enable_conf": "Do you want to enable it?", "set_enable_conf": "Do you want to enable it?",
"set_disable_conf": "Do you want to disable it?", "set_disable_conf": "Do you want to disable it?",
"set_changed_reboot": "Done. A reboot is required (the option won't be used until then)", "set_changed_reboot": "Done. A reboot is required (the option won't be used until then)",
"set_viewer_info": "You must enable this if you want to use the PC foreground viewer (QForegroundViewer). It is not necessary otherwise.", "set_viewer_info": "You must enable this if you want to use the PC foreground viewer (uViewer). It is not necessary otherwise.",
"set_flog_info": "This must be enabled to be able to launch homebrew directly as applications. Note that this might involve BAN RISK.", "set_flog_info": "This must be enabled to be able to launch homebrew directly as applications. Note that this might involve BAN RISK.",
"startup_welcome_info": "Welcome! Please select an account to use.", "startup_welcome_info": "Welcome! Please select an account to use.",
"startup_control_info": "Hold the L or R-stick in order to open menus easily from main menu.", "startup_control_info": "Hold the L or R-stick in order to open menus easily from main menu.",

View file

@ -490,19 +490,16 @@ namespace ui
realidx--; realidx--;
auto hb = homebrew[realidx]; auto hb = homebrew[realidx];
auto info = cfg::GetRecordInformation(hb); auto info = cfg::GetRecordInformation(hb);
auto lent = cfg::GetRecordInformationLanguageEntry(info);
if(lent != NULL) if(info.strings.name.empty()) this->itemName->SetText(cfg::GetLanguageString(config.main_lang, config.default_lang, "unknown"));
{ else this->itemName->SetText(info.strings.name);
this->itemName->SetText(lent->name);
this->itemAuthor->SetText(lent->author); if(info.strings.author.empty()) this->itemAuthor->SetText(cfg::GetLanguageString(config.main_lang, config.default_lang, "unknown"));
} else this->itemAuthor->SetText(info.strings.author);
else
{ if(info.strings.version.empty()) this->itemVersion->SetText("0");
this->itemName->SetText(cfg::GetLanguageString(config.main_lang, config.default_lang, "unknown")); else this->itemVersion->SetText(info.strings.version);
this->itemAuthor->SetText(cfg::GetLanguageString(config.main_lang, config.default_lang, "unknown"));
}
if(strlen(info.nacp.version)) this->itemVersion->SetText(info.nacp.version);
else this->itemVersion->SetText("0");
this->bannerImage->SetImage(cfg::GetAssetByTheme(theme, "ui/BannerHomebrew.png")); this->bannerImage->SetImage(cfg::GetAssetByTheme(theme, "ui/BannerHomebrew.png"));
} }
} }
@ -532,19 +529,16 @@ namespace ui
{ {
auto title = folder.titles[titleidx]; auto title = folder.titles[titleidx];
auto info = cfg::GetRecordInformation(title); auto info = cfg::GetRecordInformation(title);
auto lent = cfg::GetRecordInformationLanguageEntry(info);
if(lent != NULL) if(info.strings.name.empty()) this->itemName->SetText(cfg::GetLanguageString(config.main_lang, config.default_lang, "unknown"));
{ else this->itemName->SetText(info.strings.name);
this->itemName->SetText(lent->name);
this->itemAuthor->SetText(lent->author); if(info.strings.author.empty()) this->itemAuthor->SetText(cfg::GetLanguageString(config.main_lang, config.default_lang, "unknown"));
} else this->itemAuthor->SetText(info.strings.author);
else
{ if(info.strings.version.empty()) this->itemVersion->SetText("0");
this->itemName->SetText(cfg::GetLanguageString(config.main_lang, config.default_lang, "unknown")); else this->itemVersion->SetText(info.strings.version);
this->itemAuthor->SetText(cfg::GetLanguageString(config.main_lang, config.default_lang, "unknown"));
}
if(strlen(info.nacp.version)) this->itemVersion->SetText(info.nacp.version);
else this->itemVersion->SetText("0");
if((cfg::TitleType)title.title_type == cfg::TitleType::Homebrew) this->bannerImage->SetImage(cfg::GetAssetByTheme(theme, "ui/BannerHomebrew.png")); if((cfg::TitleType)title.title_type == cfg::TitleType::Homebrew) this->bannerImage->SetImage(cfg::GetAssetByTheme(theme, "ui/BannerHomebrew.png"));
else this->bannerImage->SetImage(cfg::GetAssetByTheme(theme, "ui/BannerInstalled.png")); else this->bannerImage->SetImage(cfg::GetAssetByTheme(theme, "ui/BannerInstalled.png"));
} }

View file

@ -101,7 +101,7 @@ namespace ui
x += (SubItemsSize - texw) / 2; x += (SubItemsSize - texw) / 2;
y += (SubItemsSize - texh) / 2; y += (SubItemsSize - texh) / 2;
if(direction == dir) SDL_SetTextureColorMod(tex, 200, 200, 255); if(direction == dir) SDL_SetTextureColorMod(tex, 150, 150, 200);
else SDL_SetTextureColorMod(tex, 255, 255, 255); else SDL_SetTextureColorMod(tex, 255, 255, 255);
Drawer->RenderTexture(tex, x, y, { fgalpha, texw, texh, -1 }); Drawer->RenderTexture(tex, x, y, { fgalpha, texw, texh, -1 });

View file

@ -1,25 +0,0 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28803.156
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QForegroundViewer", "QForegroundViewer\QForegroundViewer.csproj", "{667868E3-08A1-49C4-8898-FBFACD70D3A8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{667868E3-08A1-49C4-8898-FBFACD70D3A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{667868E3-08A1-49C4-8898-FBFACD70D3A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{667868E3-08A1-49C4-8898-FBFACD70D3A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{667868E3-08A1-49C4-8898-FBFACD70D3A8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3DC56532-83F0-4FA0-A971-E32BD10BD01E}
EndGlobalSection
EndGlobal

View file

@ -1,120 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>

138
README.md
View file

@ -16,22 +16,7 @@ uLaunch is a very ambitious project, consisting on two custom library applets, a
- For those who are interested in how the UI was done, this project is, like [Goldleaf](https://github.com/XorTroll/Goldleaf), a good example of how powerful [Plutonium libraries](https://github.com/XorTroll/Plutonium) can be in order to make beautiful UIs. - For those who are interested in how the UI was done, this project is, like [Goldleaf](https://github.com/XorTroll/Goldleaf), a good example of how powerful [Plutonium libraries](https://github.com/XorTroll/Plutonium) can be in order to make beautiful UIs.
## Are you looking for help with themes? Check [this documentation](Themes.md) for everything you need! ## For more detailed information about the whole project (themeing too), check its [wiki](/wiki)!
## Having trouble with uLaunch? Check the [FAQ](#faq) section for support!
### **Table of contents**
1. [Features](#features)
2. [Disclaimer](#disclaimer)
3. [FAQ](#faq)
4. [Project and subprojects](#project-and-subprojects)
5. [Custom menu entries](#custom-menu-entries)
6. [Translations](#translations)
7. [Installing and removing](#installing-and-removing)
8. [Compiling](#compiling)
9. [Errors](#errors)
10. [Credits](#credits)
## Features ## Features
@ -97,7 +82,7 @@ This is the amount of features uLaunch contains, compared to the original HOME m
- Web browsing (via web-applet) directly from the main menu! - Web browsing (via web-applet) directly from the main menu!
- **Foreground capturing** from PC itself (*Windows*-only) via USB-C cable and *QForegroundViewer*! - **Foreground capturing** from PC itself (*Windows*-only) via USB-C cable and *uViewer*!
## Disclaimer ## Disclaimer
@ -107,117 +92,6 @@ uLaunch allows you to launch homebrew as an application, taking advantage of the
Since launching this title should be impossible, it might involve ban risk. uLaunch has this option **disabled by default**, so enable and use it **use it at your own risk**. Always make youre you're safe from bans (by using tools like 90DNS) before using uLaunch to avoid any possible risks. Since launching this title should be impossible, it might involve ban risk. uLaunch has this option **disabled by default**, so enable and use it **use it at your own risk**. Always make youre you're safe from bans (by using tools like 90DNS) before using uLaunch to avoid any possible risks.
## FAQ
- *How do I uninstall installed titles? How do I delete user accounts?*
- uLaunch doesn't (and won't) support these, since there is already existing homebrew serving as title or user managers, like [Goldleaf](https://github.com/XorTroll/Goldleaf).
- *How can I change my controllers, access eShop or view the album?*
- These are all TODOs for uLaunch (not yet supported), except eShop, which for what it's worth, won't be supported. If you want to use eShop, remove uLaunch and use the original HOME menu.
> TODO: add more FAQs here
## Project and subprojects
uLaunch is split, as mentioned above, into several sub-projects:
### QDaemon (SystemAppletQDaemon)
> This sub-project replaces qlaunch, aka title 0100000000001000.
This is the technically actual qlaunch reimplementation. In fact, it is used as a back-end for the project, which just does what the actual menu (QMenu) tells it to do. This had to be like that, since qlaunch is the only one who can access certain essential functionality like title launching, so QMenu needs to access it, so it communicates with this back-end to execute it and to obtain its outcome.
### QMenu (LibraryAppletQMenu)
> This sub-project uses eShop's library applet as a donor title, aka 010000000000100B.
This is the "actual" HOME menu the user will see and interact with. It contains all the UI and sounc functionality, password, themes...
### QHbTarget
This is the name for two related projects, whose aim is to target and launch homebrew NROs in a simple way. It can be considered as a simple and helpful wrapper around [nx-hbloader](https://github.com/switchbrew/nx-hbloader)'s code:
#### System application (SystemApplicationQHbTarget)
> This sub-project replaces "flog" system application, aka title 01008BB00013C000.
This is the process which runs instead of flog, which is used to launch homebrew as applications.
#### Library applet (LibraryAppletQHbTarget)
> This sub-project replaces "dataErase" library applet, aka title 0100000000001001.
This is the same process but, like in normal HOME menu and Album, it runs homebrew as an applet. However, exiting homebrew here will exit to HOME menu instead of exiting to hbmenu.
### QForegroundViewer
*QDaemon* backend process, appart from handling HOME menu functionality, also sends the console's foreground display via USB (if enabled on settings) as a raw RGBA8888 1280x720 ~3.5MB buffer. **QForegroundViewer** is the PC tool (Windows Forms) which intercepts those sent buffers and renders them. It is a bit laggy, but can be *very, very* useful for taking quick screenshots!
This option comes disabled by default because homebrew using USB (like Goldleaf or nxmtp) would interfer with the connection. If you really want to use it, enable it from settings.
This tool was used to take the screenshots shown above, all in full 1280x720 quality.
Unlike the other projects, this one isn't essential and uLaunch would be perfectly usable ignoring this sent data.
For more technical information about uLaunch and qlaunch, check [this](HOME.md).
## Custom menu entries
uLaunch supports adding custom shortcuts. This means you can show entries with a custom icon, which launch a NRO with certain argv, what could be potentially used for stuff like emulator ROM shorcuts.
Entries consist on JSON files (one per entry) located at `sd:/ulaunch/entries` (name doesn't matter, but the extension must be `*.json`!). They are used for two main purposes:
- Adding homebrew accesses (as stated above) to the main menu
- Specifying that a homebrew access or a normal, installed game/application go inside a folder
If you want to make your own access, create a JSON file in the entries folder (the name doesn't matter) like this:
```json
{
"type": 2,
"nro_path": "<path-to-nro>",
"nro_argv": "<custom-argv>",
"icon": "<custom-icon-256x256>",
"name": "Name (511 bytes max.)",
"author": "Author (255 bytex max.)",
"version": "Version (15 bytes max.)"
}
```
Note that:
- The type needs to be **always 2** (1 = installed title, other values are invalid), and that `type` and `nro_path` **must be always present**. The other parameters are all optional.
- If `name`, `author` or `version` fields aren't set they will be loaded from the NRO, thus it would show the RetroArch core's name, etc., same with the icon.
- The argv string must not include the NRO path. Usual homebrew behaviour would be `<nro-path> <arg-1> <arg-2> <...>`, but uLaunch itself appends the NRO path before the specified argv when launching the menu entry, so you should just provide `<arg-1> <arg-2> <...>`
## Translations
uLaunch is in English by default. However, you can make translations by editing the [LangDefault.json](https://github.com/XorTroll/uLaunch/blob/master/LibraryAppletQMenu/RomFs/LangDefault.json) translation file's strings and placing it into `sd:/ulaunch/lang` named as the language's code ("en-US" for American English, "es" for Spanish, "fr" for French... proper list [here](https://switchbrew.org/wiki/Settings_services#LanguageCode)
Example: `sd:/ulaunch/lang/es.json` for custom strings (which would be displayed if the console's language is Spanish)
Note that translation JSONs aren't provided by uLaunch, and that you will need to find or make your own. No PRs with translations will be accepted here.
## Installing and removing
In order to check uLaunch's installation, you will need to pay attention to the `titles` directory of the CFW you're using.
### How do I know whether it is installed?
Check if folders `0100000000001000`, `010000000000100B`, `0100000000001001` and `01008BB00013C000` exist. (and that they aren't empty, or at least contain a `exefs.nsp` file)
### How do I remove it?
1 - Delete the following folders: `010000000000100B`, `0100000000001001` and `01008BB00013C000`.
2 - Delete **only the `exefs.nsp` file** from `0100000000001000` directory (if there is a `romfs` folder present it could be a normal HOME menu theme)
## Compiling ## Compiling
You will need devkitPro, devkitA64, libnx and all SDL2 libraries for switch development. You will need devkitPro, devkitA64, libnx and all SDL2 libraries for switch development.
@ -226,14 +100,6 @@ Clone (**recursively!**) this repo (uses libstratosphere and Plutonium submodule
Using `make dev` instead of regular `make` will compile uLaunch in debug mode, which makes the backend process display a debug console and a special menu before usual boot to perform special tasks. This is only meant to be used by developers. Using `make dev` instead of regular `make` will compile uLaunch in debug mode, which makes the backend process display a debug console and a special menu before usual boot to perform special tasks. This is only meant to be used by developers.
## Errors
If you get a crash using uLaunch, please check:
- If the crash's title is `0100000000001000`, `010000000000100B`, `0100000000001001` or `01008BB00013C000` and **you know for sure you have this project in your SD**, then it is very likely related to this project.
- Note that `0100000000001001` and `01008BB00013C000` are the IDs of the processes used by uLaunch to target homebrew, so if you get a crash from these using uLaunch it probably means a crash from the homebrew you were using.
## Credits ## Credits
- Several scene developers for help with small issues or features. - Several scene developers for help with small issues or features.

View file

@ -6,6 +6,4 @@
- Get whether a title/homebrew is opened - Get whether a title/homebrew is opened
- RE controller applet, allow managing controllers same way normal qlaunch does - (check suggestions and bugs in issues for user-submitted TODOs)
- Get album working (for some reason album applet won't work...?)

178
Themes.md
View file

@ -1,178 +0,0 @@
# Theme system
> Current theme format version: 0 (will be first release's one, unreleased yet)
Themes consist on plain directories replacing RomFs content.
- Themes are placed in `sd:/ulaunch/themes` directory.
- A valid theme must, at least, contain a `/theme` subdirectory and a `/theme/Manifest.json` within it (icon or actual modifications aren't neccessary)
Thus, a theme named `DemoTheme` should have its manifest JSON in `sd:/ulaunch/themes/DemoTheme/theme/Manifest.json`. Other assets, like UI, would go inside `sd:/ulaunch/themes/DemoTheme/ui`, for instance.
**Important note:** any content (sound or UI) not found in the theme will be loaded from the default config, thus there is no need to provide every file (for instance, when making UI-only or sound-only changes)
## Manifest
A theme's manifest is stored inside `/theme` directory. It is required for the theme to be recognized as a valid one.
- `theme/Manifest.json` -> JSON containing theme manifest
Sample manifest JSON:
```json
{
"name": "My awesome theme",
"format_version": 0,
"release": "0.1",
"description": "This is a really cool theme, check it out!",
"author": "XorTroll"
}
```
Note that the name in the manifest doesn't have to match the theme's folder's one.
Properties:
- **name**: Theme name
- **format_version**: Theme format version (new updates might introduce changes to themes, thus a new format version would be out).
- **release**: Theme version string
- **description**: Theme description
- **author**: Theme author name(s)
- `theme/Icon.png` -> PNG theme icon (suggested size: 100x100, but it will be resized when shown in the theme menu)
## Sound
Sound consists on custom *background music* and *sound effects* via files inside `/sound`.
- `sound/BGM.mp3` -> MP3 file to replace with custom music
- `sound/BGM.json` -> JSON file with BGM settings
Sample sound JSON (with default values):
```json
{
"loop": true,
"fade_in_ms": 1500,
"fade_out_ms": 500
}
```
Properties:
- **loop**: Whether to replay the MP3 file again, after it finishes.
- **fade_in_ms**: Time in milliseconds for the fade-in to be applied when the BGM starts playing. (0 = no fade-in)
- **fade_out_ms**: Time in milliseconds for the fade-out to be applied when the BGM stops playing. (0 = no fade-out)
Note: returning to/launching a title/applet and returning back to HOME menu will restart the music.
Sound effects consist on short WAV files.
- `sound/TitleLaunch.wav` -> Sfx played when a title/homebrew is launched.
- `sound/MenuToggle.wav` -> Sfx played when the menu is toggled (from main menu to homebrew mode and the opposite)
## UI
Can be customized via files in `/ui`.
- `ui/UI.json` -> JSON file with UI settings
This JSON also allows setting custom X and Y positions for several UI elements (and if they are visible or not)
Sample UI JSON (with default values and some sample elements):
```json
{
"suspended_final_alpha": 80,
"text_color": "#e1e1e1ff",
"menu_focus_color": "#5ebcffff",
"menu_bg_color": "#0094ffff",
"menu_folder_text_x": 30,
"menu_folder_text_y": 200,
"menu_folder_text_size": 25,
"main_menu": {
"connection_icon": {
"x": 10,
"y": 10
},
"settings_icon": {
"visible": false
}
}
}
```
Note: colors are in "#RRGGBBAA" hex-RGBA format.
Properties:
- **suspended_final_alpha**: Final alpha the suspended title's capture will have (0 would be to fully hide it, default is 80)
- **text_color**: Color all UI's texts will use.
- **menu_focus_color**: Color menu's items will have when they're on focus.
- **menu_bg_color**: All menus' bg color.
- **menu_folder_text_x**, **menu_folder_text_y**, **menu_folder_text_size**: X, Y and text size for the small text shown over folder icons in the main menu.
Element customization: consists on menus and items within them, with customizable `visible`, `x` and `y` properties. If `visible` is false, custom X and Y will be ignored.
- Main menu (`main_menu`): items are `top_menu_bg`, `logo_icon`, `connection_icon`, `user_icon`, `web_icon`, `time_text`, `battery_text`, `battery_icon`, `settings_icon`, `themes_icon`, `firmware_text`, `menu_toggle_button`, `banner_image`, `banner_name_text`, `banner_author_text`, `banner_version_text` and `items_menu`.
- Startup/user selection menu (`startup_menu`): `users_menu_item`, `info_text`.
- Theme menu (`themes_menu`): `banner_image`, `themes_menu_item`, `current_theme_text`, `current_theme_name_text`, `current_theme_author_text`, `current_theme_version_text` and `current_theme_icon`.
- Settings menu (`settings_menu`): `settings_menu_item`, `info_text`.
- Language settings menu (`languages_menu`): `languages_menu_item`, `info_text`.
Notes: `logo_icon` and `items_menu` items can't be hidden ("visible" property is ignored) and in `items_menu`'s case only Y can be customized ("x" is also ignored)
## General
- `ui/Font.ttf` -> TTF font used for all the UI.
## Main menu
- `ui/Folder.png` -> 256x256 PNG for folders in the main menu.
- `ui/Cursor.png` -> 296x296 PNG for the cursor pointing at items in the main menu.
- `ui/Suspended.png` -> 296x296 PNG for the image shown above the suspended title in the main menu.
- `ui/Multiselect.png` -> 296x296 PNG for selected items in multi-select mode in the main menu.
- `ui/Background.png` -> 1280x720 PNG for the main menu's background.
- `ui/Hbmenu.png` -> 256x256 PNG for the option (in homebrew menu) to directly launch normal hbmenu NRO.
- `ui/Banner{type}.png` -> Different 1280x135 PNG banners for menu item types (BannerFolder, BannerHomebrew or BannerInstalled)
## Settings menu
- `ui/SettingEditable.png` -> 100x100 PNG for settings which can be edited.
- `ui/SettingNoEditable.png` -> 100x100 PNG for settings which cannot be edited (thus can only be seen).
### Top menu
The top menu is the small bar shown at the top of the main menu, whose background and icons are customizable.
All the menu icons ({...}Icon.png) are 50x50 icons, except battery ones (BatteryNormalIcon and BatteryChargeIcon are 30x30)
- `ui/TopMenu.png` -> Background of menu (1220x85 PNG)
- Icons in top menu (50x50 PNGs): ConnectionIcon, NoConnectionIcon, SettingsIcon, ThemesIcon, WebIcon

View file

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2007 James Newton-King
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

View file

@ -0,0 +1,93 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
namespace uViewer.Plugins
{
public abstract class PluginContext
{
public abstract string GetPluginName();
public abstract string GetPluginDescription();
public abstract string CanLoadPlugin(string sd_path);
public abstract string GetNroPathNotSetError();
public abstract string GetEntryMenuInformationLabel();
// Name
public abstract string GetEntryMenuNameFieldLabel();
public virtual Control GetEntryMenuNameControl()
{
return null;
}
public virtual string GetEntryMenuNameControlSelectedValue(Control control)
{
return control.Text;
}
// Author
public abstract string GetEntryMenuAuthorFieldLabel();
public virtual Control GetEntryMenuAuthorControl()
{
return null;
}
public virtual string GetEntryMenuAuthorControlSelectedValue(Control control)
{
return control.Text;
}
// Version
public abstract string GetEntryMenuVersionFieldLabel();
public virtual Control GetEntryMenuVersionControl()
{
return null;
}
public virtual string GetEntryMenuVersionControlSelectedValue(Control control)
{
return control.Text;
}
// Nro
public abstract string GetEntryMenuNroFieldLabel();
public virtual Control GetEntryMenuNroControl()
{
return null;
}
public virtual string GetEntryMenuNroControlSelectedValue(Control control)
{
return control.Text;
}
// Argv
public abstract string GetEntryMenuArgvFieldLabel();
public virtual Control GetEntryMenuArgvControl()
{
return null;
}
public virtual string GetEntryMenuArgvControlSelectedValue(Control control)
{
return control.Text;
}
}
}

View file

@ -0,0 +1,39 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// La información general de un ensamblado se controla mediante el siguiente
// conjunto de atributos. Cambie estos valores de atributo para modificar la información
// asociada con un ensamblado.
[assembly: AssemblyTitle("uViewer.Plugins")]
[assembly: AssemblyDescription("uViewer's plugin library")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Made by XorTroll")]
[assembly: AssemblyProduct("uLaunch project")]
[assembly: AssemblyCopyright("Copyright © uLaunch 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Si establece ComVisible en false, los tipos de este ensamblado no estarán visibles
// para los componentes COM. Si es necesario obtener acceso a un tipo en este ensamblado desde
// COM, establezca el atributo ComVisible en true en este tipo.
[assembly: ComVisible(true)]
// El siguiente GUID sirve como id. de typelib si este proyecto se expone a COM.
[assembly: Guid("d214a10a-d24a-4c23-ace1-19c2ab235400")]
// La información de versión de un ensamblado consta de los cuatro valores siguientes:
//
// Versión principal
// Versión secundaria
// Número de compilación
// Revisión
//
// Puede especificar todos los valores o usar los valores predeterminados de número de compilación y de revisión
// utilizando el carácter "*", como se muestra a continuación:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.2.0.0")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: NeutralResourcesLanguage("en")]

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{D214A10A-D24A-4C23-ACE1-19C2AB235400}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>uViewer.Plugins</RootNamespace>
<AssemblyName>uViewer.Plugins</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Plugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

View file

@ -0,0 +1,39 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// La información general de un ensamblado se controla mediante el siguiente
// conjunto de atributos. Cambie estos valores de atributo para modificar la información
// asociada con un ensamblado.
[assembly: AssemblyTitle("uViewer.RetroPlugin")]
[assembly: AssemblyDescription("uViewer's RetroArch entry creator plugin")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Made by XorTroll")]
[assembly: AssemblyProduct("uLaunch project")]
[assembly: AssemblyCopyright("Copyright © uLaunch 2019")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Si establece ComVisible en false, los tipos de este ensamblado no estarán visibles
// para los componentes COM. Si es necesario obtener acceso a un tipo en este ensamblado desde
// COM, establezca el atributo ComVisible en true en este tipo.
[assembly: ComVisible(true)]
// El siguiente GUID sirve como id. de typelib si este proyecto se expone a COM.
[assembly: Guid("7f456aef-a4eb-4af4-8bd7-90d60a1113d4")]
// La información de versión de un ensamblado consta de los cuatro valores siguientes:
//
// Versión principal
// Versión secundaria
// Número de compilación
// Revisión
//
// Puede especificar todos los valores o usar los valores predeterminados de número de compilación y de revisión
// utilizando el carácter "*", como se muestra a continuación:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.2.0.0")]
[assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: NeutralResourcesLanguage("en")]

View file

@ -0,0 +1,116 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Windows.Forms;
namespace uViewer.RetroPlugin
{
public class RetroPlugin : Plugins.PluginContext
{
public override string GetPluginName() => "RetroArch plugin";
public override string GetPluginDescription() => "RetroArch forwarder creator plugin";
private string sd_root = null;
private string selected_rom = null;
public override string CanLoadPlugin(string sd_path)
{
sd_root = sd_path;
if(!Directory.Exists(Path.Combine(sd_path, "retroarch"))) return "RetroArch doesn't seem to be installed in this SD card.";
if(!File.Exists(Path.Combine(sd_path, "retroarch", "retroarch.cfg"))) return "RetroArch doesn't seem to be installed in this SD card.";
if(!Directory.Exists(Path.Combine(sd_path, "retroarch", "cores"))) return "No available cores were found.";
var cores = Directory.GetFiles(Path.Combine(sd_path, "retroarch", "cores")).Where((file) => file.EndsWith("_libretro_libnx.nro"));
if(!cores.Any()) return "No available cores were found.";
return null;
}
public override string GetNroPathNotSetError() => "A RetroArch core needs to be specified.";
public override string GetEntryMenuInformationLabel() => "Create forwarders for ROMs in the SD card";
// Name
public override string GetEntryMenuNameFieldLabel() => "ROM name";
// Author
public override string GetEntryMenuAuthorFieldLabel() => "Producer";
// Version
public override string GetEntryMenuVersionFieldLabel() => "Platform";
// Nro
public override string GetEntryMenuNroFieldLabel() => "RetroArch core (emulator)";
public override Control GetEntryMenuNroControl()
{
ComboBox box = new ComboBox
{
Width = 300
};
var cores = Directory.GetFiles(Path.Combine(sd_root, "retroarch", "cores")).Where((file) => file.EndsWith("_libretro_libnx.nro"));
foreach(var core in cores)
{
box.Items.Add(Path.GetFileNameWithoutExtension(core));
}
return box;
}
public override string GetEntryMenuNroControlSelectedValue(Control control)
{
if(((ComboBox)control).SelectedIndex < 0) return null;
return Path.Combine(sd_root, "retroarch", "cores", ((ComboBox)control).SelectedItem as string + ".nro").Replace(sd_root, "sdmc:/").Replace('\\', '/');
}
// Argv
public override string GetEntryMenuArgvFieldLabel() => "ROM path";
public override Control GetEntryMenuArgvControl()
{
Button b = new Button()
{
Text = "Browse ROM file"
};
b.Width = 250;
b.Click += new EventHandler((sender, e) =>
{
OpenFileDialog ofd = new OpenFileDialog
{
Title = "Select ROM file",
InitialDirectory = sd_root,
Multiselect = false
};
if(ofd.ShowDialog() == DialogResult.OK)
{
var tmpfile = ofd.FileName;
if(!tmpfile.StartsWith(sd_root)) MessageBox.Show("Please, select a ROM file from the SD card.");
else
{
selected_rom = tmpfile.Replace(sd_root, "sdmc:/").Replace('\\', '/');
MessageBox.Show("Selected ROM: \"" + selected_rom + "\"");
}
}
});
return b;
}
public override string GetEntryMenuArgvControlSelectedValue(Control control)
{
if(string.IsNullOrEmpty(selected_rom)) return null;
return "\"" + selected_rom.Replace(sd_root, "sdmc:/").Replace('\\', '/') + "\"";
}
}
}

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{7F456AEF-A4EB-4AF4-8BD7-90D60A1113D4}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>uViewer.RetroPlugin</RootNamespace>
<AssemblyName>uViewer.RetroPlugin</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="RetroPlugin.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\uViewer.Plugins\uViewer.Plugins.csproj">
<Project>{d214a10a-d24a-4c23-ace1-19c2ab235400}</Project>
<Name>uViewer.Plugins</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>

37
uViewer/uViewer.sln Normal file
View file

@ -0,0 +1,37 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.28803.156
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "uViewer", "uViewer\uViewer.csproj", "{667868E3-08A1-49C4-8898-FBFACD70D3A8}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "uViewer.Plugins", "uViewer.Plugins\uViewer.Plugins.csproj", "{D214A10A-D24A-4C23-ACE1-19C2AB235400}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "uViewer.RetroPlugin", "uViewer.RetroPlugin\uViewer.RetroPlugin.csproj", "{7F456AEF-A4EB-4AF4-8BD7-90D60A1113D4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{667868E3-08A1-49C4-8898-FBFACD70D3A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{667868E3-08A1-49C4-8898-FBFACD70D3A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{667868E3-08A1-49C4-8898-FBFACD70D3A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{667868E3-08A1-49C4-8898-FBFACD70D3A8}.Release|Any CPU.Build.0 = Release|Any CPU
{D214A10A-D24A-4C23-ACE1-19C2AB235400}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D214A10A-D24A-4C23-ACE1-19C2AB235400}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D214A10A-D24A-4C23-ACE1-19C2AB235400}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D214A10A-D24A-4C23-ACE1-19C2AB235400}.Release|Any CPU.Build.0 = Release|Any CPU
{7F456AEF-A4EB-4AF4-8BD7-90D60A1113D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7F456AEF-A4EB-4AF4-8BD7-90D60A1113D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7F456AEF-A4EB-4AF4-8BD7-90D60A1113D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7F456AEF-A4EB-4AF4-8BD7-90D60A1113D4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3DC56532-83F0-4FA0-A971-E32BD10BD01E}
EndGlobalSection
EndGlobal

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8"?>
<configuration> <configuration>
<startup> <startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.6.1" /> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
</startup> </startup>
</configuration> </configuration>

227
uViewer/uViewer/EntryForm.Designer.cs generated Normal file
View file

@ -0,0 +1,227 @@
namespace uViewer
{
partial class EntryForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(EntryForm));
this.InformationLabel = new System.Windows.Forms.Label();
this.MainGroup = new System.Windows.Forms.GroupBox();
this.ArgvText = new System.Windows.Forms.TextBox();
this.ArgvLabel = new System.Windows.Forms.Label();
this.NroText = new System.Windows.Forms.TextBox();
this.NroLabel = new System.Windows.Forms.Label();
this.VersionText = new System.Windows.Forms.TextBox();
this.VersionLabel = new System.Windows.Forms.Label();
this.AuthorLabel = new System.Windows.Forms.Label();
this.AuthorText = new System.Windows.Forms.TextBox();
this.NameText = new System.Windows.Forms.TextBox();
this.NameLabel = new System.Windows.Forms.Label();
this.IconGroup = new System.Windows.Forms.GroupBox();
this.pictureBox1 = new System.Windows.Forms.PictureBox();
this.MakeButton = new System.Windows.Forms.Button();
this.MainGroup.SuspendLayout();
this.IconGroup.SuspendLayout();
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
this.SuspendLayout();
//
// InformationLabel
//
this.InformationLabel.AutoSize = true;
this.InformationLabel.Location = new System.Drawing.Point(12, 26);
this.InformationLabel.Name = "InformationLabel";
this.InformationLabel.Size = new System.Drawing.Size(373, 13);
this.InformationLabel.TabIndex = 0;
this.InformationLabel.Text = "Create custom menu entries here, to be later used from uLaunch\'s main menu.";
//
// MainGroup
//
this.MainGroup.Controls.Add(this.ArgvText);
this.MainGroup.Controls.Add(this.ArgvLabel);
this.MainGroup.Controls.Add(this.NroText);
this.MainGroup.Controls.Add(this.NroLabel);
this.MainGroup.Controls.Add(this.VersionText);
this.MainGroup.Controls.Add(this.VersionLabel);
this.MainGroup.Controls.Add(this.AuthorLabel);
this.MainGroup.Controls.Add(this.AuthorText);
this.MainGroup.Controls.Add(this.NameText);
this.MainGroup.Controls.Add(this.NameLabel);
this.MainGroup.Location = new System.Drawing.Point(15, 56);
this.MainGroup.Name = "MainGroup";
this.MainGroup.Size = new System.Drawing.Size(341, 321);
this.MainGroup.TabIndex = 1;
this.MainGroup.TabStop = false;
this.MainGroup.Text = "Entry settings";
//
// ArgvText
//
this.ArgvText.Location = new System.Drawing.Point(6, 273);
this.ArgvText.Name = "ArgvText";
this.ArgvText.Size = new System.Drawing.Size(329, 20);
this.ArgvText.TabIndex = 9;
//
// ArgvLabel
//
this.ArgvLabel.AutoSize = true;
this.ArgvLabel.Location = new System.Drawing.Point(6, 257);
this.ArgvLabel.Name = "ArgvLabel";
this.ArgvLabel.Size = new System.Drawing.Size(55, 13);
this.ArgvLabel.TabIndex = 8;
this.ArgvLabel.Text = "NRO argv";
//
// NroText
//
this.NroText.Location = new System.Drawing.Point(6, 223);
this.NroText.Name = "NroText";
this.NroText.Size = new System.Drawing.Size(329, 20);
this.NroText.TabIndex = 7;
//
// NroLabel
//
this.NroLabel.AutoSize = true;
this.NroLabel.Location = new System.Drawing.Point(6, 207);
this.NroLabel.Name = "NroLabel";
this.NroLabel.Size = new System.Drawing.Size(55, 13);
this.NroLabel.TabIndex = 6;
this.NroLabel.Text = "NRO path";
//
// VersionText
//
this.VersionText.Location = new System.Drawing.Point(6, 142);
this.VersionText.Name = "VersionText";
this.VersionText.Size = new System.Drawing.Size(329, 20);
this.VersionText.TabIndex = 5;
//
// VersionLabel
//
this.VersionLabel.AutoSize = true;
this.VersionLabel.Location = new System.Drawing.Point(6, 126);
this.VersionLabel.Name = "VersionLabel";
this.VersionLabel.Size = new System.Drawing.Size(68, 13);
this.VersionLabel.TabIndex = 4;
this.VersionLabel.Text = "Entry version";
//
// AuthorLabel
//
this.AuthorLabel.AutoSize = true;
this.AuthorLabel.Location = new System.Drawing.Point(6, 78);
this.AuthorLabel.Name = "AuthorLabel";
this.AuthorLabel.Size = new System.Drawing.Size(64, 13);
this.AuthorLabel.TabIndex = 3;
this.AuthorLabel.Text = "Entry author";
//
// AuthorText
//
this.AuthorText.Location = new System.Drawing.Point(6, 94);
this.AuthorText.Name = "AuthorText";
this.AuthorText.Size = new System.Drawing.Size(329, 20);
this.AuthorText.TabIndex = 2;
//
// NameText
//
this.NameText.Location = new System.Drawing.Point(6, 46);
this.NameText.Name = "NameText";
this.NameText.Size = new System.Drawing.Size(329, 20);
this.NameText.TabIndex = 1;
//
// NameLabel
//
this.NameLabel.AutoSize = true;
this.NameLabel.Location = new System.Drawing.Point(6, 30);
this.NameLabel.Name = "NameLabel";
this.NameLabel.Size = new System.Drawing.Size(60, 13);
this.NameLabel.TabIndex = 0;
this.NameLabel.Text = "Entry name";
//
// IconGroup
//
this.IconGroup.Controls.Add(this.pictureBox1);
this.IconGroup.Location = new System.Drawing.Point(368, 56);
this.IconGroup.Name = "IconGroup";
this.IconGroup.Size = new System.Drawing.Size(269, 293);
this.IconGroup.TabIndex = 2;
this.IconGroup.TabStop = false;
this.IconGroup.Text = "Entry icon";
//
// pictureBox1
//
this.pictureBox1.Location = new System.Drawing.Point(6, 19);
this.pictureBox1.Name = "pictureBox1";
this.pictureBox1.Size = new System.Drawing.Size(256, 256);
this.pictureBox1.TabIndex = 0;
this.pictureBox1.TabStop = false;
//
// MakeButton
//
this.MakeButton.Location = new System.Drawing.Point(368, 364);
this.MakeButton.Name = "MakeButton";
this.MakeButton.Size = new System.Drawing.Size(269, 39);
this.MakeButton.TabIndex = 3;
this.MakeButton.Text = "Create";
this.MakeButton.UseVisualStyleBackColor = true;
this.MakeButton.Click += new System.EventHandler(this.MakeButton_Click);
//
// EntryForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(653, 422);
this.Controls.Add(this.MakeButton);
this.Controls.Add(this.IconGroup);
this.Controls.Add(this.MainGroup);
this.Controls.Add(this.InformationLabel);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "EntryForm";
this.Text = "uViewer - uLaunch menu entry creator";
this.MainGroup.ResumeLayout(false);
this.MainGroup.PerformLayout();
this.IconGroup.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Label InformationLabel;
private System.Windows.Forms.GroupBox MainGroup;
private System.Windows.Forms.TextBox VersionText;
private System.Windows.Forms.Label VersionLabel;
private System.Windows.Forms.Label AuthorLabel;
private System.Windows.Forms.TextBox AuthorText;
private System.Windows.Forms.TextBox NameText;
private System.Windows.Forms.Label NameLabel;
private System.Windows.Forms.GroupBox IconGroup;
private System.Windows.Forms.TextBox ArgvText;
private System.Windows.Forms.Label ArgvLabel;
private System.Windows.Forms.TextBox NroText;
private System.Windows.Forms.Label NroLabel;
private System.Windows.Forms.PictureBox pictureBox1;
private System.Windows.Forms.Button MakeButton;
}
}

View file

@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.Windows.Forms;
using Newtonsoft.Json.Linq;
namespace uViewer
{
public partial class EntryForm : Form
{
public Plugins.PluginContext Plugin;
public EntryForm(Plugins.PluginContext plugin)
{
InitializeComponent();
Plugin = plugin;
if(Plugin != null)
{
Text += " (" + Plugin.GetPluginName() + ")";
void SetField(string value, Control control)
{
if(!string.IsNullOrEmpty(value)) control.Text = value;
}
void SetControl(Control custom, string parent_control, string actual_name)
{
if (custom != null)
{
Control control;
if (!string.IsNullOrEmpty(parent_control))
{
var parent = Controls.Find(parent_control, false).First();
control = parent.Controls.Find(actual_name, false).First();
}
else control = Controls.Find(actual_name, false).First();
custom.Margin = control.Margin;
custom.Location = control.Location;
custom.Name = actual_name;
if (!string.IsNullOrEmpty(parent_control))
{
var parent = Controls.Find(parent_control, true).First();
parent.Controls.RemoveByKey(actual_name);
parent.Controls.Add(custom);
}
else
{
Controls.RemoveByKey(actual_name);
Controls.Add(custom);
}
}
}
SetField(Plugin.GetEntryMenuInformationLabel(), InformationLabel);
SetField(Plugin.GetEntryMenuNameFieldLabel(), NameLabel);
SetControl(Plugin.GetEntryMenuNameControl(), "MainGroup", "NameText");
SetField(Plugin.GetEntryMenuAuthorFieldLabel(), AuthorLabel);
SetControl(Plugin.GetEntryMenuAuthorControl(), "MainGroup", "AuthorText");
SetField(Plugin.GetEntryMenuVersionFieldLabel(), VersionLabel);
SetControl(Plugin.GetEntryMenuVersionControl(), "MainGroup", "VersionText");
SetField(Plugin.GetEntryMenuNroFieldLabel(), NroLabel);
SetControl(Plugin.GetEntryMenuNroControl(), "MainGroup", "NroText");
SetField(Plugin.GetEntryMenuArgvFieldLabel(), ArgvLabel);
SetControl(Plugin.GetEntryMenuArgvControl(), "MainGroup", "ArgvText");
}
}
private void MakeButton_Click(object sender, EventArgs e)
{
string name = null;
string author = null;
string version = null;
string nro = null;
string argv = null;
if(Plugin != null)
{
string GetOutValue(Func<Control, string> fn, string parent_control, string ctrlname)
{
Control control;
if (!string.IsNullOrEmpty(parent_control))
{
var parent = Controls.Find(parent_control, false).First();
control = parent.Controls.Find(ctrlname, false).First();
}
else control = Controls.Find(ctrlname, false).First();
if (Plugin == null) return control.Text;
else return fn(control);
}
name = GetOutValue(Plugin.GetEntryMenuNameControlSelectedValue, "MainGroup", "NameText");
author = GetOutValue(Plugin.GetEntryMenuAuthorControlSelectedValue, "MainGroup", "AuthorText");
version = GetOutValue(Plugin.GetEntryMenuVersionControlSelectedValue, "MainGroup", "VersionText");
nro = GetOutValue(Plugin.GetEntryMenuNroControlSelectedValue, "MainGroup", "NroText");
argv = GetOutValue(Plugin.GetEntryMenuArgvControlSelectedValue, "MainGroup", "ArgvText");
}
else
{
name = NameText.Text;
author = AuthorText.Text;
version = VersionText.Text;
nro = NroText.Text;
argv = ArgvText.Text;
}
dynamic dynjson = new JObject();
dynjson.madeby = "uViewer";
dynjson.type = 2;
if(string.IsNullOrEmpty(nro))
{
var def = "A NRO path needs to be specified.";
if(Plugin != null) def = Plugin.GetNroPathNotSetError();
MessageBox.Show(def);
return;
}
else dynjson.nro_path = nro;
if(!string.IsNullOrEmpty(argv)) dynjson.nro_argv = argv;
if(!string.IsNullOrEmpty(name)) dynjson.name = name;
if(!string.IsNullOrEmpty(author)) dynjson.author = author;
if(!string.IsNullOrEmpty(version)) dynjson.version = version;
string json = dynjson.ToString();
SHA256 cd = SHA256.Create();
var jsonhash = cd.ComputeHash(Encoding.UTF8.GetBytes(json));
var fname = "uViewer-" + BitConverter.ToString(jsonhash).Replace("-", "").Substring(0, 32) + ".json";
string entry = Path.Combine(Utils.SDDrivePath, "ulaunch", "entries", fname);
File.WriteAllText(entry, json);
MessageBox.Show("The entry was saved in uLaunch's entry directory as '" + fname + "'.");
}
}
}

View file

Before

Width:  |  Height:  |  Size: 64 KiB

After

Width:  |  Height:  |  Size: 64 KiB

174
uViewer/uViewer/MainForm.Designer.cs generated Normal file
View file

@ -0,0 +1,174 @@
namespace uViewer
{
partial class MainForm
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainForm));
this.EntryManagerButton = new System.Windows.Forms.Button();
this.ViewerButton = new System.Windows.Forms.Button();
this.FoundDriveText = new System.Windows.Forms.Label();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.PluginUnloadButton = new System.Windows.Forms.Button();
this.PluginButton = new System.Windows.Forms.Button();
this.PluginLabel = new System.Windows.Forms.Label();
this.label1 = new System.Windows.Forms.Label();
this.AboutButton = new System.Windows.Forms.Button();
this.groupBox1.SuspendLayout();
this.SuspendLayout();
//
// EntryManagerButton
//
this.EntryManagerButton.Enabled = false;
this.EntryManagerButton.Location = new System.Drawing.Point(360, 42);
this.EntryManagerButton.Name = "EntryManagerButton";
this.EntryManagerButton.Size = new System.Drawing.Size(345, 27);
this.EntryManagerButton.TabIndex = 0;
this.EntryManagerButton.Text = "Load entry creator";
this.EntryManagerButton.TextImageRelation = System.Windows.Forms.TextImageRelation.TextAboveImage;
this.EntryManagerButton.UseCompatibleTextRendering = true;
this.EntryManagerButton.UseVisualStyleBackColor = true;
this.EntryManagerButton.Click += new System.EventHandler(this.EntryManagerButton_Click);
//
// ViewerButton
//
this.ViewerButton.Enabled = false;
this.ViewerButton.Location = new System.Drawing.Point(12, 143);
this.ViewerButton.Name = "ViewerButton";
this.ViewerButton.Size = new System.Drawing.Size(711, 68);
this.ViewerButton.TabIndex = 1;
this.ViewerButton.Text = "Screen viewer (needs USB, Windows-only)";
this.ViewerButton.UseVisualStyleBackColor = true;
this.ViewerButton.Click += new System.EventHandler(this.ViewerButton_Click);
//
// FoundDriveText
//
this.FoundDriveText.AutoSize = true;
this.FoundDriveText.Dock = System.Windows.Forms.DockStyle.Right;
this.FoundDriveText.Location = new System.Drawing.Point(446, 16);
this.FoundDriveText.Name = "FoundDriveText";
this.FoundDriveText.Size = new System.Drawing.Size(262, 13);
this.FoundDriveText.TabIndex = 2;
this.FoundDriveText.Text = "No SD card drive (containing uLaunch) was detected.";
//
// groupBox1
//
this.groupBox1.Controls.Add(this.PluginUnloadButton);
this.groupBox1.Controls.Add(this.PluginButton);
this.groupBox1.Controls.Add(this.PluginLabel);
this.groupBox1.Controls.Add(this.EntryManagerButton);
this.groupBox1.Controls.Add(this.FoundDriveText);
this.groupBox1.Location = new System.Drawing.Point(12, 47);
this.groupBox1.Name = "groupBox1";
this.groupBox1.Size = new System.Drawing.Size(711, 81);
this.groupBox1.TabIndex = 5;
this.groupBox1.TabStop = false;
this.groupBox1.Text = "Main menu entry creator";
//
// PluginUnloadButton
//
this.PluginUnloadButton.Enabled = false;
this.PluginUnloadButton.Location = new System.Drawing.Point(138, 46);
this.PluginUnloadButton.Name = "PluginUnloadButton";
this.PluginUnloadButton.Size = new System.Drawing.Size(126, 23);
this.PluginUnloadButton.TabIndex = 7;
this.PluginUnloadButton.Text = "Unload plugin";
this.PluginUnloadButton.UseVisualStyleBackColor = true;
this.PluginUnloadButton.Click += new System.EventHandler(this.PluginUnloadButton_Click);
//
// PluginButton
//
this.PluginButton.Enabled = false;
this.PluginButton.Location = new System.Drawing.Point(10, 46);
this.PluginButton.Name = "PluginButton";
this.PluginButton.Size = new System.Drawing.Size(122, 23);
this.PluginButton.TabIndex = 6;
this.PluginButton.Text = "Load plugin";
this.PluginButton.UseVisualStyleBackColor = true;
this.PluginButton.Click += new System.EventHandler(this.PluginButton_Click);
//
// PluginLabel
//
this.PluginLabel.AutoSize = true;
this.PluginLabel.Location = new System.Drawing.Point(7, 30);
this.PluginLabel.Name = "PluginLabel";
this.PluginLabel.Size = new System.Drawing.Size(199, 13);
this.PluginLabel.TabIndex = 5;
this.PluginLabel.Text = "There is no entry creator plugin selected.";
//
// label1
//
this.label1.AutoSize = true;
this.label1.Font = new System.Drawing.Font("Microsoft Sans Serif", 10F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
this.label1.Location = new System.Drawing.Point(8, 18);
this.label1.Name = "label1";
this.label1.Size = new System.Drawing.Size(411, 17);
this.label1.TabIndex = 6;
this.label1.Text = "Welcome to uViewer! Are you having a good time with uLaunch?";
//
// AboutButton
//
this.AboutButton.Location = new System.Drawing.Point(12, 217);
this.AboutButton.Name = "AboutButton";
this.AboutButton.Size = new System.Drawing.Size(711, 37);
this.AboutButton.TabIndex = 7;
this.AboutButton.Text = "About uLaunch project";
this.AboutButton.UseVisualStyleBackColor = true;
this.AboutButton.Click += new System.EventHandler(this.AboutButton_Click);
//
// MainForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(737, 270);
this.Controls.Add(this.AboutButton);
this.Controls.Add(this.label1);
this.Controls.Add(this.groupBox1);
this.Controls.Add(this.ViewerButton);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "MainForm";
this.Text = "uViewer - uLaunch\'s PC toolbox";
this.groupBox1.ResumeLayout(false);
this.groupBox1.PerformLayout();
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
private System.Windows.Forms.Button EntryManagerButton;
private System.Windows.Forms.Button ViewerButton;
private System.Windows.Forms.Label FoundDriveText;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.Button PluginButton;
private System.Windows.Forms.Label PluginLabel;
private System.Windows.Forms.Button PluginUnloadButton;
private System.Windows.Forms.Label label1;
private System.Windows.Forms.Button AboutButton;
}
}

111
uViewer/uViewer/MainForm.cs Normal file
View file

@ -0,0 +1,111 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows.Forms;
using libusbK;
namespace uViewer
{
public partial class MainForm : Form
{
public UsbK USB = null;
public ViewerMainForm Viewer = null;
public EntryForm Entry = null;
public Plugins.PluginContext LoadedPlugin = null;
public MainForm()
{
InitializeComponent();
ViewerButton.Enabled = Utils.IsWindows();
if(Utils.IsWindows())
{
try
{
var pat = new KLST_PATTERN_MATCH { DeviceID = @"USB\VID_057E&PID_3000" };
var lst = new LstK(0, ref pat);
lst.MoveNext(out var dinfo);
USB = new UsbK(dinfo);
}
catch
{
}
if(USB == null) ViewerButton.Enabled = false;
}
var drive = Utils.DetectSDCardDrive();
if(drive != null)
{
EntryManagerButton.Enabled = true;
PluginButton.Enabled = true;
FoundDriveText.Text = "Found SD card on drive \"" + drive.VolumeLabel + "\" (" + drive.Name + ")";
}
}
private void ViewerButton_Click(object sender, EventArgs e)
{
if(Viewer != null && Viewer.IsDisposed) Viewer = null;
if(Viewer == null) Viewer = new ViewerMainForm(USB) { Owner = this };
Viewer.Show();
}
private void EntryManagerButton_Click(object sender, EventArgs e)
{
if(LoadedPlugin != null)
{
var val = LoadedPlugin.CanLoadPlugin(Utils.SDDrivePath);
if(!string.IsNullOrEmpty(val))
{
MessageBox.Show("Plugin refused to load: " + val);
return;
}
}
if(Entry != null && Entry.IsDisposed) Entry = null;
if(Entry == null) Entry = new EntryForm(LoadedPlugin) { Owner = this };
Entry.Show();
}
private void PluginButton_Click(object sender, EventArgs e)
{
OpenFileDialog ofd = new OpenFileDialog
{
Title = "Load uViewer entry creator plugin",
Filter = "Dynamic library (*.dll)|*.dll",
Multiselect = false
};
if(ofd.ShowDialog() == DialogResult.OK)
{
var plugin = PluginHandler.LoadPluginLibrary(ofd.FileName);
LoadedPlugin = plugin;
if(LoadedPlugin != null)
{
PluginLabel.Text = "Selected plugin: " + LoadedPlugin.GetPluginName();
PluginUnloadButton.Enabled = true;
}
else PluginLabel.Text = "There is no entry creator plugin selected.";
}
}
private void PluginUnloadButton_Click(object sender, EventArgs e)
{
if(LoadedPlugin != null)
{
LoadedPlugin = null;
PluginLabel.Text = "There is no entry creator plugin selected.";
PluginUnloadButton.Enabled = false;
}
}
private void AboutButton_Click(object sender, EventArgs e)
{
Process.Start("https://github.com/XorTroll/uLaunch");
}
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,40 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
namespace uViewer
{
public static class PluginHandler
{
public static Plugins.PluginContext LoadPluginLibrary(string path)
{
Plugins.PluginContext plugin = null;
try
{
var ldll = Assembly.LoadFile(path);
var types = ldll.GetExportedTypes();
if(types.Any())
{
foreach(var type in types)
{
if(typeof(Plugins.PluginContext) != type)
{
if(typeof(Plugins.PluginContext).IsAssignableFrom(type))
{
plugin = Activator.CreateInstance(type) as Plugins.PluginContext;
}
}
}
}
}
catch
{
}
return plugin;
}
}
}

View file

@ -1,10 +1,10 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Reflection;
using System.Threading.Tasks; using System.IO;
using System.Windows.Forms; using System.Windows.Forms;
namespace QForegroundViewer namespace uViewer
{ {
static class Program static class Program
{ {
@ -16,7 +16,7 @@ namespace QForegroundViewer
{ {
Application.EnableVisualStyles(); Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false); Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new ViewerMainForm()); Application.Run(new MainForm());
} }
} }
} }

View file

@ -6,8 +6,8 @@ using System.Runtime.InteropServices;
// La información general de un ensamblado se controla mediante el siguiente // La información general de un ensamblado se controla mediante el siguiente
// conjunto de atributos. Cambie estos valores de atributo para modificar la información // conjunto de atributos. Cambie estos valores de atributo para modificar la información
// asociada con un ensamblado. // asociada con un ensamblado.
[assembly: AssemblyTitle("QForegroundViewer")] [assembly: AssemblyTitle("uViewer")]
[assembly: AssemblyDescription("uLaunch's foreground display viewer")] [assembly: AssemblyDescription("uLaunch's PC toolbox")]
[assembly: AssemblyConfiguration("")] [assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Made by XorTroll")] [assembly: AssemblyCompany("Made by XorTroll")]
[assembly: AssemblyProduct("uLaunch project")] [assembly: AssemblyProduct("uLaunch project")]
@ -33,7 +33,7 @@ using System.Runtime.InteropServices;
// Puede especificar todos los valores o usar los valores predeterminados de número de compilación y de revisión // Puede especificar todos los valores o usar los valores predeterminados de número de compilación y de revisión
// utilizando el carácter "*", como se muestra a continuación: // utilizando el carácter "*", como se muestra a continuación:
// [assembly: AssemblyVersion("1.0.*")] // [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.1.0.0")] [assembly: AssemblyVersion("0.2.0.0")]
[assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyFileVersion("0.2.0.0")]
[assembly: NeutralResourcesLanguage("en")] [assembly: NeutralResourcesLanguage("en")]

View file

@ -8,7 +8,7 @@
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace QForegroundViewer.Properties { namespace uViewer.Properties {
using System; using System;
@ -39,7 +39,7 @@ namespace QForegroundViewer.Properties {
internal static global::System.Resources.ResourceManager ResourceManager { internal static global::System.Resources.ResourceManager ResourceManager {
get { get {
if (object.ReferenceEquals(resourceMan, null)) { if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("QForegroundViewer.Properties.Resources", typeof(Resources).Assembly); global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("uViewer.Properties.Resources", typeof(Resources).Assembly);
resourceMan = temp; resourceMan = temp;
} }
return resourceMan; return resourceMan;

View file

@ -8,7 +8,7 @@
// </auto-generated> // </auto-generated>
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
namespace QForegroundViewer.Properties { namespace uViewer.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]

View file

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Drawing;
using System.Windows.Forms;
using System.Text;
namespace uViewer
{
public static class SDHelper
{
public static List<string> ListNROsByPath(string sd_name, string path)
{
var fullpath = Path.Combine(sd_name, path);
var nros = Directory.GetFiles(fullpath, "*.nro", SearchOption.AllDirectories);
return nros.ToList();
}
public static (string name, string author, string version, Bitmap icon, long size) GetNROInformation(string nro_path)
{
string nroname = null;
string nroauthor = null;
string nrover = null;
Bitmap nroicon = null;
long nrosize = 0;
FileStream fs = new FileStream(nro_path, FileMode.Open);
BinaryReader br = new BinaryReader(fs);
br.ReadBytes(16);
uint magic = br.ReadUInt32();
if(magic == 0x304F524E)
{
br.ReadUInt32();
uint size = br.ReadUInt32();
br.BaseStream.Position = size;
uint asthdr = br.ReadUInt32();
if(asthdr == 0x54455341)
{
br.ReadUInt32();
ulong icon_off = br.ReadUInt64();
ulong icon_size = br.ReadUInt64();
var tmppos = br.BaseStream.Position;
br.BaseStream.Position = size + (long)icon_off;
var icon = br.ReadBytes((int)icon_size);
br.BaseStream.Position = tmppos;
nroicon = Image.FromStream(new MemoryStream(icon)) as Bitmap;
ulong nacp_off = br.ReadUInt64();
ulong nacp_size = br.ReadUInt64();
tmppos = br.BaseStream.Position;
br.BaseStream.Position = size + (long)nacp_off;
var rawnacp = br.ReadBytes((int)nacp_size);
br.BaseStream.Position = tmppos;
for(var i = 0; i < 16; i++)
{
int baseoff = i * 0x300;
var name = Encoding.UTF8.GetString(rawnacp, baseoff, 0x200);
var author = Encoding.UTF8.GetString(rawnacp, baseoff + 0x200, 0x100);
if(name.Any() && author.Any())
{
nroname = name.TrimEnd('\0');
nroauthor = author.TrimEnd('\0');
break;
}
}
var ver = Encoding.UTF8.GetString(rawnacp, 0x3060, 0x10);
nrover = ver.TrimEnd('\0');
nrosize = new FileInfo(nro_path).Length;
}
}
fs.Close();
br.Close();
return (nroname, nroauthor, nrover, nroicon, nrosize);
}
}
}

View file

@ -1,4 +1,4 @@
namespace QForegroundViewer namespace uViewer
{ {
partial class ScreenshotForm partial class ScreenshotForm
{ {
@ -28,6 +28,7 @@
/// </summary> /// </summary>
private void InitializeComponent() private void InitializeComponent()
{ {
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ScreenshotForm));
this.groupBox1 = new System.Windows.Forms.GroupBox(); this.groupBox1 = new System.Windows.Forms.GroupBox();
this.ScreenshotList = new System.Windows.Forms.ComboBox(); this.ScreenshotList = new System.Windows.Forms.ComboBox();
this.ScreenshotBox = new System.Windows.Forms.PictureBox(); this.ScreenshotBox = new System.Windows.Forms.PictureBox();
@ -139,8 +140,9 @@
this.Controls.Add(this.ScreenshotBox); this.Controls.Add(this.ScreenshotBox);
this.Controls.Add(this.groupBox1); this.Controls.Add(this.groupBox1);
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "ScreenshotForm"; this.Name = "ScreenshotForm";
this.Text = "Save screenshot"; this.Text = "uViewer - USB screen viewer - Save screenshot";
this.groupBox1.ResumeLayout(false); this.groupBox1.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.ScreenshotBox)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.ScreenshotBox)).EndInit();
this.groupBox2.ResumeLayout(false); this.groupBox2.ResumeLayout(false);

View file

@ -8,7 +8,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace QForegroundViewer namespace uViewer
{ {
public partial class ScreenshotForm : Form public partial class ScreenshotForm : Form
{ {

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
namespace QForegroundViewer namespace uViewer
{ {
partial class ToolboxForm partial class ToolboxForm
{ {
@ -29,7 +29,6 @@
private void InitializeComponent() private void InitializeComponent()
{ {
this.ScreenshotButton = new System.Windows.Forms.Button(); this.ScreenshotButton = new System.Windows.Forms.Button();
this.AboutButton = new System.Windows.Forms.Button();
this.ResizeButton = new System.Windows.Forms.Button(); this.ResizeButton = new System.Windows.Forms.Button();
this.groupBox1 = new System.Windows.Forms.GroupBox(); this.groupBox1 = new System.Windows.Forms.GroupBox();
this.IncrementNumeric = new System.Windows.Forms.NumericUpDown(); this.IncrementNumeric = new System.Windows.Forms.NumericUpDown();
@ -47,16 +46,6 @@
this.ScreenshotButton.UseVisualStyleBackColor = true; this.ScreenshotButton.UseVisualStyleBackColor = true;
this.ScreenshotButton.Click += new System.EventHandler(this.ScreenshotButton_Click); this.ScreenshotButton.Click += new System.EventHandler(this.ScreenshotButton_Click);
// //
// AboutButton
//
this.AboutButton.Location = new System.Drawing.Point(12, 393);
this.AboutButton.Name = "AboutButton";
this.AboutButton.Size = new System.Drawing.Size(222, 45);
this.AboutButton.TabIndex = 1;
this.AboutButton.Text = "About uLaunch";
this.AboutButton.UseVisualStyleBackColor = true;
this.AboutButton.Click += new System.EventHandler(this.AboutButton_Click);
//
// ResizeButton // ResizeButton
// //
this.ResizeButton.Location = new System.Drawing.Point(6, 67); this.ResizeButton.Location = new System.Drawing.Point(6, 67);
@ -95,14 +84,13 @@
// //
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(246, 450); this.ClientSize = new System.Drawing.Size(246, 190);
this.Controls.Add(this.groupBox1); this.Controls.Add(this.groupBox1);
this.Controls.Add(this.AboutButton);
this.Controls.Add(this.ScreenshotButton); this.Controls.Add(this.ScreenshotButton);
this.Cursor = System.Windows.Forms.Cursors.Default; this.Cursor = System.Windows.Forms.Cursors.Default;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow; this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedToolWindow;
this.Name = "ToolboxForm"; this.Name = "ToolboxForm";
this.Text = "Toolbox"; this.Text = "uViewer - USB screen viewer - Toolbox";
this.groupBox1.ResumeLayout(false); this.groupBox1.ResumeLayout(false);
((System.ComponentModel.ISupportInitialize)(this.IncrementNumeric)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.IncrementNumeric)).EndInit();
this.ResumeLayout(false); this.ResumeLayout(false);
@ -112,7 +100,6 @@
#endregion #endregion
private System.Windows.Forms.Button ScreenshotButton; private System.Windows.Forms.Button ScreenshotButton;
private System.Windows.Forms.Button AboutButton;
private System.Windows.Forms.Button ResizeButton; private System.Windows.Forms.Button ResizeButton;
private System.Windows.Forms.GroupBox groupBox1; private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.NumericUpDown IncrementNumeric; private System.Windows.Forms.NumericUpDown IncrementNumeric;

View file

@ -8,7 +8,7 @@ using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
namespace QForegroundViewer namespace uViewer
{ {
public partial class ToolboxForm : Form public partial class ToolboxForm : Form
{ {
@ -23,12 +23,6 @@ namespace QForegroundViewer
IncrementNumeric.Value = 1.0m; IncrementNumeric.Value = 1.0m;
} }
private void AboutButton_Click(object sender, EventArgs e)
{
var resp = MessageBox.Show("uLaunch is a custom reimplementation of Nintendo Switch's HOME menu.\n\nWould you like to visit our GitHub page?", "About uLaunch project", MessageBoxButtons.OKCancel);
if(resp == DialogResult.OK) Process.Start("https://github.com/XorTroll/uLaunch");
}
private void ScreenshotButton_Click(object sender, EventArgs e) private void ScreenshotButton_Click(object sender, EventArgs e)
{ {
new ScreenshotForm(main).ShowDialog(); new ScreenshotForm(main).ShowDialog();

46
uViewer/uViewer/Utils.cs Normal file
View file

@ -0,0 +1,46 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using System.Reflection;
namespace uViewer
{
public static class Utils
{
public static string SDDrivePath { get; set; }
public static string GetCwd() => Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
public static DriveInfo DetectSDCardDrive()
{
if (DriveInfo.GetDrives().Any())
{
foreach(DriveInfo driveInfo in DriveInfo.GetDrives())
{
if(driveInfo.IsReady)
{
// Exists hbmenu.nro?
if(File.Exists(Path.Combine(driveInfo.Name, "hbmenu.nro")))
{
// Exists /switch?
if(Directory.Exists(Path.Combine(driveInfo.Name, "switch")))
{
// Exists /ulaunch?
if(Directory.Exists(Path.Combine(driveInfo.Name, "ulaunch")))
{
SDDrivePath = driveInfo.Name;
return driveInfo;
}
}
}
}
}
}
return null;
}
public static bool IsWindows() => Environment.OSVersion.ToString().Contains("Windows");
}
}

View file

@ -1,4 +1,4 @@
namespace QForegroundViewer namespace uViewer
{ {
partial class ViewerMainForm partial class ViewerMainForm
{ {
@ -53,7 +53,7 @@
this.Controls.Add(this.CaptureBox); this.Controls.Add(this.CaptureBox);
this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon"))); this.Icon = ((System.Drawing.Icon)(resources.GetObject("$this.Icon")));
this.Name = "ViewerMainForm"; this.Name = "ViewerMainForm";
this.Text = "uLaunch - Foreground display viewer"; this.Text = "uViewer - USB screen viewer";
((System.ComponentModel.ISupportInitialize)(this.CaptureBox)).EndInit(); ((System.ComponentModel.ISupportInitialize)(this.CaptureBox)).EndInit();
this.ResumeLayout(false); this.ResumeLayout(false);

View file

@ -9,7 +9,7 @@ using System.Runtime.InteropServices;
using System.Windows.Forms; using System.Windows.Forms;
using libusbK; using libusbK;
namespace QForegroundViewer namespace uViewer
{ {
public partial class ViewerMainForm : Form public partial class ViewerMainForm : Form
{ {
@ -30,21 +30,12 @@ namespace QForegroundViewer
new byte[RawRGBAScreenBufferSize], new byte[RawRGBAScreenBufferSize],
}; };
public ViewerMainForm() public ViewerMainForm(UsbK USB)
{ {
InitializeComponent(); InitializeComponent();
InitializePictureBox(CaptureBox); InitializePictureBox(CaptureBox);
try this.USB = USB;
{
var pat = new KLST_PATTERN_MATCH { DeviceID = @"USB\VID_057E&PID_3000" };
var lst = new LstK(0, ref pat);
lst.MoveNext(out var dinfo);
USB = new UsbK(dinfo);
}
catch
{
}
Toolbox = new ToolboxForm(this); Toolbox = new ToolboxForm(this);
Toolbox.Show(); Toolbox.Show();

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Newtonsoft.Json" version="12.0.3" targetFramework="net472" />
</packages>

View file

@ -6,12 +6,13 @@
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{667868E3-08A1-49C4-8898-FBFACD70D3A8}</ProjectGuid> <ProjectGuid>{667868E3-08A1-49C4-8898-FBFACD70D3A8}</ProjectGuid>
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>
<RootNamespace>QForegroundViewer</RootNamespace> <RootNamespace>uViewer</RootNamespace>
<AssemblyName>QForegroundViewer</AssemblyName> <AssemblyName>uViewer</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion> <TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment> <FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects> <AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Deterministic>true</Deterministic> <Deterministic>true</Deterministic>
<TargetFrameworkProfile />
</PropertyGroup> </PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget> <PlatformTarget>AnyCPU</PlatformTarget>
@ -34,12 +35,15 @@
<WarningLevel>4</WarningLevel> <WarningLevel>4</WarningLevel>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<StartupObject>QForegroundViewer.Program</StartupObject> <StartupObject>uViewer.Program</StartupObject>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<ApplicationIcon>Icon.ico</ApplicationIcon> <ApplicationIcon>Icon.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
<HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
@ -53,12 +57,27 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="EntryForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="EntryForm.Designer.cs">
<DependentUpon>EntryForm.cs</DependentUpon>
</Compile>
<Compile Include="MainForm.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="MainForm.Designer.cs">
<DependentUpon>MainForm.cs</DependentUpon>
</Compile>
<Compile Include="PluginHandler.cs" />
<Compile Include="SDHelper.cs" />
<Compile Include="ToolboxForm.cs"> <Compile Include="ToolboxForm.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
<Compile Include="ToolboxForm.Designer.cs"> <Compile Include="ToolboxForm.Designer.cs">
<DependentUpon>ToolboxForm.cs</DependentUpon> <DependentUpon>ToolboxForm.cs</DependentUpon>
</Compile> </Compile>
<Compile Include="Utils.cs" />
<Compile Include="ViewerMainForm.cs"> <Compile Include="ViewerMainForm.cs">
<SubType>Form</SubType> <SubType>Form</SubType>
</Compile> </Compile>
@ -74,6 +93,12 @@
<Compile Include="ScreenshotForm.Designer.cs"> <Compile Include="ScreenshotForm.Designer.cs">
<DependentUpon>ScreenshotForm.cs</DependentUpon> <DependentUpon>ScreenshotForm.cs</DependentUpon>
</Compile> </Compile>
<EmbeddedResource Include="EntryForm.resx">
<DependentUpon>EntryForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="MainForm.resx">
<DependentUpon>MainForm.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="ToolboxForm.resx"> <EmbeddedResource Include="ToolboxForm.resx">
<DependentUpon>ToolboxForm.cs</DependentUpon> <DependentUpon>ToolboxForm.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
@ -93,6 +118,7 @@
<EmbeddedResource Include="ScreenshotForm.resx"> <EmbeddedResource Include="ScreenshotForm.resx">
<DependentUpon>ScreenshotForm.cs</DependentUpon> <DependentUpon>ScreenshotForm.cs</DependentUpon>
</EmbeddedResource> </EmbeddedResource>
<None Include="packages.config" />
<None Include="Properties\Settings.settings"> <None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator> <Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput> <LastGenOutput>Settings.Designer.cs</LastGenOutput>
@ -109,5 +135,15 @@
<ItemGroup> <ItemGroup>
<Content Include="Icon.ico" /> <Content Include="Icon.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<ProjectReference Include="..\uViewer.Plugins\uViewer.Plugins.csproj">
<Project>{d214a10a-d24a-4c23-ace1-19c2ab235400}</Project>
<Name>uViewer.Plugins</Name>
</ProjectReference>
<ProjectReference Include="..\uViewer.RetroPlugin\uViewer.RetroPlugin.csproj">
<Project>{7f456aef-a4eb-4af4-8bd7-90d60a1113d4}</Project>
<Name>uViewer.RetroPlugin</Name>
</ProjectReference>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>