Compare commits
13 Commits
v1.0.0-bet
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| caee674448 | |||
| 44da1214dd | |||
| 776a53d7be | |||
| cc543076c7 | |||
| 17043cde37 | |||
| 41d84b18b7 | |||
| b5eef93ffd | |||
| 90eb204021 | |||
| eb923a0c57 | |||
| c93e930286 | |||
| 380b9a4604 | |||
| 0e895fe1fb | |||
| 0feb6fa073 |
84
README.MD
@@ -1,13 +1,10 @@
|
||||
<h1 align="center">Silk Fly Launcher</h1>
|
||||
<h3 align="center">
|
||||
— A Silksong mod manager —
|
||||
</h3>
|
||||
|
||||
<p align="center">
|
||||
<img alt="Release" src="https://img.shields.io/github/v/release/Gabi-Zar/Silk-Fly-Launcher">
|
||||
<img alt="Stars" src="https://img.shields.io/github/stars/Gabi-Zar/Silk-Fly-Launcher?color=magenta">
|
||||
<img alt="Forks" src="https://img.shields.io/github/forks/Gabi-Zar/Silk-Fly-Launcher?color=purple">
|
||||
<img alt="License" src="https://img.shields.io/github/license/Gabi-Zar/Silk-Fly-Launcher?color=BB0000">
|
||||
<img alt="Release" src="https://img.shields.io/github/v/release/Gabi-Zar/Silk-Fly-Launcher?include_prereleases&style=flat&color=green">
|
||||
<img alt="Stars" src="https://img.shields.io/github/stars/Gabi-Zar/Silk-Fly-Launcher?style=flat&color=magenta">
|
||||
<img alt="Forks" src="https://img.shields.io/github/forks/Gabi-Zar/Silk-Fly-Launcher?style=flat&color=purple">
|
||||
<img alt="License" src="https://img.shields.io/github/license/Gabi-Zar/Silk-Fly-Launcher?style=flat&color=BB0000">
|
||||
<br>
|
||||
<a href="https://github.com/Gabi-Zar"><img title="Developer" src="https://img.shields.io/badge/developer-GabiZar-blue"></a>
|
||||
<img alt="Maintained" src="https://img.shields.io/badge/Maintained-Yes-009900">
|
||||
@@ -16,9 +13,7 @@
|
||||
|
||||
---
|
||||
|
||||
Silk Fly Launcher is an open-source mod manager for Hollow Knight: Silksong, designed to simplify installing and managing Nexus Mods.
|
||||
|
||||
Built with Electron, it provides a clean UI, secure Nexus API integration, and seamless switching between vanilla and modded gameplay.
|
||||
Silk Fly Launcher is an open-source mod manager for Hollow Knight: Silksong built with Electron, it download mods from Nexus Mods (Thunderstore comming in stable v1.0.0).
|
||||
|
||||
<p align="center">
|
||||
<img src="assets/github/about.png">
|
||||
@@ -37,74 +32,75 @@ Built with Electron, it provides a clean UI, secure Nexus API integration, and s
|
||||
</p>
|
||||
</details>
|
||||
|
||||
## 🌟 Features
|
||||
## Features
|
||||
|
||||
- Install / Uninstall / Backup BepInEx for Silksong.
|
||||
- Automatically detect Silksong installation path for Steam on Windows
|
||||
- Browse and download Nexus mods directly in the app
|
||||
- Browse and download Thunderstore and Nexus mods directly in the app
|
||||
- Activate and deactivate mods
|
||||
- Multiple themes inspired by Silksong
|
||||
- Launch Silksong in Vanilla or Modded without deleting the mods
|
||||
- Securely store your Nexus API key on your device using Electron's safeStorage, and then additionally encrypt it with an AES key (provided at build time) via Electron Store.
|
||||
- Securely store your Nexus API key on your device using Electron's safeStorage, and then encrypt it with an AES key (provided at build time) via Electron Store.
|
||||
- Works on Windows (Linux coming soon)
|
||||
|
||||
## 💻 Compatibility
|
||||
## Compatibility
|
||||
|
||||
- ✅ Windows x64
|
||||
- 🛠 Linux (Comming in v1.0.0)
|
||||
- ❌ macOS (need a tester)
|
||||
- [x] Windows x64
|
||||
- [ ] Linux (Comming in v1.0.0)
|
||||
- [ ] macOS (need a tester)
|
||||
|
||||
## 🚀 Installation & Usage
|
||||
## Installation & Usage
|
||||
|
||||
1. Download the app in your desired format from the [GitHub Releases](https://github.com/Gabi-Zar/Silk-Fly-Launcher/releases) page or build it from [source](#how-to-build) to have the latest features.
|
||||
1. Download the app in your desired format from the [GitHub Releases](https://github.com/Gabi-Zar/Silk-Fly-Launcher/releases) page or build it from source to have the latest features.
|
||||
|
||||
2. Install it and follow the in-app guide
|
||||
2. Install it and follow the in-app guide.
|
||||
|
||||
## ⚙️ How to build
|
||||
## How to build
|
||||
|
||||
1. Install [Git](https://git-scm.com/) and [Node JS](https://nodejs.org)
|
||||
2. Clone the repo `git clone https://github.com/Gabi-Zar/Silk-Fly-Launcher `
|
||||
3. Go into the repository `cd Silk-Fly-Launcher `
|
||||
4. Install npm dependencies `npm install `
|
||||
5. Start the app with `npm run start `
|
||||
6. Build the app with `npm run make:options `
|
||||
7. Available Build options are
|
||||
6. Optionnal dependencies:
|
||||
- [Wix toolset v3](https://github.com/wixtoolset/wix3/releases/tag/wix3141rtm) for msi windows build
|
||||
7. Build the app with `npm run make:options `
|
||||
8. Available Build options:
|
||||
- none - build all
|
||||
- zip - Make a zip for Windows x64
|
||||
- msi - Make a msi build for Windows x64
|
||||
|
||||
```
|
||||
none - build all
|
||||
zip - Make a zip for Windows x64
|
||||
msi - Make a msi build for Windows x64
|
||||
```
|
||||
## Todo
|
||||
|
||||
## ✅ Todo
|
||||
|
||||
#### For release 1.0.0
|
||||
#### For stable release 1.0.0
|
||||
|
||||
- [x] BepInEx support
|
||||
- [x] Nexus support
|
||||
- [ ] Disable / Enable Individual mods
|
||||
- [ ] Support for offline mods
|
||||
- [ ] Auto Download Mods Dependencies
|
||||
- [x] Disable / Enable Individual mods
|
||||
- [x] Automatic update
|
||||
- [x] Support for Thunderstore
|
||||
- [ ] Linux support
|
||||
- [ ] Automatic update
|
||||
- [x] Support for offline mods
|
||||
- [ ] Auto Download Mods Dependencies
|
||||
|
||||
#### For Later
|
||||
#### For later
|
||||
|
||||
- [ ] Automatically detect Silksong installation path on other platforms
|
||||
- [ ] Multiple mods profiles
|
||||
- [ ] Support for MelonLoader
|
||||
- [ ] Support for Thunderstore
|
||||
- [ ] French translation
|
||||
- [ ] macOS support (need a tester)
|
||||
|
||||
## 🤝 Contributing
|
||||
##### Todo list sorted by importance
|
||||
|
||||
Pull requests are welcome.
|
||||
For major changes, please open an issue first to discuss what you would like to change.
|
||||
## Contributing
|
||||
|
||||
## 📜 License and credit
|
||||
This is my first Electron project, I will love any feedback. Just open an issue or make a pull request.
|
||||
|
||||
This project is licensed under the [GPL-3.0 license](LICENSE).
|
||||
This product uses third-party modules or assets under open source [third-party](THIRD-PARTY-LICENSES) licenses
|
||||
## License and credit
|
||||
|
||||
This project is licensed under the [GPL-3.0 license](LICENSE).<br>
|
||||
This product uses third-party modules or assets under open source [third-party](THIRD-PARTY-LICENSES) licenses.
|
||||
|
||||
> [!CAUTION]
|
||||
> Some assets came from Hollow Knight: Silksong.
|
||||
@@ -113,4 +109,4 @@ This product uses third-party modules or assets under open source [third-party](
|
||||
|
||||
---
|
||||
|
||||
<p align="center">If you like this app, consider giving it a ⭐ on GitHub!</p>
|
||||
<p align="center">If you like this app, consider giving it a star on GitHub!</p>
|
||||
|
||||
@@ -8,6 +8,152 @@ Hollow Knight: Silksong is property of Team Cherry.
|
||||
This project is not affiliated with or endorsed by Team Cherry.
|
||||
|
||||
|
||||
LibreICONS
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Diemen Design
|
||||
|
||||
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.
|
||||
|
||||
Iconbuddy Simple Icons
|
||||
CC0 1.0
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
|
||||
|
||||
7zip-bin 5.2.0
|
||||
MIT
|
||||
The MIT License (MIT)
|
||||
@@ -3444,684 +3590,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
silkflylauncher 1.0.0
|
||||
GPL-3.0
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU General Public License is a free, copyleft license for
|
||||
software and other kinds of works.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
the GNU General Public License is intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users. We, the Free Software Foundation, use the
|
||||
GNU General Public License for most of our software; it applies also to
|
||||
any other work released this way by its authors. You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to prevent others from denying you
|
||||
these rights or asking you to surrender the rights. Therefore, you have
|
||||
certain responsibilities if you distribute copies of the software, or if
|
||||
you modify it: responsibilities to respect the freedom of others.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must pass on to the recipients the same
|
||||
freedoms that you received. You must make sure that they, too, receive
|
||||
or can get the source code. And you must show them these terms so they
|
||||
know their rights.
|
||||
|
||||
Developers that use the GNU GPL protect your rights with two steps:
|
||||
(1) assert copyright on the software, and (2) offer you this License
|
||||
giving you legal permission to copy, distribute and/or modify it.
|
||||
|
||||
For the developers' and authors' protection, the GPL clearly explains
|
||||
that there is no warranty for this free software. For both users' and
|
||||
authors' sake, the GPL requires that modified versions be marked as
|
||||
changed, so that their problems will not be attributed erroneously to
|
||||
authors of previous versions.
|
||||
|
||||
Some devices are designed to deny users access to install or run
|
||||
modified versions of the software inside them, although the manufacturer
|
||||
can do so. This is fundamentally incompatible with the aim of
|
||||
protecting users' freedom to change the software. The systematic
|
||||
pattern of such abuse occurs in the area of products for individuals to
|
||||
use, which is precisely where it is most unacceptable. Therefore, we
|
||||
have designed this version of the GPL to prohibit the practice for those
|
||||
products. If such problems arise substantially in other domains, we
|
||||
stand ready to extend this provision to those domains in future versions
|
||||
of the GPL, as needed to protect the freedom of users.
|
||||
|
||||
Finally, every program is threatened constantly by software patents.
|
||||
States should not allow patents to restrict development and use of
|
||||
software on general-purpose computers, but in those that do, we wish to
|
||||
avoid the special danger that patents applied to a free program could
|
||||
make it effectively proprietary. To prevent this, the GPL assures that
|
||||
patents cannot be used to render the program non-free.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Use with the GNU Affero General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU Affero General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the special requirements of the GNU Affero General Public License,
|
||||
section 13, concerning interaction through a network will apply to the
|
||||
combination as such.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program does terminal interaction, make it output a short
|
||||
notice like this when it starts in an interactive mode:
|
||||
|
||||
<program> Copyright (C) <year> <name of author>
|
||||
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, your program's commands
|
||||
might be different; for a GUI interface, you would use an "about box".
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU GPL, see
|
||||
<https://www.gnu.org/licenses/>.
|
||||
|
||||
The GNU General Public License does not permit incorporating your program
|
||||
into proprietary programs. If your program is a subroutine library, you
|
||||
may consider it more useful to permit linking proprietary applications with
|
||||
the library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License. But first, please read
|
||||
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||
|
||||
|
||||
sshpk 1.18.0
|
||||
MIT
|
||||
Copyright Joyent, Inc. All rights reserved.
|
||||
@@ -4535,6 +4003,32 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
vdf-parser 1.2.1
|
||||
MIT
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2014-2016 Rossen Georgiev (https://github.com/rossengeorgiev)
|
||||
Copyright (c) 2019-2023 p0358 (https://github.com/p0358)
|
||||
|
||||
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.
|
||||
|
||||
|
||||
verror 1.10.0
|
||||
MIT
|
||||
Copyright (c) 2016, Joyent, Inc. All rights reserved.
|
||||
|
||||
BIN
assets/icon.png
Normal file
|
After Width: | Height: | Size: 21 KiB |
@@ -2,6 +2,7 @@ const { FusesPlugin } = require("@electron-forge/plugin-fuses");
|
||||
const { FuseV1Options, FuseVersion } = require("@electron/fuses");
|
||||
const fs = require("fs/promises");
|
||||
const path = require("path");
|
||||
const packageJson = require("./package.json");
|
||||
|
||||
const buildTarget = process.env.BUILD_TARGET || "all";
|
||||
|
||||
@@ -12,6 +13,7 @@ if (buildTarget == "msi" || buildTarget == "all") {
|
||||
config: {
|
||||
icon: "./assets/icon.ico",
|
||||
ui: { enabled: true, chooseDirectory: true },
|
||||
upgradeCode: "e3bb759f-c2b2-4545-bb7b-35bed5be4cd5",
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -19,7 +21,35 @@ if (buildTarget == "msi" || buildTarget == "all") {
|
||||
if (buildTarget == "zip" || buildTarget == "all") {
|
||||
makers.push({
|
||||
name: "@electron-forge/maker-zip",
|
||||
platforms: ["win32"],
|
||||
platforms: ["win32", "linux"],
|
||||
});
|
||||
}
|
||||
|
||||
if (buildTarget == "deb" || buildTarget == "all") {
|
||||
makers.push({
|
||||
name: "@electron-forge/maker-deb",
|
||||
config: {
|
||||
options: {
|
||||
bin: packageJson.productName,
|
||||
name: packageJson.productName,
|
||||
icon: "./assets/icon.png",
|
||||
maintainer: "GabiZar",
|
||||
homepage: "https://github.com/Gabi-Zar/Silk-Fly-Launcher",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (buildTarget == "appimage" || buildTarget == "all") {
|
||||
makers.push({
|
||||
name: "@reforged/maker-appimage",
|
||||
config: {
|
||||
options: {
|
||||
bin: packageJson.productName,
|
||||
name: packageJson.productName,
|
||||
icon: "./assets/icon.png",
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -75,6 +105,9 @@ module.exports = {
|
||||
postMake: async (forgeConfig, makeResults) => {
|
||||
if (buildTarget == "msi" || buildTarget == "all") {
|
||||
const outDir = path.join(__dirname, "out", "make", "wix", "x64");
|
||||
if (!(await fileExists(outDir))) {
|
||||
return;
|
||||
}
|
||||
const files = await fs.readdir(outDir, { recursive: true });
|
||||
const msiFile = path.join(
|
||||
outDir,
|
||||
|
||||
661
main.js
@@ -11,6 +11,10 @@ import { path7za } from "7zip-bin";
|
||||
import node7z from "node-7z";
|
||||
const { extractFull } = node7z;
|
||||
import packageJson from "./package.json" with { type: "json" };
|
||||
import semverGt from "semver/functions/gt.js";
|
||||
import { randomUUID } from "crypto";
|
||||
import { spawn } from "child_process";
|
||||
import vdf from "vdf-parser";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
@@ -26,6 +30,7 @@ const installedModsStore = new Store({ name: "installed-mods-list" });
|
||||
const NexusAPIStore = new Store({ name: "nexus-api", encryptionKey: packageJson["AES-key-nexus-api"], fileExtension: "encrypted", clearInvalidConfig: true });
|
||||
|
||||
const userSavePath = app.getPath("userData");
|
||||
const tempPath = app.getPath("temp");
|
||||
const modSavePath = path.join(userSavePath, "mods");
|
||||
const dataPath = path.join(userSavePath, "config.json");
|
||||
let sevenZipPath = path7za;
|
||||
@@ -36,9 +41,13 @@ let installedCachedModList;
|
||||
let installedTotalModsCount;
|
||||
let onlineCachedModList;
|
||||
let onlineTotalModsCount;
|
||||
let thunderstoreCachedModList;
|
||||
let thunderstoreTotalModsCount;
|
||||
let allThunderstoreCachedModList;
|
||||
let allThunderstoreCachedModListNeedRefresh = true;
|
||||
|
||||
const bepinexFiles = [".doorstop_version", "changelog.txt", "doorstop_config.ini", "winhttp.dll"];
|
||||
let bepinexVersion;
|
||||
const bepinexFiles = [".doorstop_version", "changelog.txt", "doorstop_config.ini", "winhttp.dll", "libdoorstop.so", "run_bepinex.sh"];
|
||||
let bepinexVersion = bepinexStore.get("bepinex-version");
|
||||
let bepinexBackupVersion;
|
||||
|
||||
let mainWindow;
|
||||
@@ -66,6 +75,7 @@ async function createWindow() {
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, "preload.js"),
|
||||
},
|
||||
backgroundColor: "#000",
|
||||
show: false,
|
||||
});
|
||||
|
||||
@@ -77,27 +87,31 @@ async function createWindow() {
|
||||
|
||||
mainWindow.loadFile(path.join("renderer", htmlFile));
|
||||
|
||||
mainWindow.once("ready-to-show", () => {
|
||||
mainWindow.webContents.once("did-finish-load", () => {
|
||||
mainWindow.show();
|
||||
if (!isDev) {
|
||||
verifyUpdate();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
app.whenReady().then(async () => {
|
||||
if (isDev) {
|
||||
app.setAsDefaultProtocolClient("nxm", process.execPath, [path.resolve(process.argv[1])]);
|
||||
} else {
|
||||
app.setAsDefaultProtocolClient("nxm");
|
||||
sevenZipPath = path7za.replace("\\app.asar\\node_modules", "");
|
||||
sevenZipPath = path7za.replace(path.join("app.asar", "node_modules"), "");
|
||||
Menu.setApplicationMenu(null);
|
||||
}
|
||||
|
||||
if (gotTheLock) {
|
||||
createNexus(loadNexusApi());
|
||||
checkInstalledMods();
|
||||
await checkInstalledMods();
|
||||
await checkForCoreAndPatcherMods();
|
||||
createWindow();
|
||||
}
|
||||
|
||||
app.on("activate", () => {
|
||||
app.on("activate", async () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
@@ -115,13 +129,61 @@ app.on("open-url", (event, url) => {
|
||||
handleNxmUrl(url);
|
||||
});
|
||||
|
||||
async function verifyUpdate() {
|
||||
const GITHUB_URL = "https://api.github.com/repos/Gabi-Zar/Silk-Fly-Launcher/releases";
|
||||
|
||||
const res = await fetch(GITHUB_URL, {
|
||||
headers: {
|
||||
"User-Agent": userAgent,
|
||||
Accept: "application/vnd.github+json",
|
||||
},
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
if (res.status == 403) {
|
||||
mainWindow.webContents.send("showToast", "Github has blocked the application. Please try again later.", "error");
|
||||
}
|
||||
throw new Error(`GitHub API error: ${res.status}`);
|
||||
}
|
||||
|
||||
const releases = await res.json();
|
||||
const prerelease = releases.find((r) => r.prerelease);
|
||||
const release = releases.find((r) => !r.prerelease && !r.draft);
|
||||
|
||||
let prereleaseVersion;
|
||||
let releaseVersion;
|
||||
let latestVersion;
|
||||
|
||||
if (prerelease) {
|
||||
prereleaseVersion = prerelease.tag_name.replace(/^v/, "");
|
||||
latestVersion = prereleaseVersion;
|
||||
}
|
||||
if (release) {
|
||||
releaseVersion = release.tag_name.replace(/^v/, "");
|
||||
latestVersion = releaseVersion;
|
||||
}
|
||||
|
||||
if (prereleaseVersion && releaseVersion) {
|
||||
latestVersion = semverGt(prereleaseVersion, releaseVersion) ? prereleaseVersion : releaseVersion;
|
||||
}
|
||||
if (latestVersion != VERSION) {
|
||||
mainWindow.webContents.send(
|
||||
"showBanner",
|
||||
`Update v${latestVersion} is available on <a href="" class="link" onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar/Silk-Fly-Launcher/releases/tag/v${latestVersion}')">GitHub</a>! Your current version is ${VERSION}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
///////////////// SAVING AND LOADING /////////////////
|
||||
ipcMain.handle("save-path", (event, path) => {
|
||||
saveSilksongPath(path);
|
||||
ipcMain.handle("save-path", async (event, path) => {
|
||||
await saveSilksongPath(path);
|
||||
});
|
||||
function saveSilksongPath(path) {
|
||||
|
||||
async function saveSilksongPath(path) {
|
||||
store.set("silksong-path", path);
|
||||
await checkInstalledMods();
|
||||
await checkForCoreAndPatcherMods();
|
||||
}
|
||||
|
||||
function loadSilksongPath() {
|
||||
@@ -201,16 +263,48 @@ ipcMain.handle("load-theme", () => {
|
||||
return theme;
|
||||
});
|
||||
|
||||
async function saveModInfo(modId, suppr = false) {
|
||||
async function saveModInfo(modId, suppr = false, optionalModInfo = {}) {
|
||||
if (suppr == true) {
|
||||
installedModsStore.delete(String(modId));
|
||||
return;
|
||||
}
|
||||
|
||||
const modInfo = onlineCachedModList.find((mod) => mod.modId == modId);
|
||||
let modInfo;
|
||||
if (onlineCachedModList) {
|
||||
modInfo = onlineCachedModList.find((mod) => mod.modId == modId);
|
||||
}
|
||||
if (!modInfo) {
|
||||
if (thunderstoreCachedModList) {
|
||||
modInfo = thunderstoreCachedModList.find((mod) => mod.modId == modId);
|
||||
}
|
||||
}
|
||||
if (!modInfo) {
|
||||
modInfo = optionalModInfo;
|
||||
}
|
||||
modInfo.activated = true;
|
||||
|
||||
const modFiles = await fs.readdir(path.join(modSavePath, modId));
|
||||
modInfo.fileSize = 0;
|
||||
for (const file of modFiles) {
|
||||
const fileStats = await fs.stat(path.join(modSavePath, modId, file));
|
||||
modInfo.fileSize += fileStats.size;
|
||||
}
|
||||
|
||||
installedModsStore.set(String(modId), modInfo);
|
||||
}
|
||||
|
||||
ipcMain.handle("save-linux-steam", (event, state) => {
|
||||
store.set("linux.steam", state);
|
||||
});
|
||||
|
||||
function loadLinuxSteam() {
|
||||
return store.get("linux.steam");
|
||||
}
|
||||
|
||||
ipcMain.handle("load-linux-steam", () => {
|
||||
return loadLinuxSteam();
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
/////////////////// DATA HANDLING ////////////////////
|
||||
|
||||
@@ -303,16 +397,20 @@ async function installBepinex() {
|
||||
|
||||
const release = await res.json();
|
||||
|
||||
const asset = release.assets.find((a) => a.name.endsWith(".zip") && a.name.toLowerCase().includes("win_x64"));
|
||||
let asset;
|
||||
if (process.platform === "win32") {
|
||||
asset = release.assets.find((a) => a.name.endsWith(".zip") && a.name.toLowerCase().includes("win_x64"));
|
||||
} else if (process.platform === "linux") {
|
||||
asset = release.assets.find((a) => a.name.endsWith(".zip") && a.name.toLowerCase().includes("linux_x64"));
|
||||
}
|
||||
|
||||
await downloadAndUnzip(asset.browser_download_url, silksongPath);
|
||||
|
||||
saveBepinexVersion(release.tag_name);
|
||||
}
|
||||
|
||||
if (await fileExists(modSavePath)) {
|
||||
await fs.cp(modSavePath, path.join(silksongPath, "BepInEx", "plugins"), { recursive: true });
|
||||
}
|
||||
await checkInstalledMods();
|
||||
await checkForCoreAndPatcherMods();
|
||||
}
|
||||
|
||||
ipcMain.handle("install-bepinex", async () => {
|
||||
@@ -350,6 +448,8 @@ async function backupBepinex() {
|
||||
const bepinexFolderPath = path.join(silksongPath, "BepInEx");
|
||||
const bepinexBackupPath = path.join(silksongPath, "BepInEx-Backup");
|
||||
const BepinexPluginsPath = path.join(silksongPath, "BepInEx", "plugins");
|
||||
const bepinexCorePath = path.join(silksongPath, "BepInEx", "core", "custom");
|
||||
const bepinexPatcherPath = path.join(silksongPath, "BepInEx", "patchers", "custom");
|
||||
|
||||
if (!(await fileExists(silksongPath))) {
|
||||
mainWindow.webContents.send("showToast", "Path to the game invalid", "warning");
|
||||
@@ -360,9 +460,15 @@ async function backupBepinex() {
|
||||
await fs.mkdir(bepinexBackupPath);
|
||||
}
|
||||
|
||||
if (fileExists(BepinexPluginsPath)) {
|
||||
if (await fileExists(BepinexPluginsPath)) {
|
||||
await fs.rm(BepinexPluginsPath, { recursive: true });
|
||||
}
|
||||
if (await fileExists(bepinexCorePath)) {
|
||||
await fs.rm(bepinexCorePath, { recursive: true });
|
||||
}
|
||||
if (await fileExists(bepinexPatcherPath)) {
|
||||
await fs.rm(bepinexPatcherPath, { recursive: true });
|
||||
}
|
||||
|
||||
if (await fileExists(bepinexFolderPath)) {
|
||||
await fs.cp(bepinexFolderPath, path.join(bepinexBackupPath, "BepInEx"), {
|
||||
@@ -401,7 +507,7 @@ ipcMain.handle("delete-bepinex-backup", async () => {
|
||||
});
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
/////////////////////// NEXUS ////////////////////////
|
||||
//////////////// NEXUS / THUNDERSTORE ////////////////
|
||||
|
||||
async function createNexus(api) {
|
||||
if (api == undefined) {
|
||||
@@ -414,9 +520,10 @@ async function createNexus(api) {
|
||||
} catch (error) {
|
||||
if (error.mStatusCode == 401) {
|
||||
mainWindow.webContents.send("showToast", "Invalid Nexus API key", "error");
|
||||
}
|
||||
if (error.code == "ENOTFOUND") {
|
||||
} else if (error.code == "ENOTFOUND") {
|
||||
mainWindow.webContents.send("showToast", "Unable to communicate with Nexus servers", "error");
|
||||
} else {
|
||||
mainWindow.webContents.send("showToast", "Unable to create Nexus API ", "error");
|
||||
}
|
||||
nexus = undefined;
|
||||
}
|
||||
@@ -440,12 +547,17 @@ ipcMain.handle("get-mods", async (event, type) => {
|
||||
if (!installedCachedModList) {
|
||||
await searchInstalledMods("");
|
||||
}
|
||||
return { modsInfo: installedCachedModList, installedTotalCount: installedTotalModsCount };
|
||||
return { installedModsInfo: installedCachedModList, installedTotalCount: installedTotalModsCount };
|
||||
} else if (type == "mods-online") {
|
||||
if (!onlineCachedModList) {
|
||||
await searchNexusMods("");
|
||||
}
|
||||
return { mods: onlineCachedModList, onlineTotalCount: onlineTotalModsCount };
|
||||
return { onlineModsInfo: onlineCachedModList, onlineTotalCount: onlineTotalModsCount };
|
||||
} else if (type == "mods-thunderstore") {
|
||||
if (!thunderstoreCachedModList) {
|
||||
await searchThunderstoreMods("");
|
||||
}
|
||||
return { thunderstoreModsInfo: thunderstoreCachedModList, thunderstoreTotalCount: thunderstoreTotalModsCount };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -511,38 +623,6 @@ async function startDownload(modId, fileId, key, expires) {
|
||||
installedCachedModList = undefined;
|
||||
}
|
||||
|
||||
async function checkInstalledMods() {
|
||||
const bepinexFolderPath = path.join(loadSilksongPath(), "BepInEx");
|
||||
|
||||
for (const [key, modInfo] of Object.entries(installedModsStore.store)) {
|
||||
modInfo.modId = String(modInfo.modId);
|
||||
if (!(await fileExists(path.join(modSavePath, modInfo.modId)))) {
|
||||
saveModInfo(key, true);
|
||||
await fs.rm(path.join(bepinexFolderPath, "plugins", modInfo.modId), { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.handle("uninstall-mod", async (event, modId) => {
|
||||
modId = String(modId);
|
||||
const BepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
|
||||
const modPath = path.join(BepinexPluginsPath, modId);
|
||||
if (await fileExists(path.join(modSavePath, modId))) {
|
||||
await fs.rm(path.join(modSavePath, modId), { recursive: true });
|
||||
}
|
||||
if (await fileExists(modPath)) {
|
||||
await fs.rm(modPath, { recursive: true });
|
||||
}
|
||||
|
||||
for (let i = 0; i < installedCachedModList.length; i++) {
|
||||
if (installedCachedModList[i].modId == modId) {
|
||||
installedCachedModList.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
saveModInfo(modId, true);
|
||||
});
|
||||
|
||||
ipcMain.handle("search-nexus-mods", async (event, keywords, offset, count, sortFilter, sortOrder) => {
|
||||
await searchNexusMods(keywords, offset, count, sortFilter, sortOrder);
|
||||
});
|
||||
@@ -600,6 +680,7 @@ async function searchNexusMods(keywords, offset = 0, count = 10, sortFilter = "d
|
||||
onlineCachedModList = data.mods.nodes;
|
||||
|
||||
for (let i = 0; i < onlineCachedModList.length; i++) {
|
||||
onlineCachedModList[i].source = "nexusmods";
|
||||
if (onlineCachedModList[i].modId == 26) {
|
||||
onlineCachedModList.splice(i, 1);
|
||||
}
|
||||
@@ -608,6 +689,116 @@ async function searchNexusMods(keywords, offset = 0, count = 10, sortFilter = "d
|
||||
onlineTotalModsCount = data.mods.totalCount;
|
||||
}
|
||||
|
||||
ipcMain.handle("search-thunderstore-mods", async (event, keywords, offset, count, sortFilter, sortOrder) => {
|
||||
await searchThunderstoreMods(keywords, offset, count, sortFilter, sortOrder);
|
||||
});
|
||||
|
||||
async function searchThunderstoreMods(keywords, offset = 0, count = 10, sortFilter = "downloads", sortOrder = "DESC") {
|
||||
if (allThunderstoreCachedModListNeedRefresh) {
|
||||
const res = await fetch("https://thunderstore.io/c/hollow-knight-silksong/api/v1/package/", {
|
||||
headers: {
|
||||
"User-Agent": userAgent,
|
||||
},
|
||||
});
|
||||
let modsInfo = await res.json();
|
||||
|
||||
const modsToRemove = ["f21c391c-0bc5-431d-a233-95323b95e01b", "42f76853-d2a4-4520-949b-13a02fdbbbcb", "34eac80c-5497-470e-b98c-f53421b828c0"];
|
||||
let reMappedModsInfo = [];
|
||||
for (let i = 0; i < modsInfo.length; i++) {
|
||||
modsInfo[i].source = "thunderstore";
|
||||
if (modsToRemove.includes(modsInfo[i].uuid4)) {
|
||||
modsInfo.splice(i, 1);
|
||||
i--;
|
||||
continue;
|
||||
}
|
||||
reMappedModsInfo.push(reMapThunderstoreModsInfo(modsInfo[i]));
|
||||
}
|
||||
allThunderstoreCachedModList = reMappedModsInfo;
|
||||
allThunderstoreCachedModListNeedRefresh = false;
|
||||
setTimeout(
|
||||
() => {
|
||||
allThunderstoreCachedModListNeedRefresh = true;
|
||||
},
|
||||
10 * 60 * 1000,
|
||||
);
|
||||
}
|
||||
|
||||
const result = sortAndFilterModsList(allThunderstoreCachedModList, keywords, offset, count, sortFilter, sortOrder);
|
||||
thunderstoreCachedModList = result.list;
|
||||
thunderstoreTotalModsCount = result.totalCount;
|
||||
}
|
||||
|
||||
function reMapThunderstoreModsInfo(modInfo) {
|
||||
let totalDownloads = 0;
|
||||
for (const version of modInfo.versions) {
|
||||
totalDownloads += version.downloads;
|
||||
}
|
||||
|
||||
return {
|
||||
author: modInfo.owner,
|
||||
endorsements: modInfo.rating_score,
|
||||
modId: modInfo.uuid4,
|
||||
name: modInfo.name,
|
||||
pictureUrl: modInfo.versions[0].icon,
|
||||
summary: modInfo.versions[0].description,
|
||||
updatedAt: modInfo.date_updated,
|
||||
createdAt: modInfo.date_created,
|
||||
version: modInfo.versions[0].version_number,
|
||||
downloads: totalDownloads,
|
||||
fileSize: modInfo.versions[0].file_size,
|
||||
source: modInfo.source,
|
||||
dependencies: modInfo.versions[0].dependencies,
|
||||
};
|
||||
}
|
||||
|
||||
ipcMain.handle("download-thunderstore-mods", async (event, url, modId) => {
|
||||
await downloadThunderstoreMods(url, modId);
|
||||
});
|
||||
|
||||
async function downloadThunderstoreMods(url, modId) {
|
||||
const bepinexFolderPath = path.join(loadSilksongPath(), "BepInEx");
|
||||
if (!(await fileExists(loadSilksongPath()))) {
|
||||
mainWindow.webContents.send("showToast", "Path to the game invalid", "warning");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(await fileExists(modSavePath))) {
|
||||
await fs.mkdir(modSavePath);
|
||||
}
|
||||
|
||||
await downloadAndUnzip(url, path.join(modSavePath, modId));
|
||||
if (await fileExists(bepinexFolderPath)) {
|
||||
await fs.cp(path.join(modSavePath, modId), path.join(bepinexFolderPath, "plugins", modId), { recursive: true });
|
||||
}
|
||||
|
||||
saveModInfo(modId);
|
||||
await downloadThunderstoreModsDependencies(modId);
|
||||
await checkForCoreAndPatcherMods();
|
||||
mainWindow.webContents.send("showToast", "Mod downloaded successfully.");
|
||||
installedCachedModList = undefined;
|
||||
}
|
||||
|
||||
async function downloadThunderstoreModsDependencies(modId) {
|
||||
const dependencies = allThunderstoreCachedModList.find((mod) => mod.modId == modId).dependencies;
|
||||
for (const dependency of dependencies) {
|
||||
const dependencyArray = dependency.split("-");
|
||||
const modInfo = allThunderstoreCachedModList.find((mod) => mod.author === dependencyArray[0] && mod.name === dependencyArray[1]);
|
||||
if (modInfo) {
|
||||
const bepinexFolderPath = path.join(loadSilksongPath(), "BepInEx");
|
||||
const url = `https://thunderstore.io/package/download/${dependencyArray[0]}/${dependencyArray[1]}/${dependencyArray[2]}`;
|
||||
await downloadAndUnzip(url, path.join(modSavePath, modInfo.modId));
|
||||
if (await fileExists(bepinexFolderPath)) {
|
||||
await fs.cp(path.join(modSavePath, modInfo.modId), path.join(bepinexFolderPath, "plugins", modInfo.modId), { recursive: true });
|
||||
}
|
||||
saveModInfo(modInfo.modId, undefined, modInfo);
|
||||
await downloadThunderstoreModsDependencies(modInfo.modId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
//////////////////////// MODS ////////////////////////
|
||||
|
||||
ipcMain.handle("search-installed-mods", async (event, keywords, offset, count, sortFilter, sortOrder) => {
|
||||
await searchInstalledMods(keywords, offset, count, sortFilter, sortOrder);
|
||||
});
|
||||
@@ -618,21 +809,220 @@ async function searchInstalledMods(keywords, offset = 0, count = 10, sortFilter
|
||||
modsInfo.push(modInfo);
|
||||
}
|
||||
|
||||
const modsInfoFiltered = modsInfo.filter((mod) => mod.name.toLowerCase().includes(keywords.toLowerCase()));
|
||||
const result = sortAndFilterModsList(modsInfo, keywords, offset, count, sortFilter, sortOrder);
|
||||
installedCachedModList = result.list;
|
||||
installedTotalModsCount = result.totalCount;
|
||||
}
|
||||
|
||||
async function checkInstalledMods() {
|
||||
if (!loadSilksongPath()) {
|
||||
return;
|
||||
}
|
||||
const bepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
|
||||
|
||||
for (const [key, modInfo] of Object.entries(installedModsStore.store)) {
|
||||
modInfo.modId = String(modInfo.modId);
|
||||
if (!(await fileExists(path.join(modSavePath, modInfo.modId)))) {
|
||||
saveModInfo(key, true);
|
||||
if (await fileExists(path.join(bepinexPluginsPath, modInfo.modId))) {
|
||||
await fs.rm(path.join(bepinexPluginsPath, modInfo.modId), { recursive: true });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (modInfo.activated) {
|
||||
await fs.cp(path.join(modSavePath, modInfo.modId), path.join(bepinexPluginsPath, modInfo.modId), { recursive: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ipcMain.handle("uninstall-mod", async (event, modId) => {
|
||||
modId = String(modId);
|
||||
const BepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
|
||||
const modPath = path.join(BepinexPluginsPath, modId);
|
||||
if (await fileExists(path.join(modSavePath, modId))) {
|
||||
await fs.rm(path.join(modSavePath, modId), { recursive: true });
|
||||
}
|
||||
if (await fileExists(modPath)) {
|
||||
await fs.rm(modPath, { recursive: true });
|
||||
}
|
||||
|
||||
for (let i = 0; i < installedCachedModList.length; i++) {
|
||||
if (installedCachedModList[i].modId == modId) {
|
||||
installedCachedModList.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
saveModInfo(modId, true);
|
||||
checkForCoreAndPatcherMods();
|
||||
});
|
||||
|
||||
ipcMain.handle("activate-mod", async (event, modId) => {
|
||||
await activateMod(modId);
|
||||
});
|
||||
|
||||
async function activateMod(modId) {
|
||||
if (!loadSilksongPath()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const BepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
|
||||
|
||||
if (!installedModsStore.get(`${modId}.activated`)) {
|
||||
installedModsStore.set(`${modId}.activated`, true);
|
||||
}
|
||||
|
||||
if (bepinexVersion) {
|
||||
if (!(await fileExists(path.join(BepinexPluginsPath, String(modId))))) {
|
||||
await fs.cp(path.join(modSavePath, String(modId)), path.join(BepinexPluginsPath, String(modId)), { recursive: true });
|
||||
}
|
||||
}
|
||||
checkForCoreAndPatcherMods();
|
||||
}
|
||||
|
||||
ipcMain.handle("deactivate-mod", async (event, modId) => {
|
||||
const BepinexPluginsPath = path.join(loadSilksongPath(), "BepInEx", "plugins");
|
||||
|
||||
if (installedModsStore.get(`${modId}.activated`)) {
|
||||
installedModsStore.set(`${modId}.activated`, false);
|
||||
|
||||
if (bepinexVersion) {
|
||||
if (await fileExists(path.join(BepinexPluginsPath, String(modId)))) {
|
||||
await fs.rm(path.join(BepinexPluginsPath, String(modId)), { recursive: true });
|
||||
}
|
||||
}
|
||||
checkForCoreAndPatcherMods();
|
||||
}
|
||||
});
|
||||
|
||||
function sortAndFilterModsList(list, keywords, offset, count, sortFilter, sortOrder) {
|
||||
const listFiltered = list.filter((element) => element.name.toLowerCase().includes(keywords.toLowerCase()));
|
||||
const sortFactor = sortOrder == "ASC" ? 1 : -1;
|
||||
|
||||
let modsInfoSorted;
|
||||
let listSorted;
|
||||
if (sortFilter == "name" || sortFilter == "createdAt" || sortFilter == "updatedAt") {
|
||||
modsInfoSorted = modsInfoFiltered.sort((a, b) => sortFactor * a[sortFilter].localeCompare(b[sortFilter]));
|
||||
listSorted = listFiltered.sort((a, b) => sortFactor * a[sortFilter].localeCompare(b[sortFilter]));
|
||||
} else if (sortFilter == "downloads" || sortFilter == "endorsements" || sortFilter == "size") {
|
||||
if (sortFilter == "size") {
|
||||
sortFilter = "fileSize";
|
||||
}
|
||||
modsInfoSorted = modsInfoFiltered.sort((a, b) => sortFactor * (a[sortFilter] - b[sortFilter]));
|
||||
listSorted = listFiltered.sort((a, b) => sortFactor * (a[sortFilter] - b[sortFilter]));
|
||||
}
|
||||
|
||||
installedTotalModsCount = modsInfoSorted.length;
|
||||
installedCachedModList = modsInfoSorted.slice(offset, offset + count);
|
||||
return { list: listSorted.slice(offset, offset + count), totalCount: listSorted.length };
|
||||
}
|
||||
|
||||
ipcMain.handle("add-offline-mod", async () => {
|
||||
const { canceled, filePaths } = await dialog.showOpenDialog({
|
||||
title: "Add mod",
|
||||
properties: ["openFile"],
|
||||
filters: [{ name: "mod", extensions: ["dll", "zip", "7z"] }],
|
||||
});
|
||||
|
||||
if (canceled || !filePaths) return false;
|
||||
|
||||
const fileName = path.basename(filePaths[0]);
|
||||
const extension = path.extname(fileName).toLowerCase();
|
||||
let uuid;
|
||||
do {
|
||||
uuid = randomUUID();
|
||||
} while (installedModsStore.get(uuid));
|
||||
|
||||
if (extension === ".dll") {
|
||||
await fs.mkdir(path.join(modSavePath, uuid));
|
||||
await fs.copyFile(filePaths[0], path.join(modSavePath, uuid, fileName));
|
||||
} else if ([".zip", ".7z"].includes(extension)) {
|
||||
await extractArchive(filePaths[0], path.join(modSavePath, uuid));
|
||||
} else {
|
||||
mainWindow.webContents.send("showToast", `Unsupported file type: ${extension}`, "error");
|
||||
return false;
|
||||
}
|
||||
|
||||
activateMod(uuid);
|
||||
|
||||
const time = new Date().toLocaleDateString();
|
||||
const splitedTime = time.split("/");
|
||||
let newTime = "";
|
||||
for (let i = 2; i >= 0; i--) {
|
||||
newTime = newTime.concat(splitedTime[i]);
|
||||
if (i > 0) {
|
||||
newTime = newTime.concat("-");
|
||||
}
|
||||
}
|
||||
|
||||
await saveModInfo(uuid, false, {
|
||||
modId: uuid,
|
||||
name: fileName.split(".").shift(),
|
||||
summary: "Local mod",
|
||||
updatedAt: newTime,
|
||||
createdAt: newTime,
|
||||
source: "local",
|
||||
});
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
async function checkForCoreAndPatcherMods() {
|
||||
const bepinexFolderPath = path.join(loadSilksongPath(), "BepInEx");
|
||||
const bepinexPluginsPath = path.join(bepinexFolderPath, "Plugins");
|
||||
const bepinexCorePath = path.join(bepinexFolderPath, "core", "custom");
|
||||
const bepinexPatcherPath = path.join(bepinexFolderPath, "patchers", "custom");
|
||||
|
||||
if (await fileExists(bepinexCorePath)) {
|
||||
await fs.rm(bepinexCorePath, { recursive: true });
|
||||
}
|
||||
if (await fileExists(bepinexPatcherPath)) {
|
||||
await fs.rm(bepinexPatcherPath, { recursive: true });
|
||||
}
|
||||
await fs.mkdir(bepinexCorePath, { recursive: true });
|
||||
await fs.mkdir(bepinexPatcherPath, { recursive: true });
|
||||
|
||||
async function scanDir(dirPath, relativePath = "") {
|
||||
let entries;
|
||||
try {
|
||||
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.isDirectory()) continue;
|
||||
|
||||
const currentRelative = path.join(relativePath, entry.name);
|
||||
|
||||
if (entry.name === "core") {
|
||||
await copyContents(path.join(dirPath, entry.name), bepinexCorePath);
|
||||
if (await fileExists(path.join(bepinexPluginsPath, currentRelative))) {
|
||||
await fs.rm(path.join(bepinexPluginsPath, currentRelative), { recursive: true });
|
||||
}
|
||||
} else if (entry.name === "patchers") {
|
||||
await copyContents(path.join(dirPath, entry.name), bepinexPatcherPath);
|
||||
if (await fileExists(path.join(bepinexPluginsPath, currentRelative))) {
|
||||
await fs.rm(path.join(bepinexPluginsPath, currentRelative), { recursive: true });
|
||||
}
|
||||
} else {
|
||||
if (!(await fileExists(path.join(bepinexPluginsPath, currentRelative)))) continue;
|
||||
await scanDir(path.join(dirPath, entry.name), currentRelative);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function copyContents(srcDir, destDir) {
|
||||
let files;
|
||||
try {
|
||||
files = await fs.readdir(srcDir);
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
const src = path.join(srcDir, file);
|
||||
const dest = path.join(destDir, file);
|
||||
await fs.copyFile(src, dest);
|
||||
}
|
||||
}
|
||||
|
||||
await scanDir(modSavePath);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
@@ -681,7 +1071,16 @@ ipcMain.handle("open-window", async (event, file) => {
|
||||
});
|
||||
|
||||
ipcMain.handle("launch-game", async (event, mode) => {
|
||||
const silksongExecutablePath = path.join(loadSilksongPath(), "Hollow Knight Silksong.exe");
|
||||
let silksongExecutable;
|
||||
if (process.platform === "win32") {
|
||||
silksongExecutable = "Hollow Knight Silksong.exe";
|
||||
} else if (process.platform === "linux") {
|
||||
silksongExecutable = "Hollow Knight Silksong";
|
||||
}
|
||||
|
||||
const silksongExecutablePath = path.join(loadSilksongPath(), silksongExecutable);
|
||||
const silksongScriptPath = path.join(loadSilksongPath(), "run_bepinex.sh");
|
||||
const bepinexFolderPath = path.join(loadSilksongPath(), "BepInEx");
|
||||
if (!fileExists(silksongExecutablePath)) {
|
||||
mainWindow.webContents.send("showToast", "Path to the game invalid", "warning");
|
||||
return;
|
||||
@@ -689,22 +1088,74 @@ ipcMain.handle("launch-game", async (event, mode) => {
|
||||
|
||||
if (mode === "modded") {
|
||||
if (await fileExists(bepinexFolderPath)) {
|
||||
await shell.openExternal(silksongExecutablePath);
|
||||
if (process.platform === "win32") {
|
||||
executeGame(silksongExecutablePath);
|
||||
}
|
||||
if (process.platform === "linux") {
|
||||
if (loadLinuxSteam()) {
|
||||
if (!(await getSteamLinuxState())) {
|
||||
mainWindow.webContents.send("showToast", "Preparing Steam");
|
||||
await prepareSteamLinux(true);
|
||||
}
|
||||
const game = spawn("steam", ["-applaunch", "1030300"], {
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
});
|
||||
game.unref();
|
||||
return;
|
||||
}
|
||||
executeGame(silksongScriptPath, [silksongExecutable]);
|
||||
}
|
||||
} else {
|
||||
await installBepinex();
|
||||
await shell.openExternal(silksongExecutablePath);
|
||||
if (process.platform === "win32") {
|
||||
executeGame(silksongExecutablePath);
|
||||
}
|
||||
if (process.platform === "linux") {
|
||||
if (loadLinuxSteam()) {
|
||||
if (!(await getSteamLinuxState())) {
|
||||
mainWindow.webContents.send("showToast", "Preparing Steam");
|
||||
await prepareSteamLinux(true);
|
||||
}
|
||||
const game = spawn("steam", ["-applaunch", "1030300"], {
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
});
|
||||
game.unref();
|
||||
return;
|
||||
}
|
||||
executeGame(silksongScriptPath, [silksongExecutable]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mode === "vanilla") {
|
||||
if (await fileExists(bepinexFolderPath)) {
|
||||
await backupBepinex();
|
||||
await shell.openExternal(silksongExecutablePath);
|
||||
if (process.platform === "linux" && loadLinuxSteam() && (await getSteamLinuxState())) {
|
||||
mainWindow.webContents.send("showToast", "Preparing Steam");
|
||||
await prepareSteamLinux(false);
|
||||
}
|
||||
executeGame(silksongExecutablePath);
|
||||
} else {
|
||||
await shell.openExternal(silksongExecutablePath);
|
||||
if (process.platform === "linux" && loadLinuxSteam() && (await getSteamLinuxState())) {
|
||||
mainWindow.webContents.send("showToast", "Preparing Steam");
|
||||
await prepareSteamLinux(false);
|
||||
}
|
||||
executeGame(silksongExecutablePath);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function executeGame(path, args = []) {
|
||||
await fs.chmod(path, 0o755);
|
||||
const game = spawn(path, args, {
|
||||
cwd: loadSilksongPath(),
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
});
|
||||
game.unref();
|
||||
}
|
||||
|
||||
async function downloadAndUnzip(url, toPath) {
|
||||
url = new URL(url);
|
||||
const fileName = url.pathname.split("/").pop();
|
||||
@@ -722,7 +1173,10 @@ async function downloadAndUnzip(url, toPath) {
|
||||
await fs.unlink(tempPath);
|
||||
}
|
||||
|
||||
function extractArchive(archivePath, destPath) {
|
||||
async function extractArchive(archivePath, destPath) {
|
||||
if (process.platform === "linux") {
|
||||
await prepareSevenZipLinux();
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
const stream = extractFull(archivePath, destPath, {
|
||||
$bin: sevenZipPath,
|
||||
@@ -736,3 +1190,78 @@ function extractArchive(archivePath, destPath) {
|
||||
ipcMain.handle("get-version", () => {
|
||||
return VERSION;
|
||||
});
|
||||
|
||||
async function prepareSevenZipLinux() {
|
||||
const targetPath = path.join(tempPath, "7za");
|
||||
if (await fileExists(targetPath)) {
|
||||
sevenZipPath = targetPath;
|
||||
return;
|
||||
}
|
||||
|
||||
await fs.copyFile(sevenZipPath, targetPath);
|
||||
await fs.chmod(targetPath, 0o755);
|
||||
|
||||
sevenZipPath = targetPath;
|
||||
}
|
||||
|
||||
async function prepareSteamLinux(isModded) {
|
||||
const kill = spawn("killall", ["steam"]);
|
||||
kill.unref();
|
||||
await waitForSteamToClose();
|
||||
|
||||
const steamUserDataPath = path.join(process.env.HOME, ".local", "share", "Steam", "userdata");
|
||||
const usersId = await fs.readdir(steamUserDataPath);
|
||||
|
||||
for (const userId of usersId) {
|
||||
const steamConfigPath = path.join(steamUserDataPath, userId, "config", "localconfig.vdf");
|
||||
const rawConfig = await fs.readFile(steamConfigPath, { encoding: "utf8" });
|
||||
let parsedConfig = vdf.parse(rawConfig);
|
||||
if (isModded) {
|
||||
parsedConfig.UserLocalConfigStore.Software.Valve.Steam.apps[1030300].LaunchOptions = "./run_bepinex.sh %command%";
|
||||
} else {
|
||||
parsedConfig.UserLocalConfigStore.Software.Valve.Steam.apps[1030300].LaunchOptions = "";
|
||||
}
|
||||
|
||||
const config = vdf.stringify(parsedConfig);
|
||||
await fs.writeFile(steamConfigPath, config, { encoding: "utf8" });
|
||||
}
|
||||
|
||||
const steam = spawn("steam", [], {
|
||||
detached: true,
|
||||
stdio: "ignore",
|
||||
});
|
||||
steam.unref();
|
||||
}
|
||||
|
||||
async function getSteamLinuxState() {
|
||||
const steamUserDataPath = path.join(process.env.HOME, ".local", "share", "Steam", "userdata");
|
||||
const usersId = await fs.readdir(steamUserDataPath);
|
||||
|
||||
let result = [];
|
||||
for (const userId of usersId) {
|
||||
const steamConfigPath = path.join(steamUserDataPath, userId, "config", "localconfig.vdf");
|
||||
const rawConfig = await fs.readFile(steamConfigPath, { encoding: "utf8" });
|
||||
let parsedConfig = vdf.parse(rawConfig);
|
||||
|
||||
result.push(parsedConfig.UserLocalConfigStore.Software.Valve.Steam.apps[1030300].LaunchOptions === "./run_bepinex.sh %command%");
|
||||
}
|
||||
return result.every(Boolean);
|
||||
}
|
||||
|
||||
function waitForSteamToClose() {
|
||||
return new Promise((resolve) => {
|
||||
function check() {
|
||||
const pgrep = spawn("pgrep", ["-x", "steam"]);
|
||||
|
||||
pgrep.on("close", (code) => {
|
||||
if (code == 1) {
|
||||
resolve();
|
||||
} else {
|
||||
setTimeout(check, 500);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
check();
|
||||
});
|
||||
}
|
||||
|
||||
383
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "silkflylauncher",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.0-dev",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "silkflylauncher",
|
||||
"version": "1.0.0",
|
||||
"version": "1.0.0-dev",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@nexusmods/nexus-api": "^1.1.5",
|
||||
@@ -14,15 +14,19 @@
|
||||
"electron-store": "^11.0.2",
|
||||
"graphql": "^16.12.0",
|
||||
"graphql-request": "^7.4.0",
|
||||
"node-7z": "^3.0.0"
|
||||
"node-7z": "^3.0.0",
|
||||
"semver": "^7.7.4",
|
||||
"vdf-parser": "^1.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^7.11.1",
|
||||
"@electron-forge/maker-deb": "^7.11.1",
|
||||
"@electron-forge/maker-wix": "^7.11.1",
|
||||
"@electron-forge/maker-zip": "^7.11.1",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "^7.11.1",
|
||||
"@electron-forge/plugin-fuses": "^7.11.1",
|
||||
"@electron/fuses": "^1.8.0",
|
||||
"@reforged/maker-appimage": "^5.2.0",
|
||||
"cross-env": "^10.1.0",
|
||||
"electron": "^40.6.0"
|
||||
}
|
||||
@@ -169,6 +173,23 @@
|
||||
"node": ">= 16.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@electron-forge/maker-deb": {
|
||||
"version": "7.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@electron-forge/maker-deb/-/maker-deb-7.11.1.tgz",
|
||||
"integrity": "sha512-QTYiryQLYPDkq6pIfBmx0GQ6D8QatUkowH7rTlW5MnCUa0uumX0Xu7yGIjesuwW37fxT3Lv4xi+FSXMCm2eC1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@electron-forge/maker-base": "7.11.1",
|
||||
"@electron-forge/shared-types": "7.11.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 16.4.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"electron-installer-debian": "^3.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@electron-forge/maker-wix": {
|
||||
"version": "7.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@electron-forge/maker-wix/-/maker-wix-7.11.1.tgz",
|
||||
@@ -1238,6 +1259,37 @@
|
||||
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@reforged/maker-appimage": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@reforged/maker-appimage/-/maker-appimage-5.2.0.tgz",
|
||||
"integrity": "sha512-5u7spsDMyMfwqAnTRsSipVgTIy+DW+wlfhceaRghCuTvyY8Sti8/tFhVKj4vb+dYTqPvS7m30Gl0InGv22J8RQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@electron-forge/maker-base": "^6.0.0 || ^7.0.0",
|
||||
"@reforged/maker-types": "^2.1.0",
|
||||
"@spacingbat3/lss": "^1.0.0",
|
||||
"semver": "^7.3.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=19.0.0 || ^18.11.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/SpacingBat3/ReForged?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@reforged/maker-types": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@reforged/maker-types/-/maker-types-2.1.0.tgz",
|
||||
"integrity": "sha512-gNMAFO6mxqGwuUov0CzXGTHUMfAawlM6v/uYrqVnKeMwmceaLBt3HtfPcuNapDSH4br6D4EZ14WuWbgefmDfOQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/SpacingBat3/ReForged?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@sindresorhus/is": {
|
||||
"version": "4.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
|
||||
@@ -1251,6 +1303,13 @@
|
||||
"url": "https://github.com/sindresorhus/is?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@spacingbat3/lss": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@spacingbat3/lss/-/lss-1.2.0.tgz",
|
||||
"integrity": "sha512-aywhxHNb6l7COooF3m439eT/6QN8E/RSl5IVboSKthMHcp0GlZYMSoS7546rqDLmFRxTD8f1tu/NIS9vtDwYAg==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/@szmarczak/http-timer": {
|
||||
"version": "4.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
|
||||
@@ -1316,6 +1375,17 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/fs-extra": {
|
||||
"version": "9.0.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.13.tgz",
|
||||
"integrity": "sha512-nEnwB++1u5lVDM2UI4c1+5R+FYaKfaAzS4OococimjVm3nQw3TuzH5UNsocrcTBbhnerblyHj4A49qXbIiZdpA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/http-cache-semantics": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz",
|
||||
@@ -2794,6 +2864,242 @@
|
||||
"node": ">= 12.20.55"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-common": {
|
||||
"version": "0.10.4",
|
||||
"resolved": "https://registry.npmjs.org/electron-installer-common/-/electron-installer-common-0.10.4.tgz",
|
||||
"integrity": "sha512-8gMNPXfAqUE5CfXg8RL0vXpLE9HAaPkgLXVoHE3BMUzogMWenf4LmwQ27BdCUrEhkjrKl+igs2IHJibclR3z3Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.2.5",
|
||||
"@malept/cross-spawn-promise": "^1.0.0",
|
||||
"debug": "^4.1.1",
|
||||
"fs-extra": "^9.0.0",
|
||||
"glob": "^7.1.4",
|
||||
"lodash": "^4.17.15",
|
||||
"parse-author": "^2.0.0",
|
||||
"semver": "^7.1.1",
|
||||
"tmp-promise": "^3.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/electron-userland/electron-installer-common?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@types/fs-extra": "^9.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-common/node_modules/@malept/cross-spawn-promise": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz",
|
||||
"integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/malept"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund"
|
||||
}
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-common/node_modules/fs-extra": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
|
||||
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-debian": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/electron-installer-debian/-/electron-installer-debian-3.2.0.tgz",
|
||||
"integrity": "sha512-58ZrlJ1HQY80VucsEIG9tQ//HrTlG6sfofA3nRGr6TmkX661uJyu4cMPPh6kXW+aHdq/7+q25KyQhDrXvRL7jw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin",
|
||||
"linux"
|
||||
],
|
||||
"dependencies": {
|
||||
"@malept/cross-spawn-promise": "^1.0.0",
|
||||
"debug": "^4.1.1",
|
||||
"electron-installer-common": "^0.10.2",
|
||||
"fs-extra": "^9.0.0",
|
||||
"get-folder-size": "^2.0.1",
|
||||
"lodash": "^4.17.4",
|
||||
"word-wrap": "^1.2.3",
|
||||
"yargs": "^16.0.2"
|
||||
},
|
||||
"bin": {
|
||||
"electron-installer-debian": "src/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-debian/node_modules/@malept/cross-spawn-promise": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@malept/cross-spawn-promise/-/cross-spawn-promise-1.1.1.tgz",
|
||||
"integrity": "sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/malept"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/subscription/pkg/npm-.malept-cross-spawn-promise?utm_medium=referral&utm_source=npm_fund"
|
||||
}
|
||||
],
|
||||
"license": "Apache-2.0",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-debian/node_modules/cliui": {
|
||||
"version": "7.0.4",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
|
||||
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-debian/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/electron-installer-debian/node_modules/fs-extra": {
|
||||
"version": "9.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
|
||||
"integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"at-least-node": "^1.0.0",
|
||||
"graceful-fs": "^4.2.0",
|
||||
"jsonfile": "^6.0.1",
|
||||
"universalify": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-debian/node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-debian/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-debian/node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-debian/node_modules/yargs": {
|
||||
"version": "16.2.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
|
||||
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"cliui": "^7.0.2",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.0",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^20.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-installer-debian/node_modules/yargs-parser": {
|
||||
"version": "20.2.9",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
|
||||
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-store": {
|
||||
"version": "11.0.2",
|
||||
"resolved": "https://registry.npmjs.org/electron-store/-/electron-store-11.0.2.tgz",
|
||||
@@ -3537,6 +3843,15 @@
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/gar": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/gar/-/gar-1.0.4.tgz",
|
||||
"integrity": "sha512-w4n9cPWyP7aHxKxYHFQMegj7WIAsL/YX/C4Bs5Rr8s1H9M1rNtRWRsw+ovYMkXDQ5S4ZbYHsHAPmevPjPgw44w==",
|
||||
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
@@ -3547,6 +3862,21 @@
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/get-folder-size": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-folder-size/-/get-folder-size-2.0.1.tgz",
|
||||
"integrity": "sha512-+CEb+GDCM7tkOS2wdMKTn9vU7DgnKUTuDlehkNJKNSovdCOVxs14OfKCk4cvSaR3za4gj+OBdl9opPN9xrJ0zA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"gar": "^1.0.4",
|
||||
"tiny-each-async": "2.0.3"
|
||||
},
|
||||
"bin": {
|
||||
"get-folder-size": "bin/get-folder-size"
|
||||
}
|
||||
},
|
||||
"node_modules/get-package-info": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-package-info/-/get-package-info-1.0.0.tgz",
|
||||
@@ -6778,6 +7108,14 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tiny-each-async": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tiny-each-async/-/tiny-each-async-2.0.3.tgz",
|
||||
"integrity": "sha512-5ROII7nElnAirvFn8g7H7MtpfV1daMcyfTGQwsn/x2VtyV+VPiO5CjReCJtWLvoKTDEDmZocf3cNPraiMnBXLA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/tmp": {
|
||||
"version": "0.0.33",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
|
||||
@@ -6791,6 +7129,28 @@
|
||||
"node": ">=0.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tmp-promise": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tmp-promise/-/tmp-promise-3.0.3.tgz",
|
||||
"integrity": "sha512-RwM7MoPojPxsOBYnyd2hy0bxtIlVrihNs9pj5SUvY8Zz1sQcQG2tG1hSr8PDxfgEB8RNKDhqbIlroIarSNDNsQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tmp": "^0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/tmp-promise/node_modules/tmp": {
|
||||
"version": "0.2.5",
|
||||
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz",
|
||||
"integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=14.14"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
@@ -7035,6 +7395,12 @@
|
||||
"spdx-expression-parse": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vdf-parser": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/vdf-parser/-/vdf-parser-1.2.1.tgz",
|
||||
"integrity": "sha512-g5c73LP8NSebruEiVjOpV6KlN6xgVXBXc5UbexHWzjkatLJR++gUBhg76YqfJ9j4ZLfZ+Hv00592uiDywR+dWg==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/verror": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
|
||||
@@ -7172,6 +7538,17 @@
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/word-wrap": {
|
||||
"version": "1.2.5",
|
||||
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
|
||||
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "6.2.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
|
||||
|
||||
10
package.json
@@ -10,17 +10,21 @@
|
||||
"package": "electron-forge package",
|
||||
"make": "electron-forge make",
|
||||
"make:msi": "cross-env BUILD_TARGET=msi electron-forge make",
|
||||
"make:zip": "cross-env BUILD_TARGET=zip electron-forge make"
|
||||
"make:zip": "cross-env BUILD_TARGET=zip electron-forge make",
|
||||
"make:deb": "cross-env BUILD_TARGET=deb electron-forge make",
|
||||
"make:appimage": "cross-env BUILD_TARGET=appimage electron-forge make"
|
||||
},
|
||||
"author": "GabiZar",
|
||||
"license": "GPL-3.0",
|
||||
"devDependencies": {
|
||||
"@electron-forge/cli": "^7.11.1",
|
||||
"@electron-forge/maker-deb": "^7.11.1",
|
||||
"@electron-forge/maker-wix": "^7.11.1",
|
||||
"@electron-forge/maker-zip": "^7.11.1",
|
||||
"@electron-forge/plugin-auto-unpack-natives": "^7.11.1",
|
||||
"@electron-forge/plugin-fuses": "^7.11.1",
|
||||
"@electron/fuses": "^1.8.0",
|
||||
"@reforged/maker-appimage": "^5.2.0",
|
||||
"cross-env": "^10.1.0",
|
||||
"electron": "^40.6.0"
|
||||
},
|
||||
@@ -30,7 +34,9 @@
|
||||
"electron-store": "^11.0.2",
|
||||
"graphql": "^16.12.0",
|
||||
"graphql-request": "^7.4.0",
|
||||
"node-7z": "^3.0.0"
|
||||
"node-7z": "^3.0.0",
|
||||
"semver": "^7.7.4",
|
||||
"vdf-parser": "^1.2.1"
|
||||
},
|
||||
"AES-key-nexus-api": "__AES_KEY__"
|
||||
}
|
||||
|
||||
25
preload.js
@@ -21,6 +21,8 @@ contextBridge.exposeInMainWorld("files", {
|
||||
loadNexusAPI: () => ipcRenderer.invoke("load-nexus-api"),
|
||||
saveTheme: (theme, lacePinState) => ipcRenderer.invoke("save-theme", theme, lacePinState),
|
||||
loadTheme: () => ipcRenderer.invoke("load-theme"),
|
||||
saveLinuxSteam: (state) => ipcRenderer.invoke("save-linux-steam", state),
|
||||
loadLinuxSteam: () => ipcRenderer.invoke("load-linux-steam"),
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld("electronAPI", {
|
||||
@@ -29,11 +31,17 @@ contextBridge.exposeInMainWorld("electronAPI", {
|
||||
launchGame: (mode) => ipcRenderer.invoke("launch-game", mode),
|
||||
loadMainPage: () => ipcRenderer.invoke("load-main-page"),
|
||||
getPage: () => ipcRenderer.invoke("get-page"),
|
||||
getOS: () => process.platform,
|
||||
onShowToast: (callback) => {
|
||||
ipcRenderer.on("showToast", (event, message, type, duration) => {
|
||||
callback(message, type, duration);
|
||||
});
|
||||
},
|
||||
onShowBanner: (callback) => {
|
||||
ipcRenderer.on("showBanner", (event, message) => {
|
||||
callback(message);
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld("bepinex", {
|
||||
@@ -45,9 +53,20 @@ contextBridge.exposeInMainWorld("bepinex", {
|
||||
|
||||
contextBridge.exposeInMainWorld("nexus", {
|
||||
verifyAPI: () => ipcRenderer.invoke("verify-nexus-api"),
|
||||
getMods: (type) => ipcRenderer.invoke("get-mods", type),
|
||||
download: (link) => ipcRenderer.invoke("open-download", link),
|
||||
uninstall: (modId) => ipcRenderer.invoke("uninstall-mod", modId),
|
||||
search: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-nexus-mods", keywords, offset, count, sortFilter, sortOrder),
|
||||
searchInstalled: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-installed-mods", keywords, offset, count, sortFilter, sortOrder),
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld("mods", {
|
||||
searchInstalled: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-installed-mods", keywords, offset, count, sortFilter, sortOrder),
|
||||
uninstall: (modId) => ipcRenderer.invoke("uninstall-mod", modId),
|
||||
getMods: (type) => ipcRenderer.invoke("get-mods", type),
|
||||
activateMods: (modId) => ipcRenderer.invoke("activate-mod", modId),
|
||||
deactivateMods: (modId) => ipcRenderer.invoke("deactivate-mod", modId),
|
||||
add: () => ipcRenderer.invoke("add-offline-mod"),
|
||||
});
|
||||
|
||||
contextBridge.exposeInMainWorld("thunderstore", {
|
||||
search: (keywords, offset, count, sortFilter, sortOrder) => ipcRenderer.invoke("search-thunderstore-mods", keywords, offset, count, sortFilter, sortOrder),
|
||||
download: (url, modId) => ipcRenderer.invoke("download-thunderstore-mods", url, modId),
|
||||
});
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg role="img" focusable="false" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14"><path d="m 1,8.3051015 c 0,-0.6641 0.2078922,-1.253128 0.6236766,-1.772859 0.4157844,-0.51973 0.9412897,-0.854668 1.5822907,-0.999037 0.1790183,-0.791146 0.5890279,-1.437922 1.2184793,-1.946102 0.6294514,-0.508181 1.3570741,-0.762272 2.1713184,-0.762272 0.79692,0 1.507219,0.248316 2.130895,0.739172 0.623677,0.490857 1.027912,1.126083 1.212705,1.899904 l 0.190568,0 c 0.51973,0 0.999037,0.127045 1.437921,0.375361 0.438883,0.248316 0.791145,0.594803 1.045236,1.033686 C 12.86718,7.3118385 13,7.7853705 13,8.3051015 c 0,0.508181 -0.12127,0.981713 -0.363811,1.414822 -0.242541,0.4331085 -0.577479,0.7795955 -0.999038,1.0394605 -0.421559,0.259866 -0.889317,0.398461 -1.391723,0.415785 l -6.4735319,0 C 2.9980751,11.14052 2.3455245,10.846006 1.8084697,10.291627 1.2714148,9.7430225 1,9.0789225 1,8.3051015 Z m 0.987488,0 c 0,0.502406 0.1732435,0.935515 0.5197305,1.305101 0.346487,0.369586 0.7680462,0.5659275 1.2646776,0.5948015 l 6.4619829,0 c 0.496631,-0.0231 0.91819,-0.2252155 1.264677,-0.5948015 0.352262,-0.369586 0.525506,-0.80847 0.525506,-1.305101 0,-0.508181 -0.190568,-0.94129 -0.565929,-1.310876 -0.375361,-0.369586 -0.820019,-0.554379 -1.33975,-0.554379 l -0.923965,0 c -0.06352,0 -0.09817,-0.03465 -0.09817,-0.103947 L 9.055828,6.0067375 C 8.992308,5.3830605 8.72089,4.8575555 8.247358,4.4359965 7.773826,4.0144375 7.225222,3.8007705 6.59577,3.8007705 c -0.6294512,0 -1.1838304,0.213667 -1.6458131,0.635226 -0.4677575,0.421559 -0.7333975,0.947064 -0.7911454,1.570741 l -0.046198,0.329162 c 0,0.0693 -0.040424,0.103947 -0.1154957,0.103947 l -0.3060635,0 c -0.4850818,0.05775 -0.8893167,0.26564 -1.2127045,0.617901 -0.3233879,0.352262 -0.4908566,0.768047 -0.4908566,1.247354 z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.8 KiB |
@@ -1 +1 @@
|
||||
<svg role="img" focusable="false" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14"><path d="m 13,5.384615 v 5.07693 q 0,0.66346 -0.47596,1.13942 -0.47596,0.47596 -1.13942,0.47596 H 2.61538 q -0.663457,0 -1.139418,-0.47596 Q 1,11.125005 1,10.461545 V 3.538465 Q 1,2.875005 1.475962,2.399045 1.951923,1.923075 2.61538,1.923075 h 2.3077 q 0.66346,0 1.13942,0.47597 0.47596,0.47596 0.47596,1.13942 v 0.23077 h 4.84616 q 0.66346,0 1.13942,0.47596 Q 13,4.721155 13,5.384615 z"/></svg>
|
||||
<svg role="img" focusable="false" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="#ffffff"><path d="m 13,5.384615 v 5.07693 q 0,0.66346 -0.47596,1.13942 -0.47596,0.47596 -1.13942,0.47596 H 2.61538 q -0.663457,0 -1.139418,-0.47596 Q 1,11.125005 1,10.461545 V 3.538465 Q 1,2.875005 1.475962,2.399045 1.951923,1.923075 2.61538,1.923075 h 2.3077 q 0.66346,0 1.13942,0.47597 0.47596,0.47596 0.47596,1.13942 v 0.23077 h 4.84616 q 0.66346,0 1.13942,0.47596 Q 13,4.721155 13,5.384615 z"/></svg>
|
||||
|
Before Width: | Height: | Size: 503 B After Width: | Height: | Size: 518 B |
@@ -1 +1 @@
|
||||
<svg class="social-github" role="img" focusable="false" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14"><path d="m 7,1.1480539 c -3.313515,0 -6,2.68652 -6,6 0,2.65087 1.719175,4.9003701 4.103505,5.6933601 C 5.403301,12.897114 5.5,12.711544 5.5,12.552834 l 0,-1.11716 C 3.831039,11.798964 3.483359,10.728164 3.483359,10.728164 3.210913,10.034324 2.817388,9.8497439 2.817388,9.8497439 c -0.544456,-0.37205 0.04101,-0.36474 0.04101,-0.36474 0.602563,0.0425 0.919922,0.6186401 0.919922,0.6186401 0.535136,0.91698 1.403301,0.65187 1.746088,0.49855 0.05371,-0.38773 0.208951,-0.6523401 0.380854,-0.8022501 -1.333048,-0.15137 -2.733437,-0.66603 -2.733437,-2.96534 0,-0.65528 0.234379,-1.19045 0.618204,-1.61036 -0.0625,-0.15137 -0.267612,-0.76171 0.05764,-1.5879 0,0 0.503913,-0.16113 1.650349,0.61523 0.478981,-0.13282 0.992214,-0.19969 1.501981,-0.20214 0.509767,0.002 1.023903,0.0693 1.502913,0.20214 1.145534,-0.77636 1.648427,-0.61523 1.648427,-0.61523 0.326709,0.82616 0.121107,1.4365 0.0591,1.5879 0.384786,0.41991 0.61768,0.95508 0.61768,1.61036 0,2.30467 -1.403797,2.81199 -2.739729,2.96044 0.214806,0.18606 0.411641,0.5517401 0.411641,1.1113201 0,0.80274 0,1.44923 0,1.64647 0,0.15967 0.09615,0.34669 0.400369,0.28812 C 11.283214,12.046534 13,9.7984539 13,7.1480839 c 0,-3.31348 -2.686544,-6.00003 -6,-6.00003 z"/></svg>
|
||||
<svg class="social-github" role="img" focusable="false" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 14 14" fill="#ffffff"><path d="m 7,1.1480539 c -3.313515,0 -6,2.68652 -6,6 0,2.65087 1.719175,4.9003701 4.103505,5.6933601 C 5.403301,12.897114 5.5,12.711544 5.5,12.552834 l 0,-1.11716 C 3.831039,11.798964 3.483359,10.728164 3.483359,10.728164 3.210913,10.034324 2.817388,9.8497439 2.817388,9.8497439 c -0.544456,-0.37205 0.04101,-0.36474 0.04101,-0.36474 0.602563,0.0425 0.919922,0.6186401 0.919922,0.6186401 0.535136,0.91698 1.403301,0.65187 1.746088,0.49855 0.05371,-0.38773 0.208951,-0.6523401 0.380854,-0.8022501 -1.333048,-0.15137 -2.733437,-0.66603 -2.733437,-2.96534 0,-0.65528 0.234379,-1.19045 0.618204,-1.61036 -0.0625,-0.15137 -0.267612,-0.76171 0.05764,-1.5879 0,0 0.503913,-0.16113 1.650349,0.61523 0.478981,-0.13282 0.992214,-0.19969 1.501981,-0.20214 0.509767,0.002 1.023903,0.0693 1.502913,0.20214 1.145534,-0.77636 1.648427,-0.61523 1.648427,-0.61523 0.326709,0.82616 0.121107,1.4365 0.0591,1.5879 0.384786,0.41991 0.61768,0.95508 0.61768,1.61036 0,2.30467 -1.403797,2.81199 -2.739729,2.96044 0.214806,0.18606 0.411641,0.5517401 0.411641,1.1113201 0,0.80274 0,1.44923 0,1.64647 0,0.15967 0.09615,0.34669 0.400369,0.28812 C 11.283214,12.046534 13,9.7984539 13,7.1480839 c 0,-3.31348 -2.686544,-6.00003 -6,-6.00003 z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
1
renderer/assets/icons/nexus-mods.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 24 24" fill="#ffffff"><path fill="#ffffff" d="M17.376 0c-.993 0-2.18.686-2.907 1.182c-1.676-.36-4.036-.545-6.787.635c-1.365-.513-2.425-.562-3.32-.488a2.16 2.16 0 0 0-1.27.429c-.33.22-2.788 2.69-3.069 4.652C-.15 7.508.68 8.932 1.218 9.718c-.44 1.76-.2 4.572.517 6.188c-.353 1.041-.713 2.089-.664 3.205c.01.584.061 1.188.398 1.684C1.72 21.19 4.528 24 6.545 24c.957 0 1.93-.428 3.07-1.24c2.16.383 4.402.348 6.448-.532c2.573 1.001 4.224.625 4.84.162c.587-.457 2.826-2.915 3.07-4.622c.1-.672-.023-1.638-1.226-3.397a11 11 0 0 0-.501-6.455c.396-1.069.673-2.188.59-3.337c-.015-.68-.221-1.167-.487-1.507c-.209-.335-2.415-2.39-4.028-2.91A3.1 3.1 0 0 0 17.376 0m-.03 2.082c.65.015 2.155 1.093 3.01 1.906l.355.34c-.959-.163-2.125.428-3.26 1.55a10.3 10.3 0 0 0-1.358 1.595c-.28.384-.517.768-.753 1.285l1.18.635l-3.895 1.477l-1.122-4.18l1.033.547c1.358-3.102 2.524-3.973 3.232-4.416h.015a5.1 5.1 0 0 1 1.49-.724zM12 3.065a9 9 0 0 1 2.22.279a8 8 0 0 0-.42.488a8.4 8.4 0 0 0-1.8-.196a8.34 8.34 0 0 0-5.897 2.432a8 8 0 0 1-.37-.433A8.9 8.9 0 0 1 12 3.065m-7.076.305c.71-.002 1.309.127 2.2.466a9.5 9.5 0 0 0-1.713 1.337c-.327-.542-.624-1.156-.488-1.803m-.606.042c-.162.96.428 2.126 1.55 3.264c.457.487 1.003.945 1.594 1.358c.383.281.767.517 1.283.754l.62-1.182l1.49 3.914l-4.176 1.122l.546-1.033c-3.099-1.36-3.969-2.526-4.412-3.235v-.015a5.1 5.1 0 0 1-.723-1.491l-.015-.074c.015-.65 1.092-2.156 1.904-3.013Zm16.035 1.483a1.3 1.3 0 0 1 .26.015l.14.023a5 5 0 0 1-.13 1.137v.015q-.152.574-.377 1.148a9.5 9.5 0 0 0-1.346-1.776c.547-.357 1.051-.546 1.453-.562M18.43 5.8a8.9 8.9 0 0 1 2.506 6.2a9 9 0 0 1-.27 2.183a8 8 0 0 0-.488-.425A8.4 8.4 0 0 0 20.364 12A8.33 8.33 0 0 0 18 6.173a8 8 0 0 1 .429-.373M3.315 9.905q.235.222.488.425A8.4 8.4 0 0 0 3.636 12c0 2.248.887 4.286 2.327 5.788a8 8 0 0 1-.426.376A8.9 8.9 0 0 1 3.065 12a9 9 0 0 1 .25-2.095m13.988 1.541l-.546 1.034c3.098 1.359 3.969 2.526 4.412 3.235v.014c.34.488.575.99.723 1.492l.014.074c-.014.65-1.092 2.156-1.903 3.013l-.34.354c.163-.96-.427-2.127-1.549-3.264a10.3 10.3 0 0 0-1.594-1.359a7 7 0 0 0-1.283-.753l-.605 1.152l-1.505-3.87zm-6.006 1.684l1.121 4.18l-1.033-.547c-1.357 3.102-2.523 3.973-3.231 4.416h-.015c-.487.34-.989.576-1.49.724l-.074.015c-.65-.015-2.154-1.093-3.01-1.906l-.354-.34c.959.163 2.124-.428 3.26-1.55c.488-.458.945-1.004 1.358-1.595c.28-.384.517-.768.753-1.285l-1.166-.635ZM3.72 16.663A9.5 9.5 0 0 0 5.086 18.5c-.697.47-1.33.665-1.777.59l-.138-.024c0-.367.038-.748.128-1.137v-.015c.11-.417.254-.835.42-1.252m14.131 1.314q.194.21.372.43A8.9 8.9 0 0 1 12 20.936a9 9 0 0 1-2.282-.296a8 8 0 0 0 .417-.487a8.34 8.34 0 0 0 7.716-2.175m.696.889c.43.666.607 1.267.534 1.698l-.023.138a5 5 0 0 1-1.136-.128h-.014a11 11 0 0 1-1.114-.366a9.5 9.5 0 0 0 1.753-1.342"/></svg>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
26
renderer/assets/icons/plus.svg
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="244"
|
||||
height="244"
|
||||
viewBox="0 0 64.558333 64.558333"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<g
|
||||
id="layer1"
|
||||
transform="translate(-1.5875,-1.5875005)">
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:6.35;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 62.970833,33.866667 H 4.7625"
|
||||
id="path1" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:6.35;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 33.866666,62.970833 V 4.7625005"
|
||||
id="path2" />
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 838 B |
@@ -3,7 +3,8 @@
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#ffffff">
|
||||
<path
|
||||
d="m 8.8752116,12.181305 v -0.845421 c 0.3824271,-0.154001 0.726847,-0.36571 1.0522956,-0.605995 l 0.7732288,0.43071 c 0.129524,0.07348 0.274423,0.111383 0.423312,0.111383 0.07294,0 0.14588,-0.0105 0.216596,-0.02797 0.216594,-0.05726 0.40349,-0.194791 0.514241,-0.381738 L 12.886967,9.137646 C 13.120376,8.7467822 12.980643,8.2446393 12.57519,8.0191521 L 11.798888,7.5845715 c 0.02336,-0.1937454 0.05915,-0.3841963 0.05915,-0.58406 0,-0.200282 -0.03578,-0.3919096 -0.05915,-0.5854981 L 12.57519,5.9817665 C 12.769673,5.8719503 12.910975,5.6936322 12.97129,5.4831529 13.02958,5.2718895 12.99661,5.0497752 12.88697,4.8614422 L 11.854885,3.1377619 C 11.699911,2.8751729 11.41463,2.7284127 11.122019,2.7284127 c -0.144899,0 -0.288488,0.034775 -0.421219,0.1098161 L 9.9275724,3.2698799 C 9.6021235,3.0301169 9.256657,2.8177291 8.8752766,2.6630465 V 1.8201103 C 8.8752766,1.3669134 8.4961856,1 8.0281931,1 H 5.9710286 C 5.5019897,1 5.1238146,1.3669134 5.1238146,1.8201103 V 2.6630465 C 4.7434808,2.8178336 4.3959207,3.0302213 4.0729577,3.2698799 L 3.2988145,2.8372867 C 3.1697472,2.7645994 3.0251103,2.7282557 2.8783147,2.7282557 c -0.073855,0 -0.1488238,0.010461 -0.2218277,0.027978 C 2.4398921,2.8119249 2.256332,2.9484095 2.1422449,3.1364026 L 1.1123216,4.8612069 c -0.23151155,0.3921709 -0.09407,0.8935296 0.3104686,1.1203242 l 0.7773489,0.433247 c -0.023163,0.193484 -0.059268,0.3851637 -0.059268,0.5855242 0,0.1997591 0.036177,0.39021 0.059268,0.583354 L 1.4227902,8.0174787 C 1.0182522,8.2431227 0.88081119,8.7454488 1.1123216,9.1373584 L 2.1421142,10.86198 c 0.1140877,0.186948 0.2977122,0.32453 0.5142421,0.381738 0.071957,0.0183 0.1458801,0.02797 0.218622,0.02797 0.149086,0 0.2937219,-0.03791 0.4237052,-0.111384 l 0.7722463,-0.430711 c 0.3249255,0.240286 0.6724201,0.452176 1.0528194,0.606022 v 0.84542 C 5.1237492,12.632693 5.5019241,13 5.9709632,13 h 2.0571645 c 0.4679925,1.83e-4 0.8470835,-0.367176 0.8470835,-0.818855 z M 4.663215,7.0004593 c 0,-1.2491741 1.0468009,-2.2607289 2.334205,-2.2607289 1.2928991,0 2.3395692,1.0114764 2.3395692,2.2607289 0,1.2470298 -1.0466701,2.2585586 -2.3395692,2.2585586 C 5.7100159,9.2589655 4.663215,8.2474891 4.663215,7.0004593 Z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
@@ -3,7 +3,8 @@
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#ffffff">
|
||||
<path
|
||||
d="M 5.75,4.5454543 C 5.75,4.6562497 5.71289,4.7521362 5.6386718,4.8331133 5.5644534,4.9140684 5.4765624,4.9545458 5.375,4.9545458 H 4.625 V 11.5 h -1.5 V 4.9545458 h -0.75 c -0.1015625,0 -0.1894532,-0.040477 -0.2636719,-0.1214325 C 2.0371098,4.7521364 2,4.6562497 2,4.5454543 2,4.434659 2.03711,4.3387842 2.1113281,4.2578297 l 1.5,-1.6363638 C 3.6855468,2.5404886 3.7734374,2.5 3.8749999,2.5 c 0.1015625,0 0.1894532,0.04049 0.2636719,0.1214659 l 1.5,1.6363638 C 5.7128911,4.3387838 5.75,4.434659 5.75,4.5454543 Z" />
|
||||
<path
|
||||
|
||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
@@ -3,7 +3,8 @@
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#ffffff">
|
||||
<path
|
||||
d="M 6,3.7272726 C 6,3.8749998 5.9505205,4.0028484 5.8515624,4.1108179 5.7526046,4.2187579 5.6354166,4.2727279 5.5,4.2727279 H 4.4999999 V 13 H 2.5 V 4.2727279 h -1 c -0.1354167,0 -0.2526042,-0.053969 -0.3515625,-0.16191 C 1.0494797,4.0028487 1,3.8749998 1,3.7272726 1,3.5795455 1.0494795,3.4517124 1.1484375,3.3437731 l 2,-2.1818186 C 3.2473957,1.0539848 3.3645832,1 3.4999998,1 3.6354166,1 3.7526041,1.053986 3.8515624,1.1619545 l 2,2.1818186 C 5.9505213,3.4517119 6,3.5795455 6,3.7272726 Z" />
|
||||
<path
|
||||
|
||||
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -3,7 +3,8 @@
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#ffffff">
|
||||
<path
|
||||
d="m 10.781355,12.15887 v -0.352258 c 0.159345,-0.06416 0.302854,-0.152379 0.438458,-0.252498 l 0.32218,0.179463 c 0.05396,0.03062 0.114338,0.0464 0.176383,0.0464 0.03039,0 0.06079,-0.0044 0.09023,-0.01163 0.09026,-0.02386 0.168121,-0.08116 0.214269,-0.159057 l 0.430036,-0.718594 c 0.09725,-0.16286 0.03902,-0.372086 -0.129909,-0.466039 l -0.323454,-0.181075 c 0.0098,-0.08074 0.02464,-0.160081 0.02464,-0.243358 0,-0.08345 -0.01491,-0.1632952 -0.02464,-0.2439564 L 12.323,9.5757277 c 0.08104,-0.04575 0.139914,-0.1200624 0.165044,-0.2077546 0.02429,-0.08803 0.0105,-0.180574 -0.03513,-0.2590459 L 12.022891,8.3907278 c -0.06457,-0.1094124 -0.18344,-0.1705615 -0.305364,-0.1705615 -0.06038,0 -0.1202,0.01449 -0.175508,0.045756 L 11.21984,8.4457815 C 11.084235,8.3458802 10.940291,8.257386 10.781381,8.192935 V 7.8417121 C 10.781381,7.6528803 10.623426,7.5 10.428427,7.5 H 9.5712702 C 9.375837,7.5 9.2182616,7.6528803 9.2182616,7.8417121 V 8.192935 C 9.0597901,8.2574296 8.9149709,8.3459242 8.7804031,8.4457815 L 8.4578409,8.265535 C 8.4040669,8.235247 8.3438034,8.220105 8.2826318,8.220105 c -0.030776,0 -0.062013,0.00436 -0.092425,0.011662 -0.090237,0.023205 -0.166733,0.080074 -0.2142681,0.1584039 L 7.5468008,9.1088332 c -0.096463,0.1634049 -0.039188,0.3723033 0.1293616,0.466802 l 0.3238982,0.1805187 c -0.00962,0.080625 -0.024687,0.1604849 -0.024687,0.2439671 0,0.08324 0.015076,0.162588 0.024687,0.243065 l -0.3238977,0.180759 c -0.168558,0.09401 -0.2258256,0.30332 -0.1293616,0.466615 l 0.4290822,0.718592 c 0.047526,0.0779 0.1240501,0.13522 0.2142681,0.159057 0.029976,0.0076 0.060788,0.01162 0.091088,0.01162 0.062124,0 0.1223876,-0.0158 0.1765454,-0.04641 l 0.3217708,-0.179464 c 0.1353866,0.100125 0.280177,0.188406 0.4386774,0.25251 v 0.352258 c 0,0.18823 0.1575741,0.341274 0.3530085,0.341274 h 0.8571563 c 0.194997,7.6e-5 0.352954,-0.15299 0.352954,-0.341189 z m -1.75501,-2.158683 c 0,-0.520488 0.4361698,-0.9419688 0.9725912,-0.9419688 0.5387118,0 0.9748248,0.4214483 0.9748248,0.9419688 0,0.519595 -0.436113,0.941065 -0.9748248,0.941065 C 9.4625148,10.94123 9.026345,10.519782 9.026345,10.000187 Z" />
|
||||
<path
|
||||
|
||||
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
@@ -3,7 +3,8 @@
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
viewBox="0 0 14 14"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="#ffffff">
|
||||
<path
|
||||
d="m 2.1464844,1.0019531 c -0.1349019,-0.006832 -0.2388263,0.00771 -0.3085938,0.044922 -1.11627905,0.5953992 -1.11627905,11.3108509 0,11.9062499 C 2.9541697,13.548524 13,8.1907983 13,7 13,5.8836266 4.1700127,1.1044327 2.1464844,1.0019531 Z m 0.7617187,1.25 C 4.5101631,2.3330828 11.5,6.1162044 11.5,7 c 0,0.9427153 -7.9541697,5.184248 -8.8378906,4.712891 -0.883721,-0.471358 -0.883721,-8.954424 0,-9.4257816 0.055233,-0.02946 0.1392964,-0.040565 0.2460937,-0.035156 z" />
|
||||
</svg>
|
||||
|
||||
|
Before Width: | Height: | Size: 616 B After Width: | Height: | Size: 634 B |
1
renderer/assets/icons/thunderstore.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="256" height="256" viewBox="0 0 24 24" fill="#ffffff"><path fill="#ffffff" d="m.322 13.174l4.706 8.192L7.2 16.855L4.824 12.72a1.416 1.416 0 0 1 0-1.444l2.965-5.16c.265-.46.718-.723 1.245-.723h1.595l-3.086 6.953h3.812L6.171 22.403L16.583 9.914h-3.201l2.184-4.52h6.052L24 1.25H7.175c-.86 0-1.598.428-2.028 1.174l-4.825 8.4a2.306 2.306 0 0 0 0 2.35m7.213 9.576h9.29a2.29 2.29 0 0 0 2.03-1.176l4.825-8.4a2.317 2.317 0 0 0 0-2.35l-1.93-3.36h-4.763l2.19 3.813c.262.46.262.987 0 1.444l-2.964 5.162a1.41 1.41 0 0 1-1.248.723h-2.154l-1.497-.017z"/></svg>
|
||||
|
After Width: | Height: | Size: 591 B |
@@ -3,7 +3,7 @@
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Silk Fly Launcher</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self' https://*.nexusmods.com" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline'; img-src 'self' https://*.nexusmods.com https://*.thunderstore.io" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
@@ -17,15 +17,16 @@
|
||||
<h5 class="logo-title">Silk Fly Launcher</h5>
|
||||
</div>
|
||||
|
||||
<div class="sidebar-content">
|
||||
<nav class="nav">
|
||||
<div class="nav-section">
|
||||
<span class="nav-title">Execute Silksong</span>
|
||||
<button class="horizontal-div" onclick="launch('vanilla')">
|
||||
<img src="assets/icons/start-vanilla.svg" class="icons invert-color" />
|
||||
<img src="assets/icons/start-vanilla.svg" class="icons" />
|
||||
Run Vanilla
|
||||
</button>
|
||||
<button class="horizontal-div" onclick="launch('modded')">
|
||||
<img src="assets/icons/start-modded.svg" class="icons invert-color" />
|
||||
<img src="assets/icons/start-modded.svg" class="icons" />
|
||||
Run Modded
|
||||
</button>
|
||||
</div>
|
||||
@@ -33,27 +34,35 @@
|
||||
<div class="nav-section">
|
||||
<span class="nav-title">Mods</span>
|
||||
<button class="horizontal-div" onclick="navigate('mods-installed')">
|
||||
<img src="assets/icons/folder.svg" class="icons invert-color" />
|
||||
<img src="assets/icons/folder.svg" class="icons" />
|
||||
Installed
|
||||
</button>
|
||||
<button class="horizontal-div" onclick="navigate('mods-online')">
|
||||
<img src="assets/icons/cloud.svg" class="icons invert-color" />
|
||||
Online
|
||||
<button class="horizontal-div" onclick="navigate('mods-thunderstore')">
|
||||
<img src="assets/icons/thunderstore.svg" class="icons" />
|
||||
Thunderstore
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="nav-section">
|
||||
<span class="nav-title">Settings</span>
|
||||
<button class="horizontal-div" onclick="navigate('general-settings')">
|
||||
<img src="assets/icons/settings.svg" class="icons invert-color" />
|
||||
General
|
||||
<button class="horizontal-div" onclick="navigate('mods-online')">
|
||||
<img src="assets/icons/nexus-mods.svg" class="icons" />
|
||||
Nexus
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<nav class="nav">
|
||||
<button class="horizontal-div" onclick="navigate('general-settings')">
|
||||
<img src="assets/icons/settings.svg" class="icons" />
|
||||
Settings
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- Main content -->
|
||||
<main class="content">
|
||||
<div class="banner-div" id="banner-div">
|
||||
<p id="banner-text"></p>
|
||||
<button class="default-button square-button" onclick="hideBanner()">X</button>
|
||||
</div>
|
||||
<h1 id="title">Silk Fly Launcher</h1>
|
||||
<div class="view" id="view"></div>
|
||||
<div class="toast-div" id="toast-div"></div>
|
||||
@@ -72,12 +81,12 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="horizontal-div">
|
||||
<img onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar/Silk-Fly-Launcher')" src="assets/icons/github.svg" alt="Github logo" class="logo-img invert-color" />
|
||||
<img onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar/Silk-Fly-Launcher')" src="assets/icons/github.svg" alt="Github logo" class="logo-img" />
|
||||
</div>
|
||||
</div>
|
||||
<br />
|
||||
<ul>
|
||||
<li>Silk Fly Launcher is a launcher and mod manager for Silksong mods from Nexus, built with Electron.</li>
|
||||
<li>Silk Fly Launcher is a launcher and mod manager for Silksong mods from Nexus and Thunderstore, built with Electron.</li>
|
||||
<li>This product is licensed under the <a href="" class="link" onclick="electronAPI.openWindow('LICENSE')">GNU General Public License Version 3</a>.</li>
|
||||
<li>This product uses third-party modules or assets under <a href="" class="link" onclick="electronAPI.openWindow('THIRD-PARTY-LICENSES')">third-party licenses</a>.</li>
|
||||
<li>
|
||||
@@ -107,7 +116,8 @@
|
||||
<li id="size" onclick="changeSort('size')">by size</li>
|
||||
</div>
|
||||
</div>
|
||||
<button class="default-button square-button" onclick="inverseSort()"><img class="icons invert-color" id="sort-order-image" src="assets/icons/sort-order-1.svg" /></button>
|
||||
<button class="default-button square-button" onclick="inverseSort()"><img class="icons" id="sort-order-image" src="assets/icons/sort-order-1.svg" /></button>
|
||||
<button class="default-button square-button" onclick="addOfflineMod()"><img class="icons" src="assets/icons/plus.svg" /></button>
|
||||
</div>
|
||||
<div class="mods-container" id="mods-container"></div>
|
||||
<div class="separated-div">
|
||||
@@ -122,7 +132,7 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="online-mods-template">
|
||||
<template id="nexus-mods-template">
|
||||
<h2>List Of Nexus Mods</h2>
|
||||
<div class="horizontal-div">
|
||||
<form class="horizontal-div input-form" id="search-form">
|
||||
@@ -140,7 +150,40 @@
|
||||
<li id="size" onclick="changeSort('size')">by size</li>
|
||||
</div>
|
||||
</div>
|
||||
<button class="default-button square-button" onclick="inverseSort()"><img class="icons invert-color" id="sort-order-image" src="assets/icons/sort-order-1.svg" /></button>
|
||||
<button class="default-button square-button" onclick="inverseSort()"><img class="icons" id="sort-order-image" src="assets/icons/sort-order-1.svg" /></button>
|
||||
</div>
|
||||
<div class="mods-container" id="mods-container"></div>
|
||||
<div class="separated-div">
|
||||
<div class="horizontal-div">
|
||||
<button class="default-button" onclick="changeModsPage('min')">First page</button>
|
||||
<button class="default-button" onclick="changeModsPage(-1)">Previous</button>
|
||||
</div>
|
||||
<div class="horizontal-div">
|
||||
<button class="default-button" onclick="changeModsPage(1)">Next</button>
|
||||
<button class="default-button" onclick="changeModsPage('max')">Last page</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="thunderstore-mods-template">
|
||||
<h2>List Of Thunderstore Mods</h2>
|
||||
<div class="horizontal-div">
|
||||
<form class="horizontal-div input-form" id="search-form">
|
||||
<input class="input" id="search-input" type="text" placeholder="Search For Mods..." />
|
||||
<button class="default-button" onclick="searchThunderstoreMods()">Search</button>
|
||||
</form>
|
||||
<div class="list-div">
|
||||
<div class="default-button smaller-button" id="sort-button" onclick="toggleSortMenu()">Sort</div>
|
||||
<div class="list-menu longer-button list-menu-inverted" id="sort-menu">
|
||||
<li id="name" onclick="changeSort('name')">by name</li>
|
||||
<li id="downloads" onclick="changeSort('downloads')">by downloads count</li>
|
||||
<li id="endorsements" onclick="changeSort('endorsements')">by rating count</li>
|
||||
<li id="createdAt" onclick="changeSort('createdAt')">by date of creation</li>
|
||||
<li id="updatedAt" onclick="changeSort('updatedAt')">by date of updating</li>
|
||||
<li id="size" onclick="changeSort('size')">by size</li>
|
||||
</div>
|
||||
</div>
|
||||
<button class="default-button square-button" onclick="inverseSort()"><img class="icons" id="sort-order-image" src="assets/icons/sort-order-1.svg" /></button>
|
||||
</div>
|
||||
<div class="mods-container" id="mods-container"></div>
|
||||
<div class="separated-div">
|
||||
@@ -161,15 +204,21 @@
|
||||
<div class="horizontal-div">
|
||||
<h3 id="mod-title">Unknown Title</h3>
|
||||
<p id="mod-author">Unknown author</p>
|
||||
<p id="mod-endorsements-number">? likes</p>
|
||||
<p id="mod-downloads-number">? download</p>
|
||||
<p id="mod-endorsements-number"></p>
|
||||
<p id="mod-downloads-number"></p>
|
||||
<p id="mod-source"></p>
|
||||
</div>
|
||||
<p id="mod-description" class="long-text">No description provided</p>
|
||||
<p class="transparent-text" id="mod-version">V1.0.0 last update on 01/01/2026</p>
|
||||
<p class="transparent-text" id="mod-version"></p>
|
||||
<br />
|
||||
<div class="horizontal-div">
|
||||
<a href="www.nexusmods.com/hollowknightsilksong/mods" class="default-button" id="uninstall-mod-button">Uninstall</a>
|
||||
<a href="www.nexusmods.com/hollowknightsilksong/mods" class="default-button" id="external-link">Website</a>
|
||||
<a href="" class="default-button" id="uninstall-mod-button">Uninstall</a>
|
||||
<a href="" class="default-button" id="external-link">Website</a>
|
||||
<p>Activated:</p>
|
||||
<label class="checkbox-container">
|
||||
<input type="checkbox" name="activated-mod" id="activated-mod" />
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -221,7 +270,7 @@
|
||||
<li id="Steel" onclick="changeTheme('Steel')">Steel</li>
|
||||
</div>
|
||||
</div>
|
||||
<label class="lace-pin-checkbox-container">
|
||||
<label class="checkbox-container">
|
||||
<input type="checkbox" name="lace-pin" id="lace-pin" />
|
||||
<span class="checkmark"></span>
|
||||
Lace Pin
|
||||
@@ -272,6 +321,20 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="settings-linux-template">
|
||||
<br />
|
||||
<h2>Linux Additional Settings</h2>
|
||||
<div class="horizontal-div">
|
||||
<h3>Do you use Steam ?</h3>
|
||||
<label class="checkbox-container">
|
||||
<input type="checkbox" name="linux-steam" id="linux-steam" />
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
<br />
|
||||
<p class="transparent-text">The game will crash if you don’t enable this setting</p>
|
||||
</template>
|
||||
|
||||
<script src="renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -4,8 +4,10 @@ const view = document.getElementById("view");
|
||||
|
||||
const HomeTemplate = document.getElementById("home-template");
|
||||
const installedModsTemplate = document.getElementById("installed-mods-template");
|
||||
const onlineModsTemplate = document.getElementById("online-mods-template");
|
||||
const nexusModsTemplate = document.getElementById("nexus-mods-template");
|
||||
const thunderstoreModsTemplate = document.getElementById("thunderstore-mods-template");
|
||||
const settingsTemplate = document.getElementById("settings-template");
|
||||
const linuxSettingsTemplate = document.getElementById("settings-linux-template");
|
||||
const installedModTemplate = document.getElementById("installed-mod-template");
|
||||
const modTemplate = document.getElementById("mod-template");
|
||||
|
||||
@@ -14,18 +16,31 @@ let actualTheme = [];
|
||||
|
||||
let searchValueNexus = "";
|
||||
let searchValueInstalled = "";
|
||||
let searchValueThunderstore = "";
|
||||
|
||||
let onlineSortFilter = "downloads";
|
||||
let installedSortFilter = "name";
|
||||
let thunderstoreSortFilter = "downloads";
|
||||
|
||||
let onlineSortOrder = "DESC";
|
||||
let installedSortOrder = "ASC";
|
||||
let thunderstoreSortOrder = "DESC";
|
||||
|
||||
let onlineOffset = 0;
|
||||
let installedOffset = 0;
|
||||
let thunderstoreOffset = 0;
|
||||
|
||||
let lastOnlineOffset = 0;
|
||||
let lastInstalledOffset = 0;
|
||||
let lastThunderstoreOffset = 0;
|
||||
|
||||
let onlineModsCount = 10;
|
||||
let installedModsCount = 10;
|
||||
let thunderstoreModsCount = 10;
|
||||
|
||||
let onlineModsTotalCount;
|
||||
let installedModsTotalCount;
|
||||
let thunderstoreModsTotalCount;
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
///////////////// CONST FOR WELCOME //////////////////
|
||||
@@ -42,7 +57,7 @@ const welcomeTemplate = document.getElementById("welcome-template");
|
||||
const silksongPathTemplate = document.getElementById("path-template");
|
||||
const nexusTemplate = document.getElementById("nexus-template");
|
||||
const styleTemplate = document.getElementById("style-template");
|
||||
const tutorialTemplate = document.getElementById("tutorial-template");
|
||||
const linuxSettingsWelcomeTemplate = document.getElementById("settings-linux-template");
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
////////////////////// STARTUP ///////////////////////
|
||||
@@ -98,14 +113,15 @@ async function navigate(page) {
|
||||
toggleSelectedListButton("sort-menu", installedSortFilter);
|
||||
setSortOrderButton();
|
||||
|
||||
const { modsInfo, installedTotalCount } = await nexus.getMods(page);
|
||||
const { installedModsInfo, installedTotalCount } = await mods.getMods(page);
|
||||
installedModsTotalCount = installedTotalCount;
|
||||
if (modsInfo == []) {
|
||||
if (installedModsInfo == []) {
|
||||
break;
|
||||
}
|
||||
|
||||
for (const modInfo of modsInfo) {
|
||||
for (const modInfo of installedModsInfo) {
|
||||
const installedModTemplateCopy = installedModTemplate.content.cloneNode(true);
|
||||
|
||||
if (modInfo.name) {
|
||||
const modTitleText = installedModTemplateCopy.getElementById("mod-title");
|
||||
modTitleText.innerText = modInfo.name;
|
||||
@@ -138,25 +154,58 @@ async function navigate(page) {
|
||||
const modPicture = installedModTemplateCopy.getElementById("mod-icon");
|
||||
modPicture.src = modInfo.pictureUrl;
|
||||
}
|
||||
if (modInfo.version && modInfo.updatedAt) {
|
||||
if (modInfo.version || modInfo.updatedAt) {
|
||||
let text = "";
|
||||
if (modInfo.version) {
|
||||
text = text.concat(`V${modInfo.version} `);
|
||||
}
|
||||
if (modInfo.updatedAt) {
|
||||
text = text.concat(`last updated on ${modInfo.updatedAt.slice(0, 10)}`);
|
||||
}
|
||||
const modVersionText = installedModTemplateCopy.getElementById("mod-version");
|
||||
modVersionText.innerText = `V${modInfo.version} last updated on ${modInfo.updatedAt.slice(0, 10)}`;
|
||||
modVersionText.innerText = text;
|
||||
}
|
||||
|
||||
const modUrl = `https://www.nexusmods.com/hollowknightsilksong/mods/${modInfo.modId}`;
|
||||
const isActivatedCheckbox = installedModTemplateCopy.getElementById("activated-mod");
|
||||
if (modInfo.activated) {
|
||||
isActivatedCheckbox.checked = true;
|
||||
}
|
||||
|
||||
isActivatedCheckbox.addEventListener("change", async function () {
|
||||
if (this.checked) {
|
||||
mods.activateMods(modInfo.modId);
|
||||
searchInstalledMods();
|
||||
} else {
|
||||
mods.deactivateMods(modInfo.modId);
|
||||
searchInstalledMods();
|
||||
}
|
||||
});
|
||||
|
||||
if (modInfo.source && modInfo.source != "local") {
|
||||
const modSource = installedModTemplateCopy.getElementById("mod-source");
|
||||
modSource.innerText = `From ${modInfo.source}`;
|
||||
|
||||
const modUrls = {
|
||||
nexusmods: `https://www.nexusmods.com/hollowknightsilksong/mods/${modInfo.modId}`,
|
||||
thunderstore: `https://new.thunderstore.io/c/hollow-knight-silksong/p/${modInfo.author}/${modInfo.name}`,
|
||||
};
|
||||
|
||||
const modLinkButton = installedModTemplateCopy.getElementById("external-link");
|
||||
modLinkButton.href = modUrl;
|
||||
modLinkButton.href = modUrls[modInfo.source];
|
||||
modLinkButton.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
const modLink = modLinkButton.href;
|
||||
electronAPI.openExternalLink(modLink);
|
||||
});
|
||||
} else {
|
||||
const modLinkButton = installedModTemplateCopy.getElementById("external-link");
|
||||
modLinkButton.remove();
|
||||
}
|
||||
|
||||
const uninstallModButton = installedModTemplateCopy.getElementById("uninstall-mod-button");
|
||||
uninstallModButton.addEventListener("click", async function (event) {
|
||||
event.preventDefault();
|
||||
await nexus.uninstall(modInfo.modId);
|
||||
await mods.uninstall(modInfo.modId);
|
||||
|
||||
navigate("refresh");
|
||||
});
|
||||
@@ -167,8 +216,8 @@ async function navigate(page) {
|
||||
break;
|
||||
|
||||
case "mods-online":
|
||||
title.innerText = "Online Mods";
|
||||
const onlineModsTemplateCopy = onlineModsTemplate.content.cloneNode(true);
|
||||
title.innerText = "Nexus Mods";
|
||||
const onlineModsTemplateCopy = nexusModsTemplate.content.cloneNode(true);
|
||||
const ModsContainer = onlineModsTemplateCopy.getElementById("mods-container");
|
||||
const searchFormNexus = onlineModsTemplateCopy.getElementById("search-form");
|
||||
const searchInputNexus = onlineModsTemplateCopy.getElementById("search-input");
|
||||
@@ -182,12 +231,12 @@ async function navigate(page) {
|
||||
toggleSelectedListButton("sort-menu", onlineSortFilter);
|
||||
setSortOrderButton();
|
||||
|
||||
const { mods, onlineTotalCount } = await nexus.getMods(page);
|
||||
const { onlineModsInfo, onlineTotalCount } = await mods.getMods(page);
|
||||
onlineModsTotalCount = onlineTotalCount;
|
||||
if (mods == undefined) {
|
||||
if (onlineModsInfo == undefined) {
|
||||
break;
|
||||
}
|
||||
for (const mod of mods) {
|
||||
for (const mod of onlineModsInfo) {
|
||||
if (mod.name == undefined) {
|
||||
continue;
|
||||
}
|
||||
@@ -239,7 +288,7 @@ async function navigate(page) {
|
||||
electronAPI.openExternalLink(modLink);
|
||||
});
|
||||
|
||||
modDownloadButton = modTemplateCopy.getElementById("download-mod-button");
|
||||
const modDownloadButton = modTemplateCopy.getElementById("download-mod-button");
|
||||
modDownloadButton.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
const modDownloadLink = `${modUrl}?tab=files`;
|
||||
@@ -250,9 +299,93 @@ async function navigate(page) {
|
||||
}
|
||||
break;
|
||||
|
||||
case "mods-thunderstore":
|
||||
title.innerText = "Thunderstore Mods";
|
||||
const thunderstoreModsTemplateCopy = thunderstoreModsTemplate.content.cloneNode(true);
|
||||
const thunderstoreModsContainer = thunderstoreModsTemplateCopy.getElementById("mods-container");
|
||||
const searchFormThunderstore = thunderstoreModsTemplateCopy.getElementById("search-form");
|
||||
const searchInputThunderstore = thunderstoreModsTemplateCopy.getElementById("search-input");
|
||||
|
||||
searchFormThunderstore.addEventListener("submit", async function (event) {
|
||||
event.preventDefault();
|
||||
});
|
||||
searchInputThunderstore.value = searchValueThunderstore;
|
||||
|
||||
view.appendChild(thunderstoreModsTemplateCopy);
|
||||
toggleSelectedListButton("sort-menu", thunderstoreSortFilter);
|
||||
setSortOrderButton();
|
||||
|
||||
const { thunderstoreModsInfo, thunderstoreTotalCount } = await mods.getMods(page);
|
||||
thunderstoreModsTotalCount = thunderstoreTotalCount;
|
||||
if (thunderstoreModsInfo == undefined) {
|
||||
break;
|
||||
}
|
||||
for (const mod of thunderstoreModsInfo) {
|
||||
if (mod.name == undefined) {
|
||||
continue;
|
||||
}
|
||||
const modTemplateCopy = modTemplate.content.cloneNode(true);
|
||||
if (mod.name) {
|
||||
const modTitleText = modTemplateCopy.getElementById("mod-title");
|
||||
modTitleText.innerText = mod.name.replaceAll("_", " ");
|
||||
}
|
||||
if (mod.author) {
|
||||
const modAuthorText = modTemplateCopy.getElementById("mod-author");
|
||||
modAuthorText.innerText = `by ${mod.author}`;
|
||||
}
|
||||
if (mod.endorsements) {
|
||||
const modEndorsementsNumber = modTemplateCopy.getElementById("mod-endorsements-number");
|
||||
if (mod.endorsements > 1) {
|
||||
modEndorsementsNumber.innerText = `${mod.endorsements} likes`;
|
||||
} else {
|
||||
modEndorsementsNumber.innerText = `${mod.endorsements} like`;
|
||||
}
|
||||
}
|
||||
if (mod.downloads) {
|
||||
const modDownloadsNumber = modTemplateCopy.getElementById("mod-downloads-number");
|
||||
if (mod.downloads > 1) {
|
||||
modDownloadsNumber.innerText = `${mod.downloads} downloads`;
|
||||
} else {
|
||||
modDownloadsNumber.innerText = `${mod.downloads} download`;
|
||||
}
|
||||
}
|
||||
if (mod.summary) {
|
||||
const modDescriptionText = modTemplateCopy.getElementById("mod-description");
|
||||
modDescriptionText.innerText = mod.summary;
|
||||
}
|
||||
if (mod.pictureUrl) {
|
||||
const modPicture = modTemplateCopy.getElementById("mod-icon");
|
||||
modPicture.src = mod.pictureUrl;
|
||||
}
|
||||
if (mod.version && mod.updatedAt) {
|
||||
const modVersionText = modTemplateCopy.getElementById("mod-version");
|
||||
modVersionText.innerText = `V${mod.version} last updated on ${mod.updatedAt.slice(0, 10)}`;
|
||||
}
|
||||
|
||||
const modUrl = `https://new.thunderstore.io/c/hollow-knight-silksong/p/${mod.author}/${mod.name}`;
|
||||
|
||||
const modLinkButton = modTemplateCopy.getElementById("external-link");
|
||||
modLinkButton.href = modUrl;
|
||||
modLinkButton.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
const modLink = modLinkButton.href;
|
||||
electronAPI.openExternalLink(modLink);
|
||||
});
|
||||
|
||||
const modDownloadButton = modTemplateCopy.getElementById("download-mod-button");
|
||||
modDownloadButton.addEventListener("click", function (event) {
|
||||
event.preventDefault();
|
||||
const modDownloadLink = `https://thunderstore.io/package/download/${mod.author}/${mod.name}/${mod.version}`;
|
||||
thunderstore.download(modDownloadLink, mod.modId);
|
||||
});
|
||||
|
||||
thunderstoreModsContainer.appendChild(modTemplateCopy);
|
||||
}
|
||||
break;
|
||||
case "general-settings":
|
||||
title.innerText = "Settings";
|
||||
const settingsTemplateCopy = settingsTemplate.content.cloneNode(true);
|
||||
const linuxSettingsTemplateCopy = linuxSettingsTemplate.content.cloneNode(true);
|
||||
const silksongPathInput = settingsTemplateCopy.getElementById("silksong-path-input");
|
||||
const nexusAPIForm = settingsTemplateCopy.getElementById("nexus-api-form");
|
||||
const versionsList = settingsTemplateCopy.getElementById("versions-list");
|
||||
@@ -298,6 +431,16 @@ async function navigate(page) {
|
||||
setThemeButton();
|
||||
toggleSelectedListButton("themes-menu", actualTheme[0]);
|
||||
setNexusAPI();
|
||||
if (electronAPI.getOS() == "linux") {
|
||||
const linuxSteamCheckbox = linuxSettingsTemplateCopy.getElementById("linux-steam");
|
||||
|
||||
linuxSteamCheckbox.checked = await files.loadLinuxSteam();
|
||||
linuxSteamCheckbox.addEventListener("change", async function () {
|
||||
files.saveLinuxSteam(this.checked);
|
||||
});
|
||||
|
||||
view.appendChild(linuxSettingsTemplateCopy);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -330,6 +473,11 @@ async function welcomeNavigate() {
|
||||
break;
|
||||
|
||||
case 2:
|
||||
pageDiv.appendChild(styleTemplate.content.cloneNode(true));
|
||||
toggleSelectedListButton("themes-menu", actualTheme[0]);
|
||||
break;
|
||||
|
||||
case 3:
|
||||
pageDiv.appendChild(nexusTemplate.content.cloneNode(true));
|
||||
const nexusLink = document.getElementById("external-link");
|
||||
const nexusAPIForm = document.getElementById("nexus-api-form");
|
||||
@@ -346,15 +494,21 @@ async function welcomeNavigate() {
|
||||
setNexusAPI();
|
||||
break;
|
||||
|
||||
case 3:
|
||||
pageDiv.appendChild(styleTemplate.content.cloneNode(true));
|
||||
toggleSelectedListButton("themes-menu", actualTheme[0]);
|
||||
break;
|
||||
|
||||
case 4:
|
||||
pageDiv.appendChild(tutorialTemplate.content.cloneNode(true));
|
||||
break;
|
||||
if (electronAPI.getOS() == "linux") {
|
||||
const linuxSettingsWelcomeTemplateCopy = linuxSettingsWelcomeTemplate.content.cloneNode(true);
|
||||
const linuxSteamCheckbox = linuxSettingsWelcomeTemplateCopy.getElementById("linux-steam");
|
||||
|
||||
linuxSteamCheckbox.checked = await files.loadLinuxSteam();
|
||||
linuxSteamCheckbox.addEventListener("change", async function () {
|
||||
files.saveLinuxSteam(this.checked);
|
||||
});
|
||||
|
||||
pageDiv.appendChild(linuxSettingsWelcomeTemplateCopy);
|
||||
} else {
|
||||
electronAPI.loadMainPage();
|
||||
}
|
||||
break;
|
||||
case 5:
|
||||
electronAPI.loadMainPage();
|
||||
break;
|
||||
@@ -448,15 +602,15 @@ async function setBepinexVersion() {
|
||||
async function searchInstalledMods() {
|
||||
const searchInput = document.getElementById("search-input");
|
||||
searchValueInstalled = searchInput.value;
|
||||
await nexus.searchInstalled(searchValueInstalled, installedOffset, installedModsCount, installedSortFilter, installedSortOrder);
|
||||
await mods.searchInstalled(searchValueInstalled, installedOffset, installedModsCount, installedSortFilter, installedSortOrder);
|
||||
await navigate("refresh");
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
/////////////////////// NEXUS ////////////////////////
|
||||
//////////////// NEXUS / THUNDERSTORE ////////////////
|
||||
|
||||
async function verifyNexusAPI() {
|
||||
response = await nexus.verifyAPI();
|
||||
const response = await nexus.verifyAPI();
|
||||
|
||||
const nexusCheckImage = document.getElementById("nexus-check-image");
|
||||
if (nexusCheckImage == undefined) {
|
||||
@@ -505,6 +659,20 @@ async function resetNexusAPI() {
|
||||
}
|
||||
}
|
||||
|
||||
async function searchThunderstoreMods() {
|
||||
let searchInput = document.getElementById("search-input");
|
||||
searchValueThunderstore = searchInput.value;
|
||||
await thunderstore.search(searchValueThunderstore, thunderstoreOffset, thunderstoreModsCount, thunderstoreSortFilter, thunderstoreSortOrder);
|
||||
await navigate("refresh");
|
||||
searchInput = document.getElementById("search-input");
|
||||
searchInput.value = searchValueThunderstore;
|
||||
}
|
||||
|
||||
async function addOfflineMod() {
|
||||
await mods.add();
|
||||
searchInstalledMods();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////
|
||||
//////////////// THEMES / SORT / LIST ////////////////
|
||||
|
||||
@@ -583,6 +751,8 @@ function setSortOrderButton() {
|
||||
sortOrder = installedSortOrder;
|
||||
} else if (oldPage == "mods-online") {
|
||||
sortOrder = onlineSortOrder;
|
||||
} else if (oldPage == "mods-thunderstore") {
|
||||
sortOrder = thunderstoreSortOrder;
|
||||
}
|
||||
|
||||
const sortOrderButton = document.getElementById("sort-order-image");
|
||||
@@ -605,6 +775,9 @@ function changeSort(sortFilterParameter) {
|
||||
} else if (oldPage == "mods-online") {
|
||||
onlineSortFilter = sortFilterParameter;
|
||||
searchNexusMods();
|
||||
} else if (oldPage == "mods-thunderstore") {
|
||||
thunderstoreSortFilter = sortFilterParameter;
|
||||
searchThunderstoreMods();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -623,6 +796,13 @@ function inverseSort() {
|
||||
onlineSortOrder = "ASC";
|
||||
}
|
||||
searchNexusMods();
|
||||
} else if (oldPage == "mods-thunderstore") {
|
||||
if (thunderstoreSortOrder == "ASC") {
|
||||
thunderstoreSortOrder = "DESC";
|
||||
} else {
|
||||
thunderstoreSortOrder = "ASC";
|
||||
}
|
||||
searchThunderstoreMods();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -672,6 +852,21 @@ function showToast(message, type = "info", duration = 3000) {
|
||||
|
||||
electronAPI.onShowToast(showToast);
|
||||
|
||||
function showBanner(message) {
|
||||
const bannerDiv = document.getElementById("banner-div");
|
||||
const bannerText = document.getElementById("banner-text");
|
||||
|
||||
bannerText.innerHTML = message;
|
||||
bannerDiv.classList.add("show");
|
||||
}
|
||||
|
||||
electronAPI.onShowBanner(showBanner);
|
||||
|
||||
function hideBanner() {
|
||||
const bannerDiv = document.getElementById("banner-div");
|
||||
bannerDiv.classList.remove("show");
|
||||
}
|
||||
|
||||
function changeModsPage(offsetChange) {
|
||||
if (oldPage == "mods-installed") {
|
||||
if (offsetChange == "min") {
|
||||
@@ -701,6 +896,20 @@ function changeModsPage(offsetChange) {
|
||||
lastOnlineOffset = onlineOffset;
|
||||
searchNexusMods();
|
||||
}
|
||||
} else if (oldPage == "mods-thunderstore") {
|
||||
if (offsetChange == "min") {
|
||||
thunderstoreOffset = 0;
|
||||
} else if (offsetChange == "max") {
|
||||
thunderstoreOffset = thunderstoreModsTotalCount;
|
||||
} else {
|
||||
thunderstoreOffset += thunderstoreModsCount * offsetChange;
|
||||
thunderstoreOffset = clamp(thunderstoreOffset, 0, thunderstoreModsTotalCount);
|
||||
}
|
||||
thunderstoreOffset = Math.floor(thunderstoreOffset / 10) * 10;
|
||||
if (lastThunderstoreOffset != thunderstoreOffset) {
|
||||
lastThunderstoreOffset = thunderstoreOffset;
|
||||
searchThunderstoreMods();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,13 @@ body {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 70px;
|
||||
display: flex;
|
||||
@@ -75,10 +82,6 @@ body {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.invert-color {
|
||||
filter: invert(1);
|
||||
}
|
||||
|
||||
.nav {
|
||||
padding: 20px;
|
||||
}
|
||||
@@ -347,7 +350,7 @@ body {
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container {
|
||||
.checkbox-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
@@ -356,13 +359,13 @@ body {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container input {
|
||||
.checkbox-container input {
|
||||
opacity: 0;
|
||||
height: 0;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container .checkmark {
|
||||
.checkbox-container .checkmark {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
height: 30px;
|
||||
@@ -373,27 +376,27 @@ body {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container:hover .checkmark {
|
||||
.checkbox-container:hover .checkmark {
|
||||
background: var(--darker-transparent-black);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container input:checked ~ .checkmark {
|
||||
.checkbox-container input:checked ~ .checkmark {
|
||||
background: var(--primary-color);
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container .checkmark:after {
|
||||
.checkbox-container .checkmark:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container input:checked ~ .checkmark:after {
|
||||
.checkbox-container input:checked ~ .checkmark:after {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.lace-pin-checkbox-container .checkmark:after {
|
||||
.checkbox-container .checkmark:after {
|
||||
left: 8px;
|
||||
width: 10px;
|
||||
height: 20px;
|
||||
@@ -447,3 +450,19 @@ body {
|
||||
.long-text {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.banner-div {
|
||||
display: none;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border: 1px solid var(--secondary-color);
|
||||
background: var(--primary-color);
|
||||
margin-bottom: 20px;
|
||||
height: 64px;
|
||||
border-radius: 12px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.banner-div.show {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
<template id="welcome-template">
|
||||
<h1 class="title">Welcome to Silk Fly Launcher</h1>
|
||||
<div class="horizontal-div welcome-div">
|
||||
<p>Silk Fly Launcher is a launcher and mod manager for Silksong mods from Nexus, built with Electron.</p>
|
||||
<p>Silk Fly Launcher is a launcher and mod manager for Silksong mods from Nexus and Thunderstore, built with Electron.</p>
|
||||
<p>Made with ♥ by <a href="" class="link" onclick="electronAPI.openExternalLink('https://github.com/Gabi-Zar')">GabiZar</a>.</p>
|
||||
</div>
|
||||
</template>
|
||||
@@ -47,22 +47,6 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="nexus-template">
|
||||
<h1 class="title">Enter your nexus API key</h1>
|
||||
<p>
|
||||
Please enter your nexus API key. To get your API key go to
|
||||
<a href="https://www.nexusmods.com/settings/api-keys" class="link" id="external-link">https://www.nexusmods.com/settings/api-keys</a> and click on new personnal API key
|
||||
</p>
|
||||
<div class="horizontal-div">
|
||||
<img class="nexus-check-image" id="nexus-check-image" src="assets/icons/cross.svg" />
|
||||
<form class="horizontal-div input-form" id="nexus-api-form">
|
||||
<input class="input" id="nexus-api-input" type="text" placeholder="Enter your nexus api" />
|
||||
<button class="default-button" onclick="setNexusAPI()">Set</button>
|
||||
</form>
|
||||
<button class="default-button" onclick="resetNexusAPI()">Reset</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="style-template">
|
||||
<h1 class="title">Chose the theme of the app</h1>
|
||||
<div class="horizontal-div welcome-div">
|
||||
@@ -82,9 +66,38 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template id="tutorial-template">
|
||||
<h1 class="title">Tutorial to download mod from nexus</h1>
|
||||
<p>After clicking on "Download," a Nexus Mods window will pop up. Please log in, then click on "Download with Mod Manager."The Nexus window should close, and the mod will start downloading.</p>
|
||||
<template id="nexus-template">
|
||||
<h1 class="title">Additional set up for nexus mods</h1>
|
||||
<h2>If you don't want to use nexus mods just click on next.</h2>
|
||||
<br />
|
||||
<br />
|
||||
<p>
|
||||
1. Please enter your nexus API key. To get your API key go to
|
||||
<a href="https://www.nexusmods.com/settings/api-keys" class="link" id="external-link">https://www.nexusmods.com/settings/api-keys</a> and click on "new personnal API key".
|
||||
</p>
|
||||
<br />
|
||||
<div class="horizontal-div">
|
||||
<img class="nexus-check-image" id="nexus-check-image" src="assets/icons/cross.svg" />
|
||||
<form class="horizontal-div input-form" id="nexus-api-form">
|
||||
<input class="input" id="nexus-api-input" type="text" placeholder="Enter your nexus api" />
|
||||
<button class="default-button" onclick="setNexusAPI()">Set</button>
|
||||
</form>
|
||||
<button class="default-button" onclick="resetNexusAPI()">Reset</button>
|
||||
</div>
|
||||
<br />
|
||||
<br />
|
||||
<p>2. To download a mod from Nexus Mods you need to log in to your Nexus Mods account and then click on "Download with Mod Manager".</p>
|
||||
</template>
|
||||
|
||||
<template id="settings-linux-template">
|
||||
<h1 class="title">Linux Additional Settings</h1>
|
||||
<div class="horizontal-div welcome-div">
|
||||
<h3>Do you use Steam ?</h3>
|
||||
<label class="checkbox-container">
|
||||
<input type="checkbox" name="linux-steam" id="linux-steam" />
|
||||
<span class="checkmark"></span>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script src="renderer.js"></script>
|
||||
|
||||