Compare commits

...

96 commits
v2.4.0 ... main

Author SHA1 Message Date
Daniel López Guimaraes
add1d2c2e2
chore: Bump to version 3.0.0 2025-02-15 21:09:10 +00:00
Daniel López Guimaraes
8bc8ac0290
Merge pull request #61 from Maschell/notifications 2025-02-15 12:40:44 +00:00
Daniel López Guimaraes
4cf3b7446e
chore: Add fallback translations to English 2025-02-15 12:38:27 +00:00
Daniel López Guimaraes
93f9a3dff4
Merge pull request #54 from weblate/weblate-pretendonetwork-inkay 2025-02-15 12:25:47 +00:00
Daniel López Guimaraes
97167e0dab
fix(lang): Fix translation entries order 2025-02-15 12:18:50 +00:00
Maschell
4a880c1a2e feat: Check title version before doing game specific patches 2025-02-09 10:39:14 +01:00
Maschell
79ccad1fd4 fix: Do title specific patches more than once 2025-02-09 10:39:08 +01:00
Maschell
ed75702ac5 Review fixes and update dockerfile 2025-02-08 23:05:25 +01:00
Maschell
429d55ba9f
Apply suggestions from code review
Co-authored-by: Daniel López Guimaraes <112760654+DaniElectra@users.noreply.github.com>
2025-02-03 19:38:44 +01:00
Maschell
e2cf529b3f fix: Only allow the plugin to init Inkay at boot to avoid potential crashes 2025-02-03 12:45:17 +01:00
Maschell
24f680c6fb fix: Only do function patches if the plugin had to to update init Inkay 2025-02-03 12:44:29 +01:00
kaio mesquita fragoso
f9b0477c4e
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/pt_BR/
2025-02-02 19:00:08 +01:00
Maschell
cc3fac1cc7 (chore): clean up includes 2025-01-30 18:27:20 +01:00
Maschell
1ffb9cf65a feat: Show notification is plugin is missing or module was never initialized 2025-01-30 18:26:01 +01:00
Deleted User
f6bb2805f6
Translated using Weblate (Spanish)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/es/
2025-01-30 09:24:23 +01:00
Vitaly Novichkov
eef3580fca
Translated using Weblate (Russian)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/ru/
2025-01-25 00:01:46 +01:00
Lacey Anaya
b5cf02bb90
Translated using Weblate (German)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/de/
2025-01-22 23:02:33 +01:00
Nervi05
a9ee61babe
Translated using Weblate (German)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/de/
2025-01-21 22:49:11 +01:00
Lacey Anaya
1acb60ff49
Translated using Weblate (German)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/de/
2025-01-21 22:49:11 +01:00
Nervi05
0afd60d7f6
Translated using Weblate (German)
Currently translated at 92.3% (12 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/de/
2025-01-21 22:48:21 +01:00
almi05
f02eacad88
Translated using Weblate (Italian)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/it/
2025-01-19 13:00:41 +01:00
Alex Wang
4adf1f267d
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/zh_Hans/
2025-01-03 07:02:17 +01:00
Eduardo Toebe
a6e1fae402
Translated using Weblate (Portuguese (Brazil))
Currently translated at 92.3% (12 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/pt_BR/
2024-12-24 06:01:08 +00:00
Ellie Rosa Maiella
816b34473b
Translated using Weblate (Italian)
Currently translated at 76.9% (10 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/it/
2024-12-24 06:01:08 +00:00
Dimitri A
b4c4cf424b
Translated using Weblate (Japanese)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/ja/
2024-12-22 16:00:36 +01:00
Dimitri A
bb34ab259c
Translated using Weblate (Japanese)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/ja/
2024-12-21 15:23:12 +01:00
Ash Tails
e6fd32cf7a
Translated using Weblate (Japanese)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/ja/
2024-12-21 15:23:12 +01:00
Dimitri A
85e64547d3
Translated using Weblate (Japanese)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/ja/
2024-12-21 15:23:12 +01:00
Dimitri A
4c4f8ae355
Translated using Weblate (French)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/fr/
2024-12-21 15:23:12 +01:00
mathijn wismeijer
f5e136704b
Translated using Weblate (Dutch)
Currently translated at 92.3% (12 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/nl/
2024-12-21 15:23:12 +01:00
Arnau Bàrcia
5b4fbd0521
Translated using Weblate (Spanish)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/es/
2024-12-21 15:23:12 +01:00
Dimitri A
3a79f3228f
Translated using Weblate (French)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/fr/
2024-12-21 15:23:12 +01:00
Dimitri A
abc2e53c6e
Translated using Weblate (French)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/fr/
2024-12-21 15:23:12 +01:00
Daniel López Guimaraes
08fa9ec269
Translated using Weblate (Spanish)
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/es/
2024-12-21 15:23:12 +01:00
Ash
fa59526cd0
docs: Add some basic compilation instructions 2024-12-21 23:17:05 +11:00
Ash
eea33cbd18
chore: Run CI builds for pull requests too 2024-12-21 22:42:05 +11:00
Ash
4966ef3eca
Merge pull request #58 from PretendoNetwork/dependabot/docker/wiiu-env/devkitppc-20241128
chore: bump wiiu-env/devkitppc from 20240505 to 20241128
2024-12-21 22:36:42 +11:00
dependabot[bot]
cf6ad91733
chore: bump wiiu-env/devkitppc from 20240505 to 20241128
Bumps wiiu-env/devkitppc from 20240505 to 20241128.

---
updated-dependencies:
- dependency-name: wiiu-env/devkitppc
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-21 11:35:09 +00:00
Ash
e1d7cb56c2
Create dependabot.yml 2024-12-21 22:34:35 +11:00
Ash Logan
9e376f7839 feat: Move all URL defines into a central location 2024-12-17 16:44:59 +11:00
Ash Logan
38f1b1848e fix(account-settings): Don't re-add function patches on every application boot
libfunctionpatcher stuff is persistent from plugin init time to plugin teardown time. but the memory replacements DO need to happen on every launch, so rearrange things a bit.
2024-12-17 15:13:39 +11:00
Ash
23728c4288
Merge pull request #56 from DimitriPilot3/fix-account-settings-patch
fix(account_settings): Avoid mixing up FSReadFile / FSCloseFile patches
2024-12-15 11:16:59 +11:00
Dimitri A.
6c78a95872 fix(account_settings): Avoid mixing up FSReadFile / FSCloseFile patches
This copy-and-paste error was a culprit of the bug described in issue #55.
It made the Account Settings applet on Wii U softlock during its initialisation, forcing the user to power off the system.

The applet now works again. However, trying to access it a second time without rebooting still freezes the Wii U, which is another regression listed in issue #55.
2024-12-15 00:29:27 +01:00
Ash
ebecf957f1
Merge pull request #53 from weblate/weblate-pretendonetwork-inkay
Translations update from Hosted Weblate
2024-12-01 00:26:01 +11:00
Ash Tails
8d7df27d42
Translated using Weblate (English (en@uwu))
Currently translated at 100.0% (13 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/en@uwu/
2024-11-30 10:11:52 +01:00
redmine4404
0f1e05b021
Translated using Weblate (French)
Currently translated at 84.6% (11 of 13 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/fr/
2024-11-30 10:11:51 +01:00
Ash Tails
2510c691f1
Added translation using Weblate (English (en@uwu)) 2024-11-30 10:11:51 +01:00
Ash Tails
4009d44a97
Deleted translation using Weblate (English (en@uwu)) 2024-11-30 10:11:50 +01:00
Ash Tails
e6edcec5a5
Added translation using Weblate (English (en@uwu)) 2024-11-30 10:11:50 +01:00
Ash Logan
a6b91be935 feat(p2p): Port overrides for Splatoon and MK8 2024-11-29 21:40:21 +11:00
Ash Logan
727f11d04d feat: Compile flags for binary size
Small binaries load faster, work better on Aroma Updater, and break the website less
2024-11-29 21:40:21 +11:00
Ash Logan
dbb648729a fix(config): Use noexcept version of WupsConfigItemStub::create 2024-11-29 21:40:21 +11:00
Ash
4b0e298b8e
Update README.md 2024-11-25 18:24:29 +11:00
Ash Logan
cd1d530da9 fix(config): Switch to snprintf for formatting
while vformat is closer to modern string-formatting sensibilities, it added 400KiB to our binaries, whereas snprintf has no noticeable impact. so... suffer, and hope the translators know what's up
2024-11-22 11:44:20 +11:00
Ash
2bf5f8bd18
Merge pull request #50 from DaniElectra/module 2024-11-22 09:44:10 +11:00
Daniel López Guimaraes
0f3115a402
fix(p2p): Don't apply patches when not using Pretendo 2024-11-21 20:43:17 +00:00
Daniel López Guimaraes
06847b21e5
fix(p2p): Fix compilation error in multiplayer port text
GCC complains about std::make_format_args using non-const references, so
we can't pass along the `get_console_peertopeer_port` directly.

Also replace `unsigned short` with `uint16_t` for clarity.
2024-11-21 20:39:51 +00:00
Ash Logan
a710bd985a feat(lang): Tell users how to fix module-related errors
Realistically, 99% of people will not know how to "ensure the inkay module is properly installed" - Aroma updater is designed to automatically fix random stuff like this
It hashes every individual file so it will pick up SD card corruption, partial installs, etc
2024-11-21 13:49:54 +11:00
Ash Logan
ea71879bb1 chore: Introduce common directory to deduplicate shared code 2024-11-21 13:19:06 +11:00
Daniel López Guimaraes
68bc066c7b feat: Add translation for module not found errors 2024-11-21 12:50:38 +11:00
Daniel López Guimaraes
7958373394 feat: Improvements in module exports
Add new `Inkay_GetStatus` which can be used for retrieving the status of
the module. Also add better handling of module initialization, and show
a notification from the plugin if the module can't be found.
2024-11-21 12:50:23 +11:00
Daniel López Guimaraes
d3ff378cc4 chore: Remove unnecesary file
`wut_extra.h` is only used in the plugin, not in the module.
2024-11-21 12:49:19 +11:00
Daniel López Guimaraes
3460feeeaf fix(Makefile): Replicate original compiler settings 2024-11-21 12:49:19 +11:00
Daniel López Guimaraes
f4aeee8034 feat!: Split Inkay into a module and a plugin
This is necessary for a future Aroma feature. This also makes more clear
the immutability of the patches that Pretendo requires, since they
cannot be reverted easily.

All patching code is now managed inside the module, which the plugin
triggers by calling an exported function from it. The WWP reset code
remains within the plugin as it isn't strictly tied to the patches.
2024-11-21 12:49:16 +11:00
Ash Logan
cc967126fe feat(p2p): Show p2p port in the config menu
with love to my weblate people
2024-11-17 23:59:26 +11:00
Ash Logan
22bebff027 feat(p2p): Allow overriding the p2p udp port for Minecraft
the idea is that you might port forward this in your router and give yourself NAT type A for free
2024-11-17 23:59:26 +11:00
Weblate (bot)
a1375ca608
Translations update from Hosted Weblate (#52)
* Translated using Weblate (Spanish)

Currently translated at 100.0% (10 of 10 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/es/

* Translated using Weblate (Spanish)

Currently translated at 100.0% (10 of 10 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/es/

---------

Co-authored-by: Arnau Bàrcia <a.barcia.sallegi@gmail.com>
2024-11-06 00:41:54 +11:00
Weblate (bot)
9d29ad78fd
Translated using Weblate (Spanish) (#47)
Currently translated at 100.0% (10 of 10 strings)

Translation: Pretendo Network/Inkay
Translate-URL: https://hosted.weblate.org/projects/pretendonetwork/inkay/es/

Co-authored-by: gallegonovato <fran-carro@hotmail.es>
2024-10-06 13:33:51 +11:00
Ash Logan
6baee13685 feat(i18n): Move all texts into .lang files which are actually Java properties and C structs at the same time
no further comment
2024-10-03 00:00:56 +10:00
Ash Logan
1d1e094257 feat(i18n): Move NN/PN messages to main config_strings struct 2024-10-02 23:46:35 +10:00
Ash Logan
b16237417c fix(olv): Allow RPLs with null names
CafeGLSL is an offender here
2024-09-11 13:07:29 +10:00
Ash Logan
aedd02b0d1 chore(config): Switch to string_view for translated strings
Elides about 100 strlen calls by allowing the length to be calculated at compile time

ofc the WUPS backend doesn't USE this info but, y'know.
2024-08-04 18:15:26 +10:00
Ash Logan
c6d80bd550 chore(config): Refactor error handling and remove exceptions
Honestly kinda feeling like the C++ API isn't worth it
2024-08-04 17:44:24 +10:00
Ash Logan
1a85a45ce3 chore: Tighten up logspam and binary size
Remove some old stuff to Make Small Inkay
2024-08-04 16:24:01 +10:00
yarb00
04d723137b feat(i18n): Add Russian translation
Closes #41

Co-authored-by: Ash Logan <ash@heyquark.com>
2024-08-03 22:05:28 +10:00
JouriR
956419a438 feat(i18n): Add Dutch translation
Closes #36

Co-authored-by: Ash Logan <ash@heyquark.com>
2024-08-03 22:00:50 +10:00
Dekokiyo
bdfb6ca859 feat(i18n): Add Japanese translation
Closes #33

Co-authored-by: Ash Logan <ash@heyquark.com>
2024-08-03 21:59:02 +10:00
Luís
a6fd3783f1 feat(i18n): Add Portuguese translation
Closes #31

Co-authored-by: Ash Logan <ash@heyquark.com>
2024-08-03 21:57:15 +10:00
一时 三月
867ea12f17 feat(i18n): Add Traditional Chinese, Simplified Chinese
Closes #25

Co-authored-by: Ash Logan <ash@heyquark.com>
2024-08-03 21:54:16 +10:00
Ash Logan
734e5d40c9 chore: clean up old includes
caused compile issues on latest tools - the curl include is old and the wut headers added C++ templates
2024-08-03 21:46:46 +10:00
Ash
538fc25014 feat(main): Version 2.6.0 2024-07-25 13:20:23 +00:00
Ash Logan
3f59781bd8 feat(spotpass): Force BOSS policy list refresh on each boot
hours in ghidra for a single line of code
2024-07-24 19:13:43 +10:00
Ash Logan
349b1f38aa fix(main): Version v2.5.0
Updating the aroma APIs does not demand a major version bump imho
Users are always expected to be on latest aroma anyway, v3 would be saved for like. Stroopwafel
2024-05-28 16:48:10 +10:00
Ash Logan
9a9bf0949d fix(config): Don't use C++ exceptions
they almost certainly don't work in this environment, and just returning is better
2024-05-28 16:47:12 +10:00
Ash Logan
695a077ebd chore: Run code formatter across project
this is the CLion formatter since I haven't set up clang-format for Inkay yet
2024-05-28 16:36:39 +10:00
TraceEntertains
b02db3f20b
feat!: Support for newer Aroma releases (#28)
* Fix aroma beta 17 incompatibilities (no network notification, no unregister boss tasks), and fix debug logger

* Update Dockerfile

* Fix logger.h

* Simplify notifcations

* Init nn::boss::Task

* Small fixes for the storage/config API usage

* Do a bunch of stuff, wish I knew what I did but I honestly forgot most of it (this is not considered a 100% functional commit

* Remove remnants of has_displayed_popup variables (not needed anymore)

* More notifications stuff

* Even more notifications stuff

* Fix notifications (yay) (thanks maschell)

* Disable debug by default

* Fix compiler warnings

* Update some stuff, cant test have to go

---------

Co-authored-by: Maschell <Maschell@gmx.de>
2024-05-13 15:45:43 +10:00
Ash Logan
b239ae82db fixup! feat(i18n): Add German translation (forgot a file) 2024-04-18 11:48:44 +10:00
Ash Logan
636354493b feat(i18n): Add German translation (thanks @SteffoSpieler and @BlinkingJarl482)
Add TL note about "Using" vs. "Connected", though I'm willing to change this later
2024-04-18 11:48:14 +10:00
Kintsugi
ddd2c9c803
Update main.cpp to include Italian (#24)
* Update main.cpp

Added Italian translation, thanks Spanish guys

* Update main.cpp
2024-04-17 11:12:42 +10:00
Kintsugi
11c170dc06 Update config.cpp
Added Italian translation
2024-04-17 11:11:32 +10:00
Ash
c05c3325d3
feat(i18n): Add French translation (thanks @InternalLoss) 2024-04-15 13:35:19 +10:00
Ash
bf0031faa8
Update Spanish translation
Was informed:
- "Pretendo" is us
- "Pretendo Network" is the network we run
2024-04-15 13:28:19 +10:00
Ash Logan
216942cb64 feat(i18n): Improve menu text and add Spanish translation (thanks @EmiSocks)
This feels like a not very scalable way of doing translations
2024-04-10 00:18:50 +10:00
Ash Logan
24596b1bff chore: Fix license headers to GPLv3
Inkay's been GPLv3 for a while and it's fine to relicense old ISC code to GPL
2024-04-09 23:31:43 +10:00
Ash Logan
e0962577cd chore: Bump version 2024-04-09 23:21:40 +10:00
Jemma Poffinbarger
cb197bcf40
Account Settings and eShop app support (#17)
* Added support for eShop and account settings apps (account settings is currently broken)

* Moved account settings patch to run at application start
2024-04-09 23:15:30 +10:00
59 changed files with 2231 additions and 458 deletions

13
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,13 @@
# To get started with Dependabot version updates, you'll need to specify which
# package ecosystems to update and where the package manifests are located.
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
version: 2
updates:
- package-ecosystem: "docker" # See documentation for possible values
directory: "/" # Location of package manifests
schedule:
interval: "weekly"
commit-message:
prefix: "chore: "

View file

@ -1,18 +1,20 @@
name: Inkay-CI
on: push
on:
- push
- pull_request
jobs:
build-inkay:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v4
- name: build toolchain container
run: docker build . -t builder
- uses: ammaraskar/gcc-problem-matcher@master
- name: build Inkay
run: docker run --rm -v ${PWD}:/app -w /app builder
- uses: actions/upload-artifact@master
- uses: actions/upload-artifact@v4
with:
name: inkay
path: "*.wps"
path: dist/

6
.gitignore vendored
View file

@ -1,6 +1,12 @@
.cache/
.vscode/
.idea/
/build
/plugin/build
/dist
*.elf
*.wms
*.wps
certs/
*.lst
inkay_config.local.h

View file

@ -1,10 +1,11 @@
FROM ghcr.io/wiiu-env/devkitppc:20231112
FROM ghcr.io/wiiu-env/devkitppc:20241128
COPY --from=ghcr.io/wiiu-env/libnotifications:20230621 /artifacts $DEVKITPRO
COPY --from=ghcr.io/wiiu-env/libnotifications:20240426 /artifacts $DEVKITPRO
COPY --from=ghcr.io/wiiu-env/libfunctionpatcher:20230621 /artifacts $DEVKITPRO
COPY --from=ghcr.io/wiiu-env/libkernel:20230621 /artifacts $DEVKITPRO
COPY --from=ghcr.io/wiiu-env/libmocha:20231127 /artifacts $DEVKITPRO
COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20230719 /artifacts $DEVKITPRO
COPY --from=ghcr.io/wiiu-env/wiiumodulesystem:20250208 /artifacts $DEVKITPRO
COPY --from=ghcr.io/wiiu-env/wiiupluginsystem:20240505 /artifacts $DEVKITPRO
WORKDIR /app
CMD make -f Makefile -j$(nproc)
CMD make -f Makefile -j$(nproc)

View file

@ -8,10 +8,10 @@ endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/wups/share/wups_rules
include $(DEVKITPRO)/wums/share/wums_rules
WUT_ROOT := $(DEVKITPRO)/wut
WUMS_ROOT := $(DEVKITPRO)/wums
WUT_ROOT := $(DEVKITPRO)/wut
#-------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
@ -21,32 +21,36 @@ WUMS_ROOT := $(DEVKITPRO)/wums
#-------------------------------------------------------------------------------
TARGET := Inkay-pretendo
BUILD := build
SOURCES := src src/patches src/utils src/ext/inih
SOURCES := src src/patches src/utils src/ext/inih common
DATA := data
INCLUDES := src src/ext/inih
INCLUDES := src src/ext/inih src/lang common
#-------------------------------------------------------------------------------
# options for code generation
#-------------------------------------------------------------------------------
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(MACHDEP)
OPT := -Os -fno-exceptions -fno-asynchronous-unwind-tables
CFLAGS := -Wall -ffunction-sections -fdata-sections \
$(MACHDEP) $(OPT)
CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -D__WUPS__
CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__
CXXFLAGS := $(CFLAGS) -std=c++20
ASFLAGS := -g $(ARCH)
LDFLAGS = -g $(ARCH) $(RPXSPECS) -Wl,-Map,$(notdir $*.map)
LDFLAGS = -g $(ARCH) $(OPT) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) -Wl,-gc-sections -T$(WUMS_ROOT)/share/libkernel.ld $(WUMSSPECS)
LDFLAGS += -T$(WUMS_ROOT)/share/libkernel.ld $(WUPSSPECS)
ifeq ($(DEBUG),1)
CXXFLAGS += -DDEBUG -g
CFLAGS += -DDEBUG -g
endif
LIBS := -lwups -lmocha -lkernel -lwut -lnotifications -lfunctionpatcher
LIBS := -lwums -lmocha -lkernel -lwut -lfunctionpatcher -lnotifications
#-------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level
# containing include and lib
#-------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS) $(WUMS_ROOT) $(WUPS_ROOT) $(WUT_ROOT) $(WUT_ROOT)/usr
LIBDIRS := $(PORTLIBS) $(WUT_ROOT) $(WUMS_ROOT) $(WUT_ROOT)/usr
#-------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
@ -98,14 +102,20 @@ export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
#-------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@$(shell [ ! -d $(BUILD) ] && mkdir -p $(BUILD))
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
@$(MAKE) --no-print-directory -C $(CURDIR)/plugin -f $(CURDIR)/plugin/Makefile
mkdir -p dist/
cp *.wms dist/
cp plugin/*.wps dist/
#-------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).wps $(TARGET).elf
@rm -fr dist $(BUILD) $(TARGET).wms $(TARGET).elf
@$(MAKE) --no-print-directory -C $(CURDIR)/plugin -f $(CURDIR)/plugin/Makefile clean
#-------------------------------------------------------------------------------
else
@ -116,10 +126,10 @@ DEPENDS := $(OFILES:.o=.d)
#-------------------------------------------------------------------------------
# main targets
#-------------------------------------------------------------------------------
all : $(OUTPUT).wps
all : $(OUTPUT).wms
$(OUTPUT).wps : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
$(OUTPUT).wms : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
$(OFILES_SRC) : $(HFILES_BIN)
@ -136,6 +146,11 @@ $(OFILES_SRC) : $(HFILES_BIN)
@echo $(notdir $<)
@$(bin2o)
#---------------------------------------------------------------------------------
%.o: %.s
@echo $(notdir $<)
@$(CC) -MMD -MP -MF $(DEPSDIR)/$*.d -x assembler-with-cpp $(ASFLAGS) -c $< -o $@ $(ERROR_FILTER)
-include $(DEPENDS)
#-------------------------------------------------------------------------------

View file

@ -2,15 +2,44 @@
[![Pretendo network logo](https://github.com/PretendoNetwork/website/raw/master/public/assets/images/opengraph/opengraph-image.png)](https://pretendo.network)
Inkay is an Aroma/WUPS plugin that patches various Nintendo Network URLs on a Wii U to use Pretendo Network instead. It also (for the time being) bypasses SSL verification in most cases.
Inkay is an Aroma/WUPS plugin that patches various Nintendo Network URLs on a Wii U to use Pretendo Network instead. It also (for the time being) bypasses SSL verification in most cases. It redirects Nintendo Network in:
Inkay does not currently include the game-specific patches present in [Nimble](https://github.com/PretendoNetwork/Nimble). These will be implemented soon™.
- IOSU-side connections (Friends, SpotPass, accounts etc.)
- Account Settings
- NNCS
- Nintendo eShop
- Miiverse (in-game)
- Miiverse applet
## Dependencies
Inkay is only supported on the release version of Aroma configured for autoboot/coldboot. For Tiramisu, see [Nimble](https://github.com/PretendoNetwork/Nimble).
Inkay also includes game-specific patches to add extra features:
- Modpack-specific matchmaking for global, regional rooms (by simulating extra DLC) - **Mario Kart 8**
- P2P port override for better connection stability (if you port forward) - **Minecraft: Wii U Edition**, **Mario Kart 8**, **Splatoon**
## Requirements
Inkay is only supported on the release version of Aroma configured for autoboot/coldboot. Other configurations (specifically lacking coldboot) may cause issues with SpotPass.
## Safety
Inkay's patches are all temporary, and only applied in-memory without modifying your console. The SSL patch, while also temporary, could reduce the overall security of your console while active - this is because it no longer checks if a server is verified. However, this does not apply to the Internet Browser, where SSL still works as expected.
## Compiling - Docker
Inkay's dependencies and build tooling can be handled as a container, which is recommended for WUPS plugins. Using `docker` or `podman`:
```shell
docker build -t inkay .
docker run --rm -v $(pwd):/app inkay make
# you can replace "make" with other commands - e.g. make clean
```
If using `podman` on SELinux systems (like Fedora Linux), you might need to use `$(pwd):/app:Z` instead of `$(pwd):/app`.
## Compiling - System
Inkay has the following dependencies aside from devkitPPC and wut:
- [WiiUPluginSystem](https://github.com/wiiu-env/WiiUPluginSystem)
- [WiiUModuleSystem](https://github.com/wiiu-env/WiiUModuleSystem)
- [libmocha](https://github.com/wiiu-env/libmocha)
- [libkernel](https://github.com/wiiu-env/libkernel/)
- [libnotifications](https://github.com/wiiu-env/libnotifications/)
- [libfunctionpatcher](https://github.com/wiiu-env/libfunctionpatcher)
Each of these should be `make install`-able. After that, you can compile Inkay with `make`.
## TODO
See [Issues](https://github.com/PretendoNetwork/Inkay/issues).

31
common/Notification.cpp Normal file
View file

@ -0,0 +1,31 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
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 <http://www.gnu.org/licenses/>.
*/
#include "Notification.h"
#include <notifications/notification_defines.h>
#include <notifications/notifications.h>
void ShowNotification(const char* notification) {
auto err1 = NotificationModule_SetDefaultValue(NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO,
NOTIFICATION_MODULE_DEFAULT_OPTION_KEEP_UNTIL_SHOWN, true);
auto err2 = NotificationModule_SetDefaultValue(NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO,
NOTIFICATION_MODULE_DEFAULT_OPTION_DURATION_BEFORE_FADE_OUT,
15.0f);
if (err1 != NOTIFICATION_MODULE_RESULT_SUCCESS || err2 != NOTIFICATION_MODULE_RESULT_SUCCESS) return;
NotificationModule_AddInfoNotification(notification);
}

3
common/Notification.h Normal file
View file

@ -0,0 +1,3 @@
#pragma once
void ShowNotification(const char* notification);

21
common/inkay_config.h Normal file
View file

@ -0,0 +1,21 @@
//
// Created by ash on 17/12/24.
//
#ifndef INKAY_INKAY_CONFIG_H
#define INKAY_INKAY_CONFIG_H
#ifdef __has_include
#if __has_include("inkay_config.local.h")
#include "inkay_config.local.h"
#define INKAY_CUSTOM 1
#endif
#endif
#ifndef NETWORK_BASEURL
#define NETWORK_BASEURL "pretendo.cc"
#endif
#endif //INKAY_INKAY_CONFIG_H

44
common/lang.cpp Normal file
View file

@ -0,0 +1,44 @@
//
// Created by ash on 21/11/24.
//
#include "lang.h"
config_strings get_config_strings(nn::swkbd::LanguageType language) {
switch (language) {
case nn::swkbd::LanguageType::English:
default: return {
#include "en_US.lang"
};
case nn::swkbd::LanguageType::Spanish: return {
#include "es_ES.lang"
};
case nn::swkbd::LanguageType::French: return {
#include "fr_FR.lang"
};
case nn::swkbd::LanguageType::Italian: return {
#include "it_IT.lang"
};
case nn::swkbd::LanguageType::German: return {
#include "de_DE.lang"
};
case nn::swkbd::LanguageType::SimplifiedChinese: return {
#include "zh_CN.lang"
};
case nn::swkbd::LanguageType::TraditionalChinese: return {
#include "zh_Hant.lang"
};
case nn::swkbd::LanguageType::Portuguese: return {
#include "pt_BR.lang"
};
case nn::swkbd::LanguageType::Japanese: return {
#include "ja_JP.lang"
};
case nn::swkbd::LanguageType::Dutch: return {
#include "nl_NL.lang"
};
case nn::swkbd::LanguageType::Russian: return {
#include "ru_RU.lang"
};
}
}

22
common/lang.h Normal file
View file

@ -0,0 +1,22 @@
#pragma once
#include <string_view>
#include <nn/swkbd.h>
struct config_strings {
const char *plugin_name;
std::string_view network_category;
std::string_view connect_to_network_setting;
std::string_view other_category;
std::string_view reset_wwp_setting;
std::string_view press_a_action;
std::string_view restart_to_apply_action;
std::string_view need_menu_action;
std::string_view using_nintendo_network;
std::string_view using_pretendo_network;
std::string_view multiplayer_port_display;
std::string_view module_not_found;
std::string_view module_init_not_found;
};
config_strings get_config_strings(nn::swkbd::LanguageType language);

115
common/sysconfig.cpp Normal file
View file

@ -0,0 +1,115 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
Copyright 2024 Ash Logan <ash@heyquark.com>
Copyright 2020-2022 V10lator <v10lator@myway.de>
Copyright 2022 Xpl0itU <DaThinkingChair@protonmail.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "sysconfig.h"
#include "utils/logger.h"
#include "utils/scope_exit.h"
#include <coreinit/mcp.h>
#include <coreinit/userconfig.h>
#include <optional>
nn::swkbd::LanguageType get_system_language() {
static std::optional <nn::swkbd::LanguageType> cached_language{};
if (cached_language) return *cached_language;
UCHandle handle = UCOpen();
if (handle < 0) {
DEBUG_FUNCTION_LINE("Error opening UC: %d", handle);
return nn::swkbd::LanguageType::English;
}
scope_exit uc_c([&] { UCClose(handle); });
nn::swkbd::LanguageType language;
alignas(0x40) UCSysConfig settings = {
.name = "cafe.language",
.access = 0,
.dataType = UC_DATATYPE_UNSIGNED_INT,
.error = UC_ERROR_OK,
.dataSize = sizeof(language),
.data = &language,
};
UCError err = UCReadSysConfig(handle, 1, &settings);
if (err != UC_ERROR_OK) {
DEBUG_FUNCTION_LINE("Error reading UC: %d!", err);
return nn::swkbd::LanguageType::English;
}
DEBUG_FUNCTION_LINE_VERBOSE("System language found: %d", language);
cached_language = language;
return language;
}
static std::optional<MCPSysProdSettings> mcp_config;
static std::optional<MCPSystemVersion> mcp_os_version;
static void get_mcp_config() {
int mcp = MCP_Open();
scope_exit mcp_c([&] { MCP_Close(mcp); });
alignas(0x40) MCPSysProdSettings config {};
if (MCP_GetSysProdSettings(mcp, &config)) {
DEBUG_FUNCTION_LINE("Could not get MCP system config!");
return;
}
mcp_config = config;
//get os version
MCPSystemVersion os_version;
if (MCP_GetSystemVersion(mcp, &os_version)) {
DEBUG_FUNCTION_LINE("Could not get MCP system config!");
return;
}
mcp_os_version = os_version;
DEBUG_FUNCTION_LINE_VERBOSE("Running on %d.%d.%d%c; %s%s",
os_version.major, os_version.minor, os_version.patch, os_version.region
config.code_id, config.serial_id
);
}
const char * get_console_serial() {
if (!mcp_config) get_mcp_config();
return mcp_config ? mcp_config->serial_id : "123456789";
}
MCPSystemVersion get_console_os_version() {
if (!mcp_os_version) get_mcp_config();
return mcp_os_version.value_or((MCPSystemVersion) { .major = 5, .minor = 5, .patch = 5, .region = 'E' });
}
static inline int digit(char a) {
if (a < '0' || a > '9') return 0;
return a - '0';
}
uint16_t get_console_peertopeer_port() {
const char * serial = get_console_serial();
uint16_t port = 50000 +
(digit(serial[4]) * 1000) +
(digit(serial[5]) * 100 ) +
(digit(serial[6]) * 10 ) +
(digit(serial[7]) * 1 );
return port;
}

16
common/sysconfig.h Normal file
View file

@ -0,0 +1,16 @@
//
// Created by ash on 9/04/24.
//
#ifndef INKAY_SYSCONFIG_H
#define INKAY_SYSCONFIG_H
#include <nn/swkbd.h>
#include <coreinit/mcp.h>
nn::swkbd::LanguageType get_system_language();
const char * get_console_serial();
MCPSystemVersion get_console_os_version();
unsigned short get_console_peertopeer_port();
#endif //INKAY_SYSCONFIG_H

13
common/utils/scope_exit.h Normal file
View file

@ -0,0 +1,13 @@
#pragma once
#include <functional>
// https://stackoverflow.com/a/61242721
template<typename F>
struct scope_exit
{
F func;
explicit scope_exit(F&& f): func(std::forward<F>(f)) {}
~scope_exit() { func(); }
};
template<typename F> scope_exit(F&& frv) -> scope_exit<F>;

149
plugin/Makefile Normal file
View file

@ -0,0 +1,149 @@
#-------------------------------------------------------------------------------
.SUFFIXES:
#-------------------------------------------------------------------------------
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>/devkitpro")
endif
TOPDIR ?= $(CURDIR)
include $(DEVKITPRO)/wups/share/wups_rules
WUT_ROOT := $(DEVKITPRO)/wut
WUMS_ROOT := $(DEVKITPRO)/wums
#-------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# DATA is a list of directories containing data files
# INCLUDES is a list of directories containing header files
#-------------------------------------------------------------------------------
TARGET := Inkay-pretendo
BUILD := build
SOURCES := src src/utils ../common
DATA := data
INCLUDES := src ../src/lang ../common
#DEBUG := 1
#-------------------------------------------------------------------------------
# options for code generation
#-------------------------------------------------------------------------------
OPT := -Os -fno-exceptions -fno-asynchronous-unwind-tables
CFLAGS := -Wall -ffunction-sections -fdata-sections \
$(MACHDEP) $(OPT)
CFLAGS += $(INCLUDE) -D__WIIU__ -D__WUT__ -D__WUPS__
ifeq ($(DEBUG),1)
CFLAGS += -DDEBUG -g
endif
CXXFLAGS := $(CFLAGS) -std=c++20
ASFLAGS := -g $(ARCH)
LDFLAGS = -g $(ARCH) $(OPT) $(RPXSPECS) -Wl,-Map,$(notdir $*.map) -Wl,-gc-sections -Wl,--discard-all
LDFLAGS += $(WUPSSPECS)
LIBS := -lwups -lwut -lnotifications
#-------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level
# containing include and lib
#-------------------------------------------------------------------------------
LIBDIRS := $(PORTLIBS) $(WUMS_ROOT) $(WUPS_ROOT) $(WUT_ROOT)
#-------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#-------------------------------------------------------------------------------
ifneq ($(BUILD),$(notdir $(CURDIR)))
#-------------------------------------------------------------------------------
export OUTPUT := $(CURDIR)/$(TARGET)
export TOPDIR := $(CURDIR)
export VPATH := $(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
$(foreach dir,$(DATA),$(CURDIR)/$(dir))
export DEPSDIR := $(CURDIR)/$(BUILD)
CFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES := $(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES := $(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))
#-------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#-------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#-------------------------------------------------------------------------------
export LD := $(CC)
#-------------------------------------------------------------------------------
else
#-------------------------------------------------------------------------------
export LD := $(CXX)
#-------------------------------------------------------------------------------
endif
#-------------------------------------------------------------------------------
export OFILES_BIN := $(addsuffix .o,$(BINFILES))
export OFILES_SRC := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)
export OFILES := $(OFILES_BIN) $(OFILES_SRC)
export HFILES_BIN := $(addsuffix .h,$(subst .,_,$(BINFILES)))
export INCLUDE := $(foreach dir,$(INCLUDES),-I$(CURDIR)/$(dir)) \
$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
-I$(CURDIR)/$(BUILD)
export LIBPATHS := $(foreach dir,$(LIBDIRS),-L$(dir)/lib)
.PHONY: $(BUILD) clean all
#-------------------------------------------------------------------------------
all: $(BUILD)
$(BUILD):
@$(shell [ ! -d $(BUILD) ] && mkdir -p $(BUILD))
@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile
#-------------------------------------------------------------------------------
clean:
@echo clean ...
@rm -fr $(BUILD) $(TARGET).wps $(TARGET).elf
#-------------------------------------------------------------------------------
else
.PHONY: all
DEPENDS := $(OFILES:.o=.d)
#-------------------------------------------------------------------------------
# main targets
#-------------------------------------------------------------------------------
all : $(OUTPUT).wps
$(OUTPUT).wps : $(OUTPUT).elf
$(OUTPUT).elf : $(OFILES)
$(OFILES_SRC) : $(HFILES_BIN)
#-------------------------------------------------------------------------------
# you need a rule like this for each extension you use as binary data
#-------------------------------------------------------------------------------
%.bin.o %_bin.h : %.bin
#-------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
%.pem.o %_pem.h : %.pem
#-------------------------------------------------------------------------------
@echo $(notdir $<)
@$(bin2o)
-include $(DEPENDS)
#-------------------------------------------------------------------------------
endif
#-------------------------------------------------------------------------------

234
plugin/src/config.cpp Normal file
View file

@ -0,0 +1,234 @@
/* Copyright 2022 Pretendo Network contributors <pretendo.network>
Copyright 2022 Ash Logan <ash@heyquark.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "wut_extra.h"
#include "utils/logger.h"
#include "sysconfig.h"
#include "lang.h"
#include <wups.h>
#include <wups/storage.h>
#include <wups/config_api.h>
#include <wups/config/WUPSConfigItemBoolean.h>
#include <wups/config/WUPSConfigItemStub.h>
#include <coreinit/title.h>
#include <coreinit/launch.h>
#include <sysapp/title.h>
#include <sysapp/launch.h>
#include <nn/act.h>
#include <format>
static config_strings strings;
bool Config::connect_to_network = true;
bool Config::need_relaunch = false;
bool Config::unregister_task_item_pressed = false;
bool Config::is_wiiu_menu = false;
static WUPSConfigAPICallbackStatus report_error(WUPSConfigAPIStatus err) {
DEBUG_FUNCTION_LINE_VERBOSE("WUPS config error: %s", WUPSConfigAPI_GetStatusStr(err));
return WUPSCONFIG_API_CALLBACK_RESULT_ERROR;
}
static void report_storage_error(WUPSStorageError err) {
DEBUG_FUNCTION_LINE_VERBOSE("WUPS storage error: %s", WUPSStorageAPI_GetStatusStr(err));
}
static void connect_to_network_changed(ConfigItemBoolean* item, bool new_value) {
DEBUG_FUNCTION_LINE_VERBOSE("connect_to_network changed to: %d", new_value);
if (new_value != Config::connect_to_network) {
Config::need_relaunch = true;
}
Config::connect_to_network = new_value;
WUPSStorageError res;
res = WUPSStorageAPI::Store<bool>("connect_to_network", Config::connect_to_network);
if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res);
}
static void unregister_task_item_on_input_cb(void *context, WUPSConfigSimplePadData input) {
if (!Config::unregister_task_item_pressed && Config::is_wiiu_menu && ((input.buttons_d & WUPS_CONFIG_BUTTON_A) == WUPS_CONFIG_BUTTON_A)) {
nn::act::Initialize();
Initialize__Q2_2nn4bossFv();
for (uint8_t i = 1; i <= nn::act::GetNumOfAccounts(); i++)
{
if (nn::act::IsSlotOccupied(i) && nn::act::IsNetworkAccountEx(i))
{
nn::boss::Task task{};
nn::act::PersistentId persistentId = nn::act::GetPersistentIdEx(i);
__ct__Q3_2nn4boss4TaskFv(&task);
Initialize__Q3_2nn4boss4TaskFPCcUi(&task, "oltopic", persistentId);
// bypasses compiler warning about unused variable
#ifdef DEBUG
uint32_t res = Unregister__Q3_2nn4boss4TaskFv(&task);
DEBUG_FUNCTION_LINE_VERBOSE("Unregistered oltopic for: SlotNo %d | Persistent ID %08x -> 0x%08x", i, persistentId, res);
#else
Unregister__Q3_2nn4boss4TaskFv(&task);
#endif
}
}
Finalize__Q2_2nn4bossFv();
nn::act::Finalize();
Config::unregister_task_item_pressed = !Config::unregister_task_item_pressed;
Config::need_relaunch = true;
}
}
static int32_t unregister_task_item_get_display_value(void *context, char *out_buf, int32_t out_size) {
auto string = strings.need_menu_action;
if (Config::is_wiiu_menu) {
if (Config::unregister_task_item_pressed) {
string = strings.restart_to_apply_action;
} else {
string = strings.press_a_action;
}
}
if ((int)string.length() > out_size - 1) return -1;
string.copy(out_buf, string.length());
out_buf[string.length()] = '\0';
return 0;
}
static WUPSConfigAPICallbackStatus ConfigMenuOpenedCallback(WUPSConfigCategoryHandle rootHandle) {
WUPSConfigAPIStatus err;
bool res;
uint64_t current_title_id = OSGetTitleID();
uint64_t wiiu_menu_tid = _SYSGetSystemApplicationTitleId(SYSTEM_APP_ID_WII_U_MENU);
Config::is_wiiu_menu = (current_title_id == wiiu_menu_tid);
// get translation strings
strings = get_config_strings(get_system_language());
// create root config category
WUPSConfigCategory root = WUPSConfigCategory(rootHandle);
auto network_cat = WUPSConfigCategory::Create(strings.network_category, err);
if (!network_cat) return report_error(err);
// config id display name default current value changed callback
auto connect_item = WUPSConfigItemBoolean::Create("connect_to_network", strings.connect_to_network_setting, true, Config::connect_to_network, &connect_to_network_changed, err);
if (!connect_item) return report_error(err);
res = network_cat->add(std::move(*connect_item), err);
if (!res) return report_error(err);
{
uint16_t port = get_console_peertopeer_port();
char buffer[256];
snprintf(buffer, sizeof(buffer), strings.multiplayer_port_display.data(), port);
auto multiplayer_port_display = WUPSConfigItemStub::Create(buffer, err);
if (!multiplayer_port_display) return report_error(err);
res = network_cat->add(std::move(*multiplayer_port_display), err);
if (!res) return report_error(err);
}
res = root.add(std::move(*network_cat), err);
if (!res) return report_error(err);
auto other_cat = WUPSConfigCategory::Create(strings.other_category, err);
if (!other_cat) return report_error(err);
WUPSConfigAPIItemCallbacksV2 unregisterTasksItemCallbacks = {
.getCurrentValueDisplay = unregister_task_item_get_display_value,
.getCurrentValueSelectedDisplay = unregister_task_item_get_display_value,
.onSelected = nullptr,
.restoreDefault = nullptr,
.isMovementAllowed = nullptr,
.onCloseCallback = nullptr,
.onInput = unregister_task_item_on_input_cb,
.onInputEx = nullptr,
.onDelete = nullptr
};
WUPSConfigAPIItemOptionsV2 unregisterTasksItemOptions = {
.displayName = strings.reset_wwp_setting.data(),
.context = nullptr,
.callbacks = unregisterTasksItemCallbacks,
};
WUPSConfigItemHandle unregisterTasksItem;
err = WUPSConfigAPI_Item_Create(unregisterTasksItemOptions, &unregisterTasksItem);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err);
err = WUPSConfigAPI_Category_AddItem(other_cat->getHandle(), unregisterTasksItem);
if (err != WUPSCONFIG_API_RESULT_SUCCESS) return report_error(err);
res = root.add(std::move(*other_cat), err);
if (!res) return report_error(err);
return WUPSCONFIG_API_CALLBACK_RESULT_SUCCESS;
}
static void ConfigMenuClosedCallback() {
// Save all changes
WUPSStorageError res;
res = WUPSStorageAPI::SaveStorage();
if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res);
if (Config::need_relaunch) {
// Need to reload the console so the patches reset
OSForceFullRelaunch();
SYSLaunchMenu();
Config::need_relaunch = false;
}
}
void Config::Init() {
WUPSConfigAPIStatus cres;
// Init the config api
WUPSConfigAPIOptionsV1 configOptions = { .name = "Inkay" };
cres = WUPSConfigAPI_Init(configOptions, ConfigMenuOpenedCallback, ConfigMenuClosedCallback);
if (cres != WUPSCONFIG_API_RESULT_SUCCESS) return (void)report_error(cres);
WUPSStorageError res;
// Try to get value from storage
res = WUPSStorageAPI::Get<bool>("connect_to_network", Config::connect_to_network);
if (res == WUPS_STORAGE_ERROR_NOT_FOUND) {
DEBUG_FUNCTION_LINE("Connect to network value not found, attempting to migrate/create");
bool skipPatches = false;
if (WUPSStorageAPI::Get<bool>("skipPatches", skipPatches) == WUPS_STORAGE_ERROR_SUCCESS) {
// Migrate old config value
Config::connect_to_network = !skipPatches;
WUPSStorageAPI::DeleteItem("skipPatches");
}
// Add the value to the storage if it's missing.
res = WUPSStorageAPI::Store<bool>("connect_to_network", connect_to_network);
if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res);
}
else if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res);
// Save storage
res = WUPSStorageAPI::SaveStorage();
if (res != WUPS_STORAGE_ERROR_SUCCESS) return report_storage_error(res);
}

24
plugin/src/config.h Normal file
View file

@ -0,0 +1,24 @@
//
// Created by ash on 10/12/22.
//
#ifndef INKAY_CONFIG_H
#define INKAY_CONFIG_H
class Config {
public:
static void Init();
// wups config items
static bool connect_to_network;
// private stuff
static bool need_relaunch;
// private stuff
static bool is_wiiu_menu;
static bool unregister_task_item_pressed;
};
#endif //INKAY_CONFIG_H

71
plugin/src/main.cpp Normal file
View file

@ -0,0 +1,71 @@
/* Copyright 2022 Pretendo Network contributors <pretendo.network>
Copyright 2022 Ash Logan <ash@heyquark.com>
Copyright 2019 Maschell
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 <http://www.gnu.org/licenses/>.
*/
#include <wups.h>
#include <notifications/notifications.h>
#include <utils/logger.h>
#include "config.h"
#include "module.h"
#define INKAY_VERSION "v3.0.0"
/**
Mandatory plugin information.
If not set correctly, the loader will refuse to use the plugin.
**/
WUPS_PLUGIN_NAME("Inkay");
WUPS_PLUGIN_DESCRIPTION("Pretendo Network Patcher");
WUPS_PLUGIN_VERSION(INKAY_VERSION);
WUPS_PLUGIN_AUTHOR("Pretendo contributors");
WUPS_PLUGIN_LICENSE("GPLv3");
WUPS_USE_STORAGE("inkay");
WUPS_USE_WUT_DEVOPTAB();
INITIALIZE_PLUGIN() {
WHBLogCafeInit();
WHBLogUdpInit();
Config::Init();
if (NotificationModule_InitLibrary() != NOTIFICATION_MODULE_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE("NotificationModule_InitLibrary failed");
}
// if using pretendo then (try to) apply the ssl patches
Inkay_Initialize(Config::connect_to_network);
}
DEINITIALIZE_PLUGIN() {
Inkay_Finalize();
NotificationModule_DeInitLibrary();
WHBLogCafeDeinit();
WHBLogUdpDeinit();
}
ON_APPLICATION_START() {
// Tell the module the plugin is running!
Inkay_SetPluginRunning();
}
ON_APPLICATION_ENDS() {
}

93
plugin/src/module.cpp Normal file
View file

@ -0,0 +1,93 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
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 <http://www.gnu.org/licenses/>.
*/
#include "module.h"
#include "Notification.h"
#include "utils/logger.h"
#include "sysconfig.h"
#include "lang.h"
#include <coreinit/dynload.h>
static OSDynLoad_Module module;
static void (*moduleInitialize)(bool) = nullptr;
static InkayStatus (*moduleGetStatus)() = nullptr;
static void (*moduleSetPluginRunning)() = nullptr;
static const char *get_module_not_found_message() {
return get_config_strings(get_system_language()).module_not_found.data();
}
static const char *get_module_init_not_found_message() {
return get_config_strings(get_system_language()).module_init_not_found.data();
}
void Inkay_Initialize(bool apply_patches) {
if (module) {
return;
}
if (OSDynLoad_Acquire("inkay", &module) != OS_DYNLOAD_OK) {
DEBUG_FUNCTION_LINE("Failed to acquire module");
ShowNotification(get_module_not_found_message());
return;
}
if (OSDynLoad_FindExport(module, OS_DYNLOAD_EXPORT_FUNC, "Inkay_Initialize", reinterpret_cast<void * *>(&moduleInitialize)) != OS_DYNLOAD_OK) {
DEBUG_FUNCTION_LINE("Failed to find initialization function");
ShowNotification(get_module_init_not_found_message());
OSDynLoad_Release(module);
return;
}
moduleInitialize(apply_patches);
}
void Inkay_Finalize() {
if (module) {
OSDynLoad_Release(module);
moduleInitialize = nullptr;
moduleGetStatus = nullptr;
moduleSetPluginRunning = nullptr;
}
}
InkayStatus Inkay_GetStatus() {
if (!module) {
return InkayStatus::Error;
}
if (!moduleGetStatus && OSDynLoad_FindExport(module, OS_DYNLOAD_EXPORT_FUNC, "Inkay_GetStatus", reinterpret_cast<void * *>(&moduleGetStatus)) != OS_DYNLOAD_OK) {
DEBUG_FUNCTION_LINE("Failed to find status function");
return InkayStatus::Error;
}
return moduleGetStatus();
}
void Inkay_SetPluginRunning() {
if (!module) {
return;
}
if (!moduleSetPluginRunning && OSDynLoad_FindExport(module, OS_DYNLOAD_EXPORT_FUNC, "Inkay_SetPluginRunning", reinterpret_cast<void * *>(&moduleSetPluginRunning)) != OS_DYNLOAD_OK) {
DEBUG_FUNCTION_LINE("Failed to find \"Inkay_SetPluginRunning\" function");
return;
}
moduleSetPluginRunning();
}

30
plugin/src/module.h Normal file
View file

@ -0,0 +1,30 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
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 <http://www.gnu.org/licenses/>.
*/
#pragma once
enum class InkayStatus {
Uninitialized, ///< The module isn't initialized
Nintendo, ///< The module is initialized but hasn't applied any patches
Pretendo, ///< The module is initialized and has applied the Pretendo patches
Error = -1 ///< Failed to retrieve the module status
};
void Inkay_Initialize(bool apply_patches);
void Inkay_Finalize();
InkayStatus Inkay_GetStatus();
void Inkay_SetPluginRunning();

36
plugin/src/utils/logger.h Normal file
View file

@ -0,0 +1,36 @@
#pragma once
#include <string.h>
#include <whb/log.h>
#include <whb/log_module.h>
#include <whb/log_cafe.h>
#include <whb/log_udp.h>
#ifdef __cplusplus
extern "C" {
#endif
#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__)
#define OSFATAL_FUNCTION_LINE(FMT, ARGS...)do { \
OSFatal_printf("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
} while (0)
#define DEBUG_FUNCTION_LINE(FMT, ARGS...)do { \
WHBLogPrintf("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
} while (0);
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...)do { \
WHBLogWritef("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
} while (0);
#ifdef DEBUG
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) DEBUG_FUNCTION_LINE(FMT, ##ARGS)
#else
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0);
#endif
#ifdef __cplusplus
}
#endif

View file

@ -11,13 +11,13 @@ extern "C" {
namespace nn::boss {
class Task {
public:
nn::act::PersistentId persistentId;
uint32_t unk1;
char task_name[8];
uint64_t title_id;
uint32_t unk2;
uint32_t unk3;
public:
nn::act::PersistentId persistentId;
uint32_t unk1;
char task_name[8];
uint64_t title_id;
uint32_t unk2;
uint32_t unk3;
};
static_assert(sizeof(Task) == 32, "nn::boss::Task must be 32 bytes.");
@ -26,15 +26,15 @@ namespace nn::boss {
extern "C" uint32_t Initialize__Q2_2nn4bossFv();
extern "C" uint32_t Finalize__Q2_2nn4bossFv();
extern "C" void __ct__Q3_2nn4boss4TaskFv(nn::boss::Task* task);
extern "C" uint32_t Initialize__Q3_2nn4boss4TaskFPCcUi(nn::boss::Task* task, char const *, unsigned int);
extern "C" uint32_t Unregister__Q3_2nn4boss4TaskFv(nn::boss::Task* task);
extern "C" void __dt__Q3_2nn4boss4TaskFv(nn::boss::Task* task);
extern "C" uint32_t StartScheduling__Q3_2nn4boss4TaskFb(nn::boss::Task* task, bool queueTaskOnCall);
extern "C" uint32_t GetState__Q3_2nn4boss4TaskCFPUi(nn::boss::Task* task, uint32_t *outExecCount);
extern "C" uint32_t Run__Q3_2nn4boss4TaskFb(nn::boss::Task* task, bool unk);
extern "C" uint32_t UpdateIntervalSec__Q3_2nn4boss4TaskFUi(nn::boss::Task* task, uint32_t seconds);
extern "C" bool IsRegistered__Q3_2nn4boss4TaskCFv(nn::boss::Task* task);
extern "C" void __ct__Q3_2nn4boss4TaskFv(nn::boss::Task *task);
extern "C" uint32_t Initialize__Q3_2nn4boss4TaskFPCcUi(nn::boss::Task *task, char const *, unsigned int);
extern "C" uint32_t Unregister__Q3_2nn4boss4TaskFv(nn::boss::Task *task);
extern "C" void __dt__Q3_2nn4boss4TaskFv(nn::boss::Task *task);
extern "C" uint32_t StartScheduling__Q3_2nn4boss4TaskFb(nn::boss::Task *task, bool queueTaskOnCall);
extern "C" uint32_t GetState__Q3_2nn4boss4TaskCFPUi(nn::boss::Task *task, uint32_t *outExecCount);
extern "C" uint32_t Run__Q3_2nn4boss4TaskFb(nn::boss::Task *task, bool unk);
extern "C" uint32_t UpdateIntervalSec__Q3_2nn4boss4TaskFUi(nn::boss::Task *task, uint32_t seconds);
extern "C" bool IsRegistered__Q3_2nn4boss4TaskCFv(nn::boss::Task *task);
#ifdef __cplusplus
}

View file

@ -1,39 +0,0 @@
#include "Notification.h"
#include <coreinit/cache.h>
#include <coreinit/thread.h>
#include <coreinit/title.h>
#include <notifications/notification_defines.h>
#include <notifications/notifications.h>
#include <thread>
static std::unique_ptr<std::thread> sShowHintThread;
static bool sShutdownHintThread = false;
void ShowNotification(const char * notification) {
// Wait for notification module to be ready
bool isOverlayReady = false;
while (!sShutdownHintThread && NotificationModule_IsOverlayReady(&isOverlayReady) == NOTIFICATION_MODULE_RESULT_SUCCESS && !isOverlayReady)
OSSleepTicks(OSMillisecondsToTicks(16));
if (sShutdownHintThread || !isOverlayReady) return;
NotificationModuleStatus err = NotificationModule_SetDefaultValue(NOTIFICATION_MODULE_NOTIFICATION_TYPE_INFO, NOTIFICATION_MODULE_DEFAULT_OPTION_DURATION_BEFORE_FADE_OUT, 15.0f);
if(err != NOTIFICATION_MODULE_RESULT_SUCCESS) return;
NotificationModule_AddInfoNotification(notification);
}
void StartNotificationThread(const char * value) {
uint64_t titleID = OSGetTitleID();
if (titleID == 0x0005001010040000L || titleID == 0x0005001010040100L || titleID == 0x0005001010040200L) {
sShutdownHintThread = false;
sShowHintThread = std::make_unique<std::thread>(ShowNotification, value);
}
}
void StopNotificationThread() {
if (sShowHintThread != nullptr) {
sShutdownHintThread = true;
OSMemoryBarrier();
sShowHintThread->join();
sShowHintThread.reset();
}
}

View file

@ -1,7 +0,0 @@
#pragma once
void ShowNotification(const char * notification);
void StartNotificationThread(const char * value);
void StopNotificationThread();

View file

@ -1,159 +1,24 @@
//
// Created by ash on 10/12/22.
//
/* Copyright 2022 Pretendo Network contributors <pretendo.network>
Copyright 2022 Ash Logan <ash@heyquark.com>
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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "wut_extra.h"
#include "utils/logger.h"
#include <wups.h>
#include <wups/storage.h>
#include <wups/config.h>
#include <wups/config/WUPSConfigItemBoolean.h>
#include <coreinit/title.h>
#include <coreinit/launch.h>
#include <sysapp/title.h>
#include <sysapp/launch.h>
#include <nn/act.h>
bool Config::connect_to_network = true;
bool Config::need_relaunch = false;
bool Config::unregister_task_item_pressed = false;
bool Config::is_wiiu_menu = false;
void Config::Init() {
WUPSStorageError storageRes = WUPS_OpenStorage();
if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to open storage %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes);
}
else {
// Try to get value from storage
if ((storageRes = WUPS_GetBool(nullptr, "connect_to_network", &connect_to_network)) == WUPS_STORAGE_ERROR_NOT_FOUND) {
bool skipPatches = false;
if ((storageRes = WUPS_GetBool(nullptr, "skipPatches", &skipPatches)) != WUPS_STORAGE_ERROR_NOT_FOUND) {
// Migrate old config value
connect_to_network = !skipPatches;
}
// Add the value to the storage if it's missing.
if (WUPS_StoreBool(nullptr, "connect_to_network", connect_to_network) != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to store bool");
}
}
else if (storageRes != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to get bool %s (%d)", WUPS_GetStorageStatusStr(storageRes), storageRes);
}
// Close storage
if (WUPS_CloseStorage() != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to close storage");
}
}
uint64_t current_title_id = OSGetTitleID();
uint64_t wiiu_menu_tid = _SYSGetSystemApplicationTitleId(SYSTEM_APP_ID_WII_U_MENU);
Config::is_wiiu_menu = (current_title_id == wiiu_menu_tid);
}
static void connect_to_network_changed(ConfigItemBoolean* item, bool new_value) {
DEBUG_FUNCTION_LINE("New value in skipPatchesChanged: %d", new_value);
if (new_value != Config::connect_to_network) {
Config::need_relaunch = true;
}
Config::connect_to_network = new_value;
WUPS_StoreInt(nullptr, "connect_to_network", Config::connect_to_network);
}
static int32_t unregister_task_item_get_display_value(void *context, char *out_buf, int32_t out_size) {
if(!Config::is_wiiu_menu)
strncpy(out_buf, "From WiiU menu only", out_size);
else
strncpy(out_buf, Config::unregister_task_item_pressed ? "Restart to apply" : "Press A", out_size);
return 0;
}
static void unregister_task_item_pressed_cb(void *context, WUPSConfigButtons button) {
if (!Config::unregister_task_item_pressed && Config::is_wiiu_menu && button == WUPS_CONFIG_BUTTON_A) {
nn::act::Initialize();
Initialize__Q2_2nn4bossFv();
for (uint8_t i = 1; i <= nn::act::GetNumOfAccounts(); i++)
{
if (nn::act::IsSlotOccupied(i) == true)
{
if (nn::act::IsNetworkAccountEx(i) == true)
{
nn::boss::Task task;
nn::act::PersistentId persistentId = nn::act::GetPersistentIdEx(i);
__ct__Q3_2nn4boss4TaskFv(&task);
Initialize__Q3_2nn4boss4TaskFPCcUi(&task, "oltopic", persistentId);
uint32_t res = Unregister__Q3_2nn4boss4TaskFv(&task);
WHBLogPrintf("Unregistered oltopic for: SlotNo %d | Persistent ID %08x -> 0x%08x", i, persistentId, res);
}
}
}
Finalize__Q2_2nn4bossFv();
nn::act::Finalize();
Config::unregister_task_item_pressed = !Config::unregister_task_item_pressed;
Config::need_relaunch = true;
}
}
static bool unregister_task_item_is_movement_allowed(void *context) {
return true;
}
WUPS_GET_CONFIG() {
// We open the storage so we can persist the configuration the user did.
if (WUPS_OpenStorage() != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to open storage");
return 0;
}
WUPSConfigHandle config;
WUPSConfig_CreateHandled(&config, "Inkay");
WUPSConfigCategoryHandle patching_cat;
WUPSConfig_AddCategoryByNameHandled(config, "Patching", &patching_cat);
WUPSConfigItemBoolean_AddToCategoryHandled(config, patching_cat, "connect_to_network", "Connect to the Pretendo network", Config::connect_to_network, &connect_to_network_changed);
WUPSConfigCategoryHandle boss_cat;
WUPSConfig_AddCategoryByNameHandled(config, "BOSS settings", &boss_cat);
WUPSConfigCallbacks_t unregisterTasksItemCallbacks = {
.getCurrentValueDisplay = unregister_task_item_get_display_value,
.getCurrentValueSelectedDisplay = unregister_task_item_get_display_value,
.onSelected = nullptr,
.restoreDefault = nullptr,
.isMovementAllowed = unregister_task_item_is_movement_allowed,
.callCallback = nullptr,
.onButtonPressed = unregister_task_item_pressed_cb,
.onDelete = nullptr
};
WUPSConfigItemHandle unregisterTasksItem;
WUPSConfigItem_Create(&unregisterTasksItem, "unregister_task_item", "Unregister Wara Wara Plaza BOSS tasks", unregisterTasksItemCallbacks, &Config::unregister_task_item_pressed);
WUPSConfigCategory_AddItem(boss_cat, unregisterTasksItem);
return config;
}
WUPS_CONFIG_CLOSED() {
// Save all changes
if (WUPS_CloseStorage() != WUPS_STORAGE_ERROR_SUCCESS) {
DEBUG_FUNCTION_LINE("Failed to close storage");
}
if (Config::need_relaunch) {
// Need to reload the console so the patches reset
OSForceFullRelaunch();
SYSLaunchMenu();
Config::need_relaunch = false;
}
}
bool Config::connect_to_network = false;
bool Config::initialized = false;
bool Config::shown_warning = false;
bool Config::plugin_is_loaded = false;
bool Config::block_initialize = false;

View file

@ -7,18 +7,16 @@
class Config {
public:
static void Init();
// wups config items
static bool connect_to_network;
// private stuff
static bool need_relaunch;
// private stuff
static bool is_wiiu_menu;
static bool initialized;
static bool unregister_task_item_pressed;
static bool shown_warning;
static bool plugin_is_loaded;
static bool block_initialize;
};
#endif //INKAY_CONFIG_H

25
src/export.h Normal file
View file

@ -0,0 +1,25 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
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 <http://www.gnu.org/licenses/>.
*/
#pragma once
enum class InkayStatus {
Uninitialized, ///< The module isn't initialized
Nintendo, ///< The module is initialized but hasn't applied any patches
Pretendo, ///< The module is initialized and has applied the Pretendo patches
Error = -1 ///< Failed to retrieve the module status
};

View file

@ -1,8 +1,7 @@
#ifndef _PATCHER_H
#define _PATCHER_H
typedef struct URL_Patch
{
typedef struct URL_Patch {
unsigned int address;
char url[80];
} URL_Patch;
@ -36,6 +35,7 @@ static const URL_Patch url_patches[] = {
{0xE22B3FFC, "https://nus.c.shop.pretendo.cc/nus/services/NetUpdateSOAP"},
{0xE229DE0C, "n.app.pretendo.cc"},
//nim-boss .bss
{0xE24B8A24, "https://nppl.app.pretendo.cc/p01/policylist/1/1/UNK"},
{0xE31930D4, "https://%s%saccount.pretendo.cc/v%u/api/"}
};

13
src/lang/de_DE.lang Normal file
View file

@ -0,0 +1,13 @@
.plugin_name="Inkay"
,.network_category="Netzwerkauswahl"
,.connect_to_network_setting="Verbinde zum Pretendo Network"
,.other_category="Andere Einstellungen"
,.reset_wwp_setting="Wara Wara Plaza zurücksetzen"
,.press_a_action="Drücke A"
,.restart_to_apply_action="Neustarten zum Anwenden"
,.need_menu_action="Nur vom Wii U-Menü aus"
,.using_nintendo_network="Nutze Nintendo Network"
,.using_pretendo_network="Nutze Pretendo Network"
,.multiplayer_port_display="Nutze UDP Port %hu für Mehrspieler"
,.module_not_found="Pretendo Patch fehlgeschlagen - nutze Aroma Updater zum Reparieren (686-1001 Module missing)"
,.module_init_not_found="Pretendo Patch fehlgeschlagen - nutze Aroma Updater zum Reparieren (686-1002 Module init)"

15
src/lang/en@uwu.lang Normal file
View file

@ -0,0 +1,15 @@
.plugin_name="Inky ^w^"
,.network_category="Newowk sewectiown! :3"
,.connect_to_network_setting="use dah pretender!!!!"
,.other_category="oth3r stuffzz"
,.reset_wwp_setting="warah wara go BYEBYE x3c"
,.press_a_action="Press A plz?"
,.restart_to_apply_action="ima restart ur consoel >:3c"
,.need_menu_action="no menu?? only menu -w-"
,.using_nintendo_network="meanie network!!!"
,.using_pretendo_network="pretender netw3rk >:D"
,.multiplayer_port_display="mah secret word is %hu... ur modem will know"
,.module_not_found="oh noez! pls get aroma updater help :( (686-1001 Module missing)"
,.module_init_not_found="oh noez! pls get aroma updater help :( (686-1002 Module init)"

13
src/lang/en_US.lang Normal file
View file

@ -0,0 +1,13 @@
.plugin_name = "Inkay"
,.network_category = "Network selection"
,.connect_to_network_setting = "Connect to the Pretendo network"
,.other_category = "Other settings"
,.reset_wwp_setting = "Reset Wara Wara Plaza"
,.press_a_action = "Press A"
,.restart_to_apply_action = "Restart to apply"
,.need_menu_action = "From WiiU menu only"
,.using_nintendo_network = "Using Nintendo Network"
,.using_pretendo_network = "Using Pretendo Network"
,.multiplayer_port_display = "Using UDP port %hu for multiplayer"
,.module_not_found = "Pretendo patch failed - use Aroma Updater to repair (686-1001 Module missing)"
,.module_init_not_found = "Pretendo patch failed - use Aroma Updater to repair (686-1002 Module init)"

13
src/lang/es_ES.lang Normal file
View file

@ -0,0 +1,13 @@
.plugin_name="Inkay"
,.network_category="Seleccionar red"
,.connect_to_network_setting="Conectar a la red Pretendo"
,.other_category="Otros ajustes"
,.reset_wwp_setting="Restablecer Plaza Wara Wara"
,.press_a_action="Pulsa A"
,.restart_to_apply_action="Reiniciar para aplicar"
,.need_menu_action="Solo desde el Menú de Wii U"
,.using_nintendo_network="Usando Nintendo Network"
,.using_pretendo_network="Usando Pretendo Network"
,.multiplayer_port_display="Usando puerto UDP %hu para multijugador"
,.module_not_found="Falló el parche Pretendo - usa Aroma Updater para reparar (686-1001 Falta un módulo)"
,.module_init_not_found="Falló el parche Pretendo - usa Aroma Updater para reparar (686-1002 Inicialización del módulo)"

13
src/lang/fr_FR.lang Normal file
View file

@ -0,0 +1,13 @@
.plugin_name="Inkay"
,.network_category="Sélection du réseau"
,.connect_to_network_setting="Connexion au Pretendo Network"
,.other_category="Autres paramètres"
,.reset_wwp_setting="Réinitialiser la place WaraWara"
,.press_a_action="Appuyez sur A"
,.restart_to_apply_action="(prendra effet au redémarrage)"
,.need_menu_action="(retournez d'abord au menu Wii U)"
,.using_nintendo_network="Réseau sélectionné : Nintendo Network"
,.using_pretendo_network="Réseau sélectionné : Pretendo Network"
,.multiplayer_port_display="Le port UDP %hu sera utilisé pour le multijoueur"
,.module_not_found="Le patch Pretendo a échoué, veuillez le réparer avec Aroma Updater (686-1001 Module introuvable)"
,.module_init_not_found="Le patch Pretendo a échoué, veuillez le réparer avec Aroma Updater (686-1002 Point d'entrée du module)"

13
src/lang/it_IT.lang Normal file
View file

@ -0,0 +1,13 @@
.plugin_name="Inkay"
,.network_category="Seleziona la rete"
,.connect_to_network_setting="Connettiti alla rete Pretendo"
,.other_category="Altre impostazioni"
,.reset_wwp_setting="Ripristina Wara Wara Plaza"
,.press_a_action="Premi A"
,.restart_to_apply_action="Riavvia per applicare"
,.need_menu_action="Solo dal menu WiiU"
,.using_nintendo_network="In uso Nintendo Network"
,.using_pretendo_network="In uso Pretendo Network"
,.multiplayer_port_display="È in uso la porta UDP %hu per il multigiocatore"
,.module_not_found="Patch di Pretendo fallita - usa Aroma Updater per correggere (686-1001 Modulo mancante)"
,.module_init_not_found="Patch di Pretendo fallita - usa Aroma Updater per correggere (686-1002 Module init)"

13
src/lang/ja_JP.lang Normal file
View file

@ -0,0 +1,13 @@
.plugin_name="Inkay"
,.network_category="ネットワークの選択"
,.connect_to_network_setting="Pretendoネットワークに接続する"
,.other_category="その他の設定"
,.reset_wwp_setting="わらわら広場をリセット"
,.press_a_action="Aボタンを押そう"
,.restart_to_apply_action="再起動後に反映されます"
,.need_menu_action="Wii Uメニューからのみ実行可能"
,.using_nintendo_network="選択済み:ニンテンドーネットワーク"
,.using_pretendo_network="選択済みPretendo ネットワーク"
,.multiplayer_port_display="マルチプレイに UDPポート %hu を使用します"
,.module_not_found="Pretendo パッチに失敗しました。Aroma Updaterを使用して解決してみてください。(686-1001 Module missing)"
,.module_init_not_found="Pretendo パッチに失敗しました。Aroma Updaterを使用して解決してみてください。(686-1002 Module init)"

13
src/lang/nl_NL.lang Normal file
View file

@ -0,0 +1,13 @@
.plugin_name="Inkay"
,.network_category="Netwerkselectie"
,.connect_to_network_setting="Verbind met het Pretendo-netwerk"
,.other_category="Overige instellingen"
,.reset_wwp_setting="Reset het Wara Wara Plaza"
,.press_a_action="Druk A"
,.restart_to_apply_action="Herstart om toe te passen"
,.need_menu_action="Alleen vanuit het WiiU-menu"
,.using_nintendo_network="Nintendo Network wordt gebruikt"
,.using_pretendo_network="Pretendo Network wordt gebruikt"
,.multiplayer_port_display = "Using UDP port %hu for multiplayer"
,.module_not_found="Pretendo patch mislukt - gebruik Aroma Updater om het te herstellen (686-1001 Module ontbreekt)"
,.module_init_not_found="Pretendo patch mislukt - gebruik Aroma Updater om het te herstellen (686-1002 Module init)"

13
src/lang/pt_BR.lang Normal file
View file

@ -0,0 +1,13 @@
.plugin_name="Inkay"
,.network_category="Selecionar rede"
,.connect_to_network_setting="Conecta-se à Pretendo Network"
,.other_category="Outras configurações"
,.reset_wwp_setting="Resetar Wara Wara Plaza"
,.press_a_action="Aperte A"
,.restart_to_apply_action="Reinicie para aplicar"
,.need_menu_action="Apenas no menu do Wii U"
,.using_nintendo_network="Usando Nintendo Network"
,.using_pretendo_network="Usando Pretendo Network"
,.multiplayer_port_display="Usando UDP port %hu para a jogatina multiplayer"
,.module_not_found="Atualização do Pretendo falhou - use o Aroma Updater para corrigir (686-1001 Módulo faltando)"
,.module_init_not_found="Atualização do Pretendo falhou - use o Aroma Updater para corrigir (686-1002 Módulo de início)"

13
src/lang/ru_RU.lang Normal file
View file

@ -0,0 +1,13 @@
.plugin_name="Inkay"
,.network_category="Выбор сети"
,.connect_to_network_setting="Подключиться к Pretendo Network"
,.other_category="Другие настройки"
,.reset_wwp_setting="Сбросить Wara Wara Plaza"
,.press_a_action="Нажмите A"
,.restart_to_apply_action="Перезагрузите для применения изменений"
,.need_menu_action="Только из меню Wii U"
,.using_nintendo_network="Используется Nintendo Network"
,.using_pretendo_network="Используется Pretendo Network"
,.multiplayer_port_display="Используем UDP-порт %hu для сетевой игры"
,.module_not_found="Ошибка установки патча Pretendo - для восстановления, запустите Aroma Updater (686-1001 Модуль отсутствует)"
,.module_init_not_found="Ошибка установки патча Pretendo - для восстановления, запустите Aroma Updater (686-1002 Инициализация модуля)"

13
src/lang/zh_CN.lang Normal file
View file

@ -0,0 +1,13 @@
.plugin_name="Inkay"
,.network_category="选择网络"
,.connect_to_network_setting="连接到Pretendo network"
,.other_category="其他设置"
,.reset_wwp_setting="重置Wara Wara Plaza"
,.press_a_action="请按 A"
,.restart_to_apply_action="重启以应用设置"
,.need_menu_action="仅来自WiiU Menu"
,.using_nintendo_network="使用 Nintendo Network"
,.using_pretendo_network="使用 Pretendo Network"
,.multiplayer_port_display="多人游戏使用 UDP 端口 %hu"
,.module_not_found="Pretendo 补丁失败 - 使用 Aroma Updater 修复686-1001 模块丢失)"
,.module_init_not_found="Pretendo 补丁失败 - 使用 Aroma Updater 修复686-1002 模块初始化)"

13
src/lang/zh_Hant.lang Normal file
View file

@ -0,0 +1,13 @@
.plugin_name = "Inkay"
,.network_category = "選擇網路"
,.connect_to_network_setting = "連接到Pretendo network"
,.other_category = "其他設定"
,.reset_wwp_setting = "重置Wara Wara Plaza"
,.press_a_action = "請按 A"
,.restart_to_apply_action = "重啓以套用設定"
,.need_menu_action = "僅來自WiiU Menu"
,.using_nintendo_network = "使用 Nintendo Network"
,.using_pretendo_network = "使用 Pretendo Network"
,.multiplayer_port_display = "Using UDP port %hu for multiplayer"
,.module_not_found = "Pretendo patch failed - use Aroma Updater to repair (686-1001 Module missing)"
,.module_init_not_found = "Pretendo patch failed - use Aroma Updater to repair (686-1002 Module init)"

View file

@ -2,86 +2,90 @@
Copyright 2022 Ash Logan <ash@heyquark.com>
Copyright 2019 Maschell
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <wups.h>
#include <optional>
#include <nsysnet/nssl.h>
#include <coreinit/cache.h>
#include <coreinit/dynload.h>
#include <coreinit/mcp.h>
#include <coreinit/memory.h>
#include <coreinit/memorymap.h>
#include <coreinit/memexpheap.h>
#include <notifications/notifications.h>
#include <utils/logger.h>
#include "export.h"
#include "iosu_url_patches.h"
#include "config.h"
#include "Notification.h"
#include "patches/olv_urls.h"
#include "patches/game_matchmaking.h"
#include <coreinit/filesystem.h>
#include <cstring>
#include <string>
#include <nn/erreula/erreula_cpp.h>
#include <nn/act/client_cpp.h>
#include <wums.h>
#include <coreinit/dynload.h>
#include <coreinit/mcp.h>
#include <notifications/notifications.h>
#include <utils/logger.h>
#include <string>
#include <optional>
#include <cstring>
#include <cstdint>
#include <curl/curl.h>
#include "ca_pem.h"
#include <gx2/surface.h>
#define INKAY_VERSION "v2.4"
#define INKAY_VERSION "v3.0.0"
/**
Mandatory plugin information.
If not set correctly, the loader will refuse to use the plugin.
**/
WUPS_PLUGIN_NAME("Inkay");
WUPS_PLUGIN_DESCRIPTION("Pretendo Network Patcher");
WUPS_PLUGIN_VERSION(INKAY_VERSION);
WUPS_PLUGIN_AUTHOR("Pretendo contributors");
WUPS_PLUGIN_LICENSE("ISC");
WUMS_MODULE_EXPORT_NAME("inkay");
WUMS_MODULE_DESCRIPTION("Pretendo Network Patcher");
WUMS_MODULE_VERSION(INKAY_VERSION);
WUMS_MODULE_AUTHOR("Pretendo contributors");
WUMS_MODULE_LICENSE("GPLv3");
WUPS_USE_STORAGE("inkay");
WUPS_USE_WUT_DEVOPTAB();
WUMS_DEPENDS_ON(homebrew_functionpatcher);
WUMS_DEPENDS_ON(homebrew_kernel);
WUMS_DEPENDS_ON(homebrew_notifications);
WUMS_USE_WUT_DEVOPTAB();
#include <kernel/kernel.h>
#include <mocha/mocha.h>
#include <function_patcher/function_patching.h>
#include "patches/account_settings.h"
#include "patches/dns_hooks.h"
#include "patches/eshop_applet.h"
#include "patches/olv_applet.h"
#include "patches/game_peertopeer.h"
#include "sysconfig.h"
#include "lang.h"
//thanks @Gary#4139 :p
static void write_string(uint32_t addr, const char* str)
{
static void write_string(uint32_t addr, const char *str) {
int len = strlen(str) + 1;
int remaining = len % 4;
int num = len - remaining;
for (int i = 0; i < (num / 4); i++) {
Mocha_IOSUKernelWrite32(addr + i * 4, *(uint32_t*)(str + i * 4));
Mocha_IOSUKernelWrite32(addr + i * 4, *(uint32_t * )(str + i * 4));
}
if (remaining > 0) {
uint8_t buf[4];
Mocha_IOSUKernelRead32(addr + num, (uint32_t*)&buf);
Mocha_IOSUKernelRead32(addr + num, (uint32_t * ) & buf);
for (int i = 0; i < remaining; i++) {
buf[i] = *(str + num + i);
}
Mocha_IOSUKernelWrite32(addr + num, *(uint32_t*)&buf);
Mocha_IOSUKernelWrite32(addr + num, *(uint32_t * ) & buf);
}
}
@ -89,15 +93,89 @@ static bool is555(MCPSystemVersion version) {
return (version.major == 5) && (version.minor == 5) && (version.patch >= 5);
}
INITIALIZE_PLUGIN() {
WHBLogUdpInit();
static const char *get_nintendo_network_message() {
// TL note: "Nintendo Network" is a proper noun - "Network" is part of the name
// TL note: "Using" instead of "Connected" is deliberate - we don't know if a successful connection exists, we are
// only specifying what we'll *attempt* to connect to
return get_config_strings(get_system_language()).using_nintendo_network.data();
}
static const char *get_pretendo_message() {
// TL note: "Pretendo Network" is also a proper noun - though "Pretendo" alone can refer to us as a project
// TL note: "Using" instead of "Connected" is deliberate - we don't know if a successful connection exists, we are
// only specifying what we'll *attempt* to connect to
return get_config_strings(get_system_language()).using_pretendo_network.data();
}
static void Inkay_SetPluginRunning() {
Config::plugin_is_loaded = true;
}
static InkayStatus Inkay_GetStatus() {
if (!Config::initialized)
return InkayStatus::Uninitialized;
if (Config::connect_to_network) {
return InkayStatus::Pretendo;
} else {
return InkayStatus::Nintendo;
}
}
static void Inkay_Initialize(bool apply_patches) {
if (Config::initialized)
return;
if (Config::block_initialize) {
ShowNotification("Cannot load Inkay while the system is running. Please restart the console");
return;
}
// if using pretendo then (try to) apply the ssl patches
if (apply_patches) {
Config::connect_to_network = true;
if (is555(get_console_os_version())) {
Mocha_IOSUKernelWrite32(0xE1019F78, 0xE3A00001); // mov r0, #1
} else {
Mocha_IOSUKernelWrite32(0xE1019E84, 0xE3A00001); // mov r0, #1
}
for (const auto &patch: url_patches) {
write_string(patch.address, patch.url);
}
// IOS-NIM-BOSS GlobalPolicyList->state: poking this forces a refresh after we changed the url
Mocha_IOSUKernelWrite32(0xE24B3D90, 4);
DEBUG_FUNCTION_LINE_VERBOSE("Pretendo URL and NoSSL patches applied successfully.");
ShowNotification(get_pretendo_message());
Config::initialized = true;
} else {
DEBUG_FUNCTION_LINE_VERBOSE("Pretendo URL and NoSSL patches skipped.");
ShowNotification(get_nintendo_network_message());
Config::initialized = true;
return;
}
if (FunctionPatcher_InitLibrary() == FUNCTION_PATCHER_RESULT_SUCCESS) {
patchDNS();
patchEshop();
patchOlvApplet();
patchAccountSettings();
install_matchmaking_patches();
} else {
DEBUG_FUNCTION_LINE("FunctionPatcher_InitLibrary failed");
}
}
WUMS_INITIALIZE() {
WHBLogCafeInit();
WHBLogUdpInit();
Config::Init();
auto res = Mocha_InitLibrary();
if (res != MOCHA_RESULT_SUCCESS) {
if (const auto res = Mocha_InitLibrary(); res != MOCHA_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE("Mocha init failed with code %d!", res);
return;
}
@ -105,66 +183,56 @@ INITIALIZE_PLUGIN() {
if (NotificationModule_InitLibrary() != NOTIFICATION_MODULE_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE("NotificationModule_InitLibrary failed");
}
//get os version
MCPSystemVersion os_version;
int mcp = MCP_Open();
int ret = MCP_GetSystemVersion(mcp, &os_version);
if (ret < 0) {
DEBUG_FUNCTION_LINE("getting system version failed (%d/%d)!", mcp, ret);
os_version = (MCPSystemVersion) {
.major = 5, .minor = 5, .patch = 5, .region = 'E'
};
}
DEBUG_FUNCTION_LINE_VERBOSE("Running on %d.%d.%d%c",
os_version.major, os_version.minor, os_version.patch, os_version.region
);
if (Config::connect_to_network) {
if (is555(os_version)) {
Mocha_IOSUKernelWrite32(0xE1019F78, 0xE3A00001); // mov r0, #1
}
else {
Mocha_IOSUKernelWrite32(0xE1019E84, 0xE3A00001); // mov r0, #1
}
for (const auto& patch : url_patches) {
write_string(patch.address, patch.url);
}
DEBUG_FUNCTION_LINE_VERBOSE("Pretendo URL and NoSSL patches applied successfully.");
StartNotificationThread("Using Pretendo Network");
}
else {
DEBUG_FUNCTION_LINE_VERBOSE("Pretendo URL and NoSSL patches skipped.");
StartNotificationThread("Using Nintendo Network");
}
MCP_Close(mcp);
if (FunctionPatcher_InitLibrary() == FUNCTION_PATCHER_RESULT_SUCCESS) {
install_matchmaking_patches();
}
}
DEINITIALIZE_PLUGIN() {
WUMS_DEINITIALIZE() {
unpatchDNS();
unpatchEshop();
unpatchOlvApplet();
unpatchAccountSettings();
remove_matchmaking_patches();
WHBLogUdpDeinit();
Mocha_DeInitLibrary();
NotificationModule_DeInitLibrary();
FunctionPatcher_DeInitLibrary();
WHBLogCafeDeinit();
WHBLogUdpDeinit();
}
ON_APPLICATION_START() {
WHBLogUdpInit();
WHBLogCafeInit();
WUMS_APPLICATION_STARTS() {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay " INKAY_VERSION " starting up...\n");
DEBUG_FUNCTION_LINE_VERBOSE("Inkay " INKAY_VERSION " starting up\n");
// Reset plugin loaded flag
Config::plugin_is_loaded = false;
}
WUMS_ALL_APPLICATION_STARTS_DONE() {
// we need to do the patches here because otherwise the Config::connect_to_network flag might be set yet
setup_olv_libs();
peertopeer_patch();
matchmaking_notify_titleswitch();
hotpatchAccountSettings();
if (Config::initialized && !Config::plugin_is_loaded) {
DEBUG_FUNCTION_LINE("Inkay is running but the plugin got unloaded");
if (!Config::block_initialize) {
ShowNotification("Inkay module is still running. Please restart the console");
}
Config::shown_warning = true;
} else if (!Config::initialized && !Config::shown_warning) {
DEBUG_FUNCTION_LINE("Inkay module not initialized");
ShowNotification("Inkay module was not initialized. Ensure you have the Inkay plugin loaded");
Config::shown_warning = true;
}
if (!Config::initialized) {
Config::block_initialize = true;
}
}
ON_APPLICATION_ENDS() {
DEBUG_FUNCTION_LINE_VERBOSE("Unloading Inkay...\n");
StopNotificationThread();
WUMS_APPLICATION_ENDS() {
}
WUMS_EXPORT_FUNCTION(Inkay_Initialize);
WUMS_EXPORT_FUNCTION(Inkay_GetStatus);
WUMS_EXPORT_FUNCTION(Inkay_SetPluginRunning);

View file

@ -0,0 +1,170 @@
/* Copyright 2023 Pretendo Network contributors <pretendo.network>
Copyright 2023 Jemma Poffinbarger <jemma@jemsoftware.dev>
Copyright 2023 Ash Logan <ash@heyquark.com>
Copyright 2019 Maschell
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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "olv_urls.h"
#include "utils/logger.h"
#include "utils/replace_mem.h"
#include "inkay_config.h"
#include <function_patcher/function_patching.h>
#include <coreinit/filesystem.h>
#include <coreinit/title.h>
#include <vector>
#include <optional>
#include "ca_pem.h" // generated at buildtime
#define ACCOUNT_SETTINGS_TID_J 0x000500101004B000
#define ACCOUNT_SETTINGS_TID_U 0x000500101004B100
#define ACCOUNT_SETTINGS_TID_E 0x000500101004B200
struct account_settings_allowlist {
char scheme[16];
char domain[128];
char path[128]; // unverified
uint32_t flags;
};
constexpr struct account_settings_allowlist original_entry = {
.scheme = "https",
.domain = "account.nintendo.net",
.path = "",
.flags = 0x01010101,
};
constexpr struct account_settings_allowlist new_entry = {
.scheme = "https",
.domain = "account." NETWORK_BASEURL,
.path = "",
.flags = 0x01010101,
};
constexpr char wave_original[] = "saccount.nintendo.net";
constexpr char wave_new[] = "saccount." NETWORK_BASEURL;
static bool isAccountSettingsTitle() {
return (OSGetTitleID() != 0 && (
OSGetTitleID() == ACCOUNT_SETTINGS_TID_J ||
OSGetTitleID() == ACCOUNT_SETTINGS_TID_U ||
OSGetTitleID() == ACCOUNT_SETTINGS_TID_E
));
}
static std::optional<FSFileHandle> rootca_pem_handle{};
std::vector<PatchedFunctionHandle> account_patches;
DECL_FUNCTION(int, FSOpenFile_accSettings, FSClient *client, FSCmdBlock *block, char *path, const char *mode, uint32_t *handle,
int error) {
if(!isAccountSettingsTitle()) {
return real_FSOpenFile_accSettings(client, block, path, mode, handle, error);
}
if (!Config::connect_to_network) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: account settings patches skipped.");
return real_FSOpenFile_accSettings(client, block, path, mode, handle, error);
}
// Check for root CA file and take note of its handle
if (strcmp("vol/content/browser/rootca.pem", path) == 0) {
int ret = real_FSOpenFile_accSettings(client, block, path, mode, handle, error);
rootca_pem_handle = *handle;
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Found account settings CA, replacing...");
return ret;
}
return real_FSOpenFile_accSettings(client, block, path, mode, handle, error);
}
DECL_FUNCTION(FSStatus, FSReadFile_accSettings, FSClient *client, FSCmdBlock *block, uint8_t *buffer, uint32_t size, uint32_t count,
FSFileHandle handle, uint32_t unk1, uint32_t flags) {
if(!isAccountSettingsTitle()) {
return real_FSReadFile_accSettings(client, block, buffer, size, count, handle, unk1, flags);
}
if (size != 1) {
DEBUG_FUNCTION_LINE("Inkay: account settings CA replacement failed!");
}
if (rootca_pem_handle && *rootca_pem_handle == handle) {
strlcpy((char *) buffer, (const char *) ca_pem, size * count);
return (FSStatus) count;
}
return real_FSReadFile_accSettings(client, block, buffer, size, count, handle, unk1, flags);
}
DECL_FUNCTION(FSStatus, FSCloseFile_accSettings, FSClient *client, FSCmdBlock *block, FSFileHandle handle, FSErrorFlag errorMask) {
if(!isAccountSettingsTitle()) {
return real_FSCloseFile_accSettings(client, block, handle, errorMask);
}
if (handle == rootca_pem_handle) {
rootca_pem_handle.reset();
}
return real_FSCloseFile_accSettings(client, block, handle, errorMask);
}
bool patchAccountSettings() {
account_patches.reserve(3);
auto add_patch = [](function_replacement_data_t repl, const char *name) {
PatchedFunctionHandle handle = 0;
if (FunctionPatcher_AddFunctionPatch(&repl, &handle, nullptr) != FUNCTION_PATCHER_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE("Inkay/Account: Failed to patch %s!", name);
}
account_patches.push_back(handle);
};
add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSOpenFile_accSettings, LIBRARY_COREINIT, FSOpenFile, FP_TARGET_PROCESS_GAME), "FSOpenFile_accSettings");
add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSReadFile_accSettings, LIBRARY_COREINIT, FSReadFile, FP_TARGET_PROCESS_GAME), "FSReadFile_accSettings");
add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSCloseFile_accSettings, LIBRARY_COREINIT, FSCloseFile, FP_TARGET_PROCESS_GAME), "FSCloseFile_accSettings");
return true;
}
bool hotpatchAccountSettings() {
if(!isAccountSettingsTitle()) {
return false;
}
if (!Config::connect_to_network) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: account settings patches skipped.");
return false;
}
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: hewwo account settings!\n");
if (!replace(0x10000000, 0x10000000, wave_original, sizeof(wave_original), wave_new, sizeof(wave_new))) {
DEBUG_FUNCTION_LINE("Inkay: We didn't find the url /)>~<(\\");
return false;
}
if (!replace(0x10000000, 0x10000000, (const char *)&original_entry, sizeof(original_entry), (const char *)&new_entry, sizeof(new_entry))) {
DEBUG_FUNCTION_LINE("Inkay: We didn't find the whitelist /)>~<(\\");
return false;
}
return true;
}
void unpatchAccountSettings() {
for (const auto handle: account_patches) {
FunctionPatcher_RemoveFunctionPatch(handle);
}
account_patches.clear();
}

View file

@ -0,0 +1,20 @@
/* Copyright 2023 Pretendo Network contributors <pretendo.network>
Copyright 2023 Jemma Poffinbarger <jemma@jemsoftware.dev>
Copyright 2023 Ash Logan <ash@heyquark.com>
Copyright 2019 Maschell
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
bool patchAccountSettings();
bool hotpatchAccountSettings();
void unpatchAccountSettings();

View file

@ -1,14 +1,35 @@
#include <wups.h>
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
Copyright 2024 Ash Logan <ash@heyquark.com>
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 <http://www.gnu.org/licenses/>.
*/
#include <netdb.h>
#include "utils/logger.h"
#include "config.h"
#include "utils/logger.h"
#include "inkay_config.h"
#include <array>
#include <vector>
#include <function_patcher/function_patching.h>
const std::pair<const char *, const char *> dns_replacements[] = {
std::vector<PatchedFunctionHandle> dns_patches;
constexpr std::pair<const char *, const char *> dns_replacements[] = {
// NNCS servers
{ "nncs1.app.nintendowifi.net", "nncs1.app.pretendo.cc" },
{ "nncs2.app.nintendowifi.net", "nncs2.app.pretendo.cc" },
{ "nncs1.app.nintendowifi.net", "nncs1.app." NETWORK_BASEURL },
{ "nncs2.app.nintendowifi.net", "nncs2.app." NETWORK_BASEURL },
};
static const char * replace_dns_name(const char *dns_name) {
@ -25,10 +46,31 @@ static const char * replace_dns_name(const char *dns_name) {
DECL_FUNCTION(struct hostent *, gethostbyname, const char *dns_name) {
return real_gethostbyname(replace_dns_name(dns_name));
}
// might need a WUPS_MUST_REPLACE_FOR_PROCESS for Friends
WUPS_MUST_REPLACE(gethostbyname, WUPS_LOADER_LIBRARY_NSYSNET, gethostbyname);
DECL_FUNCTION(int, getaddrinfo, const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res) {
return real_getaddrinfo(replace_dns_name(node), service, hints, res);
}
WUPS_MUST_REPLACE(getaddrinfo, WUPS_LOADER_LIBRARY_NSYSNET, getaddrinfo);
void patchDNS() {
dns_patches.reserve(2);
auto add_patch = [](function_replacement_data_t repl, const char *name) {
PatchedFunctionHandle handle = 0;
if (FunctionPatcher_AddFunctionPatch(&repl, &handle, nullptr) != FUNCTION_PATCHER_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE("Inkay/DNS: Failed to patch %s!", name);
}
dns_patches.push_back(handle);
};
// might need a REPLACE_FUNCTION_FOR_PROCESS for Friends
add_patch(REPLACE_FUNCTION(gethostbyname, LIBRARY_NSYSNET, gethostbyname), "gethostbyname");
add_patch(REPLACE_FUNCTION(getaddrinfo, LIBRARY_NSYSNET, getaddrinfo), "getaddrinfo");
}
void unpatchDNS() {
for (auto handle: dns_patches) {
FunctionPatcher_RemoveFunctionPatch(handle);
}
dns_patches.clear();
}

20
src/patches/dns_hooks.h Normal file
View file

@ -0,0 +1,20 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
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 <http://www.gnu.org/licenses/>.
*/
#pragma once
void patchDNS();
void unpatchDNS();

View file

@ -0,0 +1,136 @@
/* Copyright 2023 Pretendo Network contributors <pretendo.network>
Copyright 2023 Ash Logan <ash@heyquark.com>
Copyright 2019 Maschell
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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include "olv_urls.h"
#include "utils/logger.h"
#include "utils/replace_mem.h"
#include "inkay_config.h"
#include <vector>
#include <function_patcher/function_patching.h>
#include <optional>
#include <coreinit/debug.h>
#include <coreinit/filesystem.h>
#include <nsysnet/nssl.h>
#include "ca_pem.h" // generated at buildtime
constexpr char wave_original[] = "https://ninja.wup.shop.nintendo.net/ninja/wood_index.html?";
constexpr char wave_new[] = "http://samurai.wup.shop." NETWORK_BASEURL "/ninja/wood_index.html?";
struct eshop_allowlist {
char scheme[16];
char domain[128];
char path[128]; // unverified
unsigned char flags[5];
};
constexpr struct eshop_allowlist original_entry = {
.scheme = "https",
.domain = "samurai.wup.shop.nintendo.net",
.path = "",
.flags = {1, 1, 1, 1, 0},
};
constexpr struct eshop_allowlist new_entry = {
.scheme = "http",
.domain = "samurai.wup.shop." NETWORK_BASEURL,
.path = "",
.flags = {1, 1, 1, 1, 0},
};
static std::optional<FSFileHandle> rootca_pem_handle{};
std::vector<PatchedFunctionHandle> eshop_patches;
DECL_FUNCTION(int, FSOpenFile_eShop, FSClient *client, FSCmdBlock *block, char *path, const char *mode, uint32_t *handle,
int error) {
const char *initialOma = "vol/content/initial.oma";
if (!Config::connect_to_network) {
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: eShop patches skipped.");
return real_FSOpenFile_eShop(client, block, path, mode, handle, error);
}
if (strcmp(initialOma, path) == 0) {
//below is a hacky (yet functional!) way to get Inkay to redirect URLs from the Miiverse applet
//we do it when loading this file since it should only load once, preventing massive lag spikes as it searches all of MEM2 xD
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: hewwo eShop!\n");
if (!replace(0x10000000, 0x10000000, wave_original, sizeof(wave_original), wave_new, sizeof(wave_new)))
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: We didn't find the url /)>~<(\\");
if (!replace(0x10000000, 0x10000000, (const char *)&original_entry, sizeof(original_entry), (const char *)&new_entry, sizeof(new_entry)))
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: We didn't find the whitelist /)>~<(\\");
// Check for root CA file and take note of its handle
} else if (strcmp("vol/content/browser/rootca.pem", path) == 0) {
int ret = real_FSOpenFile_eShop(client, block, path, mode, handle, error);
rootca_pem_handle = *handle;
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Found eShop CA, replacing...");
return ret;
}
return real_FSOpenFile_eShop(client, block, path, mode, handle, error);
}
DECL_FUNCTION(FSStatus, FSReadFile_eShop, FSClient *client, FSCmdBlock *block, uint8_t *buffer, uint32_t size, uint32_t count,
FSFileHandle handle, uint32_t unk1, uint32_t flags) {
if (size != 1) {
DEBUG_FUNCTION_LINE("Inkay: eShop CA replacement failed!");
}
if (rootca_pem_handle && *rootca_pem_handle == handle) {
strlcpy((char *) buffer, (const char *) ca_pem, size * count);
return (FSStatus) count;
}
return real_FSReadFile_eShop(client, block, buffer, size, count, handle, unk1, flags);
}
DECL_FUNCTION(FSStatus, FSCloseFile_eShop, FSClient *client, FSCmdBlock *block, FSFileHandle handle, FSErrorFlag errorMask) {
if (handle == rootca_pem_handle) {
rootca_pem_handle.reset();
}
return real_FSCloseFile_eShop(client, block, handle, errorMask);
}
void patchEshop() {
eshop_patches.reserve(3);
auto add_patch = [](function_replacement_data_t repl, const char *name) {
PatchedFunctionHandle handle = 0;
if (FunctionPatcher_AddFunctionPatch(&repl, &handle, nullptr) != FUNCTION_PATCHER_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE("Inkay/eShop: Failed to patch %s!", name);
}
eshop_patches.push_back(handle);
};
add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSOpenFile_eShop, LIBRARY_COREINIT, FSOpenFile, FP_TARGET_PROCESS_ESHOP), "FSOpenFile_eShop");
add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSReadFile_eShop, LIBRARY_COREINIT, FSReadFile, FP_TARGET_PROCESS_ESHOP), "FSReadFile_eShop");
add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSCloseFile_eShop, LIBRARY_COREINIT, FSCloseFile, FP_TARGET_PROCESS_ESHOP), "FSCloseFile_eShop");
}
void unpatchEshop() {
for (auto handle: eshop_patches) {
FunctionPatcher_RemoveFunctionPatch(handle);
}
eshop_patches.clear();
}

View file

@ -0,0 +1,20 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
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 <http://www.gnu.org/licenses/>.
*/
#pragma once
void patchEshop();
void unpatchEshop();

View file

@ -2,18 +2,22 @@
Copyright 2023 Ash Logan <ash@heyquark.com>
Copyright 2019 Maschell
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
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 <http://www.gnu.org/licenses/>.
*/
#include "game_matchmaking.h"
#include "config.h"
#include "game_matchmaking.h"
#include "utils/logger.h"
#include "ini.h"
@ -54,7 +58,7 @@ static int handler(void *user, const char *section, const char *name, const char
static void check_modpack() {
modpack mod;
if (ini_parse("fs:/vol/content/pretendo.ini", handler, &mod)) {
DEBUG_FUNCTION_LINE("Inkay/MK8: Doesn't look like a modpack");
DEBUG_FUNCTION_LINE_VERBOSE("Inkay/MK8: Doesn't look like a modpack");
}
DEBUG_FUNCTION_LINE("Inkay/MK8: Playing %s (%08x)", mod.name.c_str(), mod.dlc_id);
@ -68,7 +72,7 @@ DECL_FUNCTION(void, mk8_MatchmakeSessionSearchCriteria_SetAttribute, void *_this
const int dlc_id = dlc_modpack->dlc_id;
if (dlc_id != -1) {
DEBUG_FUNCTION_LINE("Inkay/MK8: Searching for %s session (%08x)", dlc_modpack->name.c_str(), dlc_id);
DEBUG_FUNCTION_LINE_VERBOSE("Inkay/MK8: Searching for %s session (%08x)", dlc_modpack->name.c_str(), dlc_id);
attributeValue = dlc_id;
}
}
@ -82,7 +86,7 @@ DECL_FUNCTION(void, mk8_MatchmakeSession_SetAttribute, void *_this, uint32_t att
const int dlc_id = dlc_modpack->dlc_id;
if (dlc_id != -1) {
DEBUG_FUNCTION_LINE("Inkay/MK8: Creating %s session (%08x)", dlc_modpack->name.c_str(), dlc_id);
DEBUG_FUNCTION_LINE_VERBOSE("Inkay/MK8: Creating %s session (%08x)", dlc_modpack->name.c_str(), dlc_id);
attributeValue = dlc_id;
}
}

View file

@ -0,0 +1,131 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
Copyright 2024 Ash Logan <ash@heyquark.com>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include <coreinit/title.h>
#include <coreinit/dynload.h>
#include "game_peertopeer.h"
#include "config.h"
#include "sysconfig.h"
#include "utils/logger.h"
#include "utils/rpl_info.h"
#include "utils/replace_mem.h"
#include <optional>
#include <algorithm>
#include <string_view>
using namespace std::string_view_literals;
static struct {
std::array<uint64_t, 3> tid;
uint16_t version;
uint32_t min_port_addr;
uint32_t max_port_addr;
std::string_view rpx;
} generic_patch_games[] = {
{
// MARIO KART 8
{0x00050000'1010ec00, 0x00050000'1010ed00, 0x00050000'1010eb00},
81,
0x101a9a52,
0x101a9a54,
"Turbo.rpx"sv,
},
{
// Splatoon
{0x00050000'10176900, 0x00050000'10176a00, 0x00050000'10162b00},
288,
0x101e8952,
0x101e8954,
"Gambit.rpx"sv,
},
};
static void generic_peertopeer_patch() {
uint64_t tid = OSGetTitleID();
uint16_t title_version = 0;
if (const auto version_opt = get_current_title_version(); !version_opt) {
DEBUG_FUNCTION_LINE("Failed to detect current title version");
return;
} else {
title_version = *version_opt;
DEBUG_FUNCTION_LINE("Title version detected: %d", title_version);
}
for (const auto &patch: generic_patch_games) {
if (std::ranges::find(patch.tid, tid) == patch.tid.end()) continue;
std::optional<OSDynLoad_NotifyData> game = search_for_rpl(patch.rpx);
if (!game) {
DEBUG_FUNCTION_LINE("Couldn't find game rpx! (%s)", patch.rpx.data());
return;
}
if (title_version != patch.version) {
DEBUG_FUNCTION_LINE("Unexpected title version. Expected %d but got %d (%s)", patch.version, title_version,
patch.rpx.data());
continue;
}
auto port = get_console_peertopeer_port();
DEBUG_FUNCTION_LINE_VERBOSE("Will use port %d. %08x", port, game->textAddr);
auto target = (uint16_t *)rpl_addr(*game, patch.min_port_addr);
replace_unsigned<uint16_t>(target, 0xc000, port);
target = (uint16_t *)rpl_addr(*game, patch.max_port_addr);
replace_unsigned<uint16_t>(target, 0xffff, port);
break;
}
}
static void minecraft_peertopeer_patch() {
std::optional<OSDynLoad_NotifyData> minecraft = search_for_rpl("Minecraft.Client.rpx"sv);
if (!minecraft) {
DEBUG_FUNCTION_LINE("Couldn't find minecraft rpx!");
return;
}
if (const auto version_opt = get_current_title_version(); !version_opt || *version_opt != 688) {
DEBUG_FUNCTION_LINE("Wrong mincecraft version detected");
return;
}
auto port = get_console_peertopeer_port();
DEBUG_FUNCTION_LINE_VERBOSE("Will use port %d. %08x", port, minecraft->textAddr);
auto target_func = (uint32_t *)rpl_addr(*minecraft, 0x03579530);
replace_instruction(&target_func[0], 0x3c600001, 0x3c600000); // li r3, 0
replace_instruction(&target_func[1], 0x3863c000, 0x60630000 | port); // ori r3, r3, port
// blr
target_func = (uint32_t *)rpl_addr(*minecraft, 0x0357953c);
replace_instruction(&target_func[0], 0x3c600001, 0x3c600000); // li r3, 0
replace_instruction(&target_func[1], 0x3863ffff, 0x60630000 | port); // ori r3, r3, port
// blr
}
void peertopeer_patch() {
if (!Config::connect_to_network) {
return;
}
uint64_t tid = OSGetTitleID();
if (tid == 0x00050000'101D7500 || // EUR
tid == 0x00050000'101D9D00 || // USA
tid == 0x00050000'101DBE00) { // JPN
minecraft_peertopeer_patch();
} else {
generic_peertopeer_patch();
}
}

View file

@ -0,0 +1,16 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
Copyright 2024 Ash Logan <ash@heyquark.com>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
void peertopeer_patch();

View file

@ -2,14 +2,18 @@
Copyright 2023 Ash Logan <ash@heyquark.com>
Copyright 2019 Maschell
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
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 <http://www.gnu.org/licenses/>.
*/
#include "config.h"
@ -17,24 +21,36 @@
#include "utils/logger.h"
#include "utils/replace_mem.h"
#include <wups.h>
#include <vector>
#include <optional>
#include <coreinit/debug.h>
#include <coreinit/filesystem.h>
#include <nsysnet/nssl.h>
#include <function_patcher/function_patching.h>
#include "ca_pem.h" // generated at buildtime
const char wave_original[] = {
0x68, 0x74, 0x74, 0x70, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2E, 0x6E, 0x69, 0x6E, 0x74, 0x65, 0x6E, 0x64,
0x6F, 0x2E, 0x6E, 0x65, 0x74
struct olv_allowlist {
char scheme[16];
char domain[128];
char path[128]; // unverified
unsigned char flags[5];
};
const char wave_new[] = {
0x68, 0x74, 0x74, 0x70, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2E, 0x70, 0x72, 0x65, 0x74, 0x65, 0x6E, 0x64,
0x6F, 0x2E, 0x63, 0x63, 0x00
constexpr struct olv_allowlist original_entry = {
.scheme = "https",
.domain = ".nintendo.net",
.path = "",
.flags = {1, 1, 1, 1, 1},
};
constexpr struct olv_allowlist new_entry = {
.scheme = "https",
.domain = "." NETWORK_BASEURL,
.path = "",
.flags = {1, 1, 1, 1, 1},
};
const unsigned char miiverse_green_highlight[] = {
0x82, 0xff, 0x05, 0xff, 0x82, 0xff, 0x05, 0xff, 0x1d, 0xff, 0x04, 0xff, 0x1d, 0xff, 0x04, 0xff
};
@ -61,13 +77,14 @@ const replacement replacements[] = {
};
static std::optional<FSFileHandle> rootca_pem_handle{};
std::vector<PatchedFunctionHandle> olv_patches;
DECL_FUNCTION(int, FSOpenFile, FSClient *client, FSCmdBlock *block, char *path, const char *mode, uint32_t *handle,
int error) {
const char *initialOma = "vol/content/initial.oma";
if (!Config::connect_to_network) {
DEBUG_FUNCTION_LINE("Inkay: Miiverse patches skipped.");
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Miiverse patches skipped.");
return real_FSOpenFile(client, block, path, mode, handle, error);
}
@ -76,17 +93,17 @@ DECL_FUNCTION(int, FSOpenFile, FSClient *client, FSCmdBlock *block, char *path,
//we do it when loading this file since it should only load once, preventing massive lag spikes as it searches all of MEM2 xD
//WHBLogUdpInit();
DEBUG_FUNCTION_LINE("Inkay: hewwo!\n");
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: hewwo!\n");
auto olv_ok = setup_olv_libs();
// Patch applet binary too
if (olv_ok)
replace(0x10000000, 0x10000000, wave_original, sizeof(wave_original), wave_new, sizeof(wave_new));
replace(0x10000000, 0x10000000, (const char *)&original_entry, sizeof(original_entry), (const char *)&new_entry, sizeof(new_entry));
// Check for root CA file and take note of its handle
} else if (strcmp("vol/content/browser/rootca.pem", path) == 0) {
int ret = real_FSOpenFile(client, block, path, mode, handle, error);
rootca_pem_handle = *handle;
DEBUG_FUNCTION_LINE("Inkay: Found Miiverse CA, replacing...");
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Found Miiverse CA, replacing...");
return ret;
}
@ -119,27 +136,25 @@ DECL_FUNCTION(FSStatus, FSCloseFile, FSClient *client, FSCmdBlock *block, FSFile
return real_FSCloseFile(client, block, handle, errorMask);
}
DECL_FUNCTION(uint32_t, NSSLExportInternalServerCertificate, NSSLServerCertId cert, int unk, void *unk2, void *unk3) {
if (cert == NSSL_SERVER_CERT_THAWTE_PREMIUM_SERVER_CA) { // Martini patches
OSFatal("[598-0069] Please uninstall Martini patches to continue.\n" \
"See pretendo.network/docs/search for more info.\n\n" \
"Hold the POWER button for 4 seconds to shut down.\n\n"
" .\n"
".---------.'---.\n"
"'. : .'\n"
" '. .::: .'\n"
" '.'::'.'\n"
" '||'\n"
" ||\n"
" ||\n"
"mrz ||\n"
" ---====---");
}
return real_NSSLExportInternalServerCertificate(cert, unk, unk2, unk3);
void patchOlvApplet() {
olv_patches.reserve(3);
auto add_patch = [](function_replacement_data_t repl, const char *name) {
PatchedFunctionHandle handle = 0;
if (FunctionPatcher_AddFunctionPatch(&repl, &handle, nullptr) != FUNCTION_PATCHER_RESULT_SUCCESS) {
DEBUG_FUNCTION_LINE("Inkay/OLV: Failed to patch %s!", name);
}
olv_patches.push_back(handle);
};
add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSOpenFile, LIBRARY_COREINIT, FSOpenFile, FP_TARGET_PROCESS_MIIVERSE), "FSOpenFile");
add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSReadFile, LIBRARY_COREINIT, FSReadFile, FP_TARGET_PROCESS_MIIVERSE), "FSReadFile");
add_patch(REPLACE_FUNCTION_FOR_PROCESS(FSCloseFile, LIBRARY_COREINIT, FSCloseFile, FP_TARGET_PROCESS_MIIVERSE), "FSCloseFile");
}
WUPS_MUST_REPLACE_FOR_PROCESS(FSOpenFile, WUPS_LOADER_LIBRARY_COREINIT, FSOpenFile, WUPS_FP_TARGET_PROCESS_MIIVERSE);
WUPS_MUST_REPLACE_FOR_PROCESS(FSReadFile, WUPS_LOADER_LIBRARY_COREINIT, FSReadFile, WUPS_FP_TARGET_PROCESS_MIIVERSE);
WUPS_MUST_REPLACE_FOR_PROCESS(FSCloseFile, WUPS_LOADER_LIBRARY_COREINIT, FSCloseFile, WUPS_FP_TARGET_PROCESS_MIIVERSE);
WUPS_MUST_REPLACE_FOR_PROCESS(NSSLExportInternalServerCertificate, WUPS_LOADER_LIBRARY_NSYSNET,
NSSLExportInternalServerCertificate, WUPS_FP_TARGET_PROCESS_MIIVERSE);
void unpatchOlvApplet() {
for (auto handle: olv_patches) {
FunctionPatcher_RemoveFunctionPatch(handle);
}
olv_patches.clear();
}

20
src/patches/olv_applet.h Normal file
View file

@ -0,0 +1,20 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
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 <http://www.gnu.org/licenses/>.
*/
#pragma once
void patchOlvApplet();
void unpatchOlvApplet();

View file

@ -2,18 +2,22 @@
Copyright 2023 Ash Logan <ash@heyquark.com>
Copyright 2019 Maschell
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
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 <http://www.gnu.org/licenses/>.
*/
#include "olv_urls.h"
#include "config.h"
#include "olv_urls.h"
#include "utils/logger.h"
#include "utils/replace_mem.h"
@ -47,20 +51,20 @@ bool path_is_olv(const char* path) {
void new_rpl_loaded(OSDynLoad_Module module, void* ctx, OSDynLoad_NotifyReason reason, OSDynLoad_NotifyData* rpl) {
if (!Config::connect_to_network) {
DEBUG_FUNCTION_LINE("Inkay: Miiverse patches skipped.");
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Miiverse patches skipped.");
return;
}
// Loaded olv?
if (reason != OS_DYNLOAD_NOTIFY_LOADED) return;
if (!path_is_olv(rpl->name)) return;
if (!rpl->name || !path_is_olv(rpl->name)) return;
replace(rpl->dataAddr, rpl->dataSize, original_url, sizeof(original_url), new_url, sizeof(new_url));
}
bool setup_olv_libs() {
if (!Config::connect_to_network) {
DEBUG_FUNCTION_LINE("Inkay: Miiverse patches skipped.");
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: Miiverse patches skipped.");
return false;
}
@ -68,7 +72,7 @@ bool setup_olv_libs() {
auto olvLoaded = check_olv_libs();
if (!olvLoaded) {
DEBUG_FUNCTION_LINE("Inkay: no olv, quitting for now\n");
DEBUG_FUNCTION_LINE_VERBOSE("Inkay: no olv, quitting for now\n");
return false;
}

View file

@ -15,9 +15,10 @@
#pragma once
#include <cstdlib>
#include "inkay_config.h"
const char original_url[] = "discovery.olv.nintendo.net/v1/endpoint";
const char new_url[] = "discovery.olv.pretendo.cc/v1/endpoint";
constexpr char original_url[] = "discovery.olv.nintendo.net/v1/endpoint";
constexpr char new_url[] = "discovery.olv." NETWORK_BASEURL "/v1/endpoint";
_Static_assert(sizeof(original_url) > sizeof(new_url),
"new_url too long! Must be less than 38chars.");

View file

@ -1,33 +1,34 @@
#pragma once
#include <string.h>
#include <whb/log.h>
#include <whb/log_module.h>
#include <whb/log_cafe.h>
#include <whb/log_udp.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <string.h>
#include <whb/log.h>
#include <whb/log_udp.h>
#include <whb/log_cafe.h>
#define __FILENAME_X__ (strrchr(__FILE__, '\\') ? strrchr(__FILE__, '\\') + 1 : __FILE__)
#define __FILENAME__ (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILENAME_X__)
#define OSFATAL_FUNCTION_LINE(FMT, ARGS...)do { \
OSFatal_printf("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
OSFatal_printf("[(M) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
} while (0)
#define DEBUG_FUNCTION_LINE(FMT, ARGS...)do { \
WHBLogPrintf("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
WHBLogPrintf("[(M) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
} while (0);
#define DEBUG_FUNCTION_LINE_WRITE(FMT, ARGS...)do { \
WHBLogWritef("[(P) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
WHBLogWritef("[(M) Inkay][%23s]%30s@L%04d: " FMT "",__FILENAME__,__FUNCTION__, __LINE__, ## ARGS); \
} while (0);
#ifdef DEBUG
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) DEBUG_FUNCTION_LINE(FMT, ARGS...)
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) DEBUG_FUNCTION_LINE(FMT, ##ARGS)
#else
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while(0)
#define DEBUG_FUNCTION_LINE_VERBOSE(FMT, ARGS...) while (0);
#endif
#ifdef __cplusplus

View file

@ -2,14 +2,18 @@
Copyright 2023 Ash Logan <ash@heyquark.com>
Copyright 2019 Maschell
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
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.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
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 <http://www.gnu.org/licenses/>.
*/
#include "replace_mem.h"
@ -18,14 +22,16 @@
#include <kernel/kernel.h>
#include <coreinit/memorymap.h>
#include <algorithm>
#include <coreinit/cache.h>
bool replace(uint32_t start, uint32_t size, const char* original_val, size_t original_val_sz, const char* new_val, size_t new_val_sz) {
bool replace(uint32_t start, uint32_t size, const char *original_val, size_t original_val_sz, const char *new_val,
size_t new_val_sz) {
for (uint32_t addr = start; addr < start + size - original_val_sz; addr++) {
int ret = memcmp(original_val, (void*)addr, original_val_sz);
int ret = memcmp(original_val, (void *) addr, original_val_sz);
if (ret == 0) {
DEBUG_FUNCTION_LINE_VERBOSE("found str @%08x: %s", addr, (const char*)addr);
KernelCopyData(OSEffectiveToPhysical(addr), OSEffectiveToPhysical((uint32_t)new_val), new_val_sz);
DEBUG_FUNCTION_LINE_VERBOSE("new str @%08x: %s", addr, (const char*)addr);
DEBUG_FUNCTION_LINE_VERBOSE("found str @%08x: %s", addr, (const char *) addr);
KernelCopyData(OSEffectiveToPhysical(addr), OSEffectiveToPhysical((uint32_t) new_val), new_val_sz);
DEBUG_FUNCTION_LINE_VERBOSE("new str @%08x: %s", addr, (const char *) addr);
return true;
}
}
@ -35,24 +41,24 @@ bool replace(uint32_t start, uint32_t size, const char* original_val, size_t ori
void replaceBulk(uint32_t start, uint32_t size, std::span<const replacement> replacements) {
// work out the biggest input replacement
auto max_sz = std::max_element(replacements.begin(), replacements.end(), [](auto& a, auto& b) {
auto max_sz = std::max_element(replacements.begin(), replacements.end(), [](auto &a, auto &b) {
return a.orig.size_bytes() < b.orig.size_bytes();
})->orig.size_bytes();
int counts[replacements.size()];
for (auto& c : counts) {
for (auto &c: counts) {
c = 0;
}
for (uint32_t addr = start; addr < start + size - max_sz; addr++) {
for (int i = 0; i < (int)replacements.size(); i++) {
const auto& replacement = replacements[i];
for (int i = 0; i < (int) replacements.size(); i++) {
const auto &replacement = replacements[i];
int ret = memcmp((void*)addr, replacement.orig.data(), replacement.orig.size_bytes());
int ret = memcmp((void *) addr, replacement.orig.data(), replacement.orig.size_bytes());
if (ret == 0) {
KernelCopyData(
OSEffectiveToPhysical(addr),
OSEffectiveToPhysical((uint32_t)replacement.repl.data()),
OSEffectiveToPhysical((uint32_t) replacement.repl.data()),
replacement.repl.size_bytes()
);
counts[i]++;
@ -60,7 +66,37 @@ void replaceBulk(uint32_t start, uint32_t size, std::span<const replacement> rep
}
}
}
for (auto c : counts) {
#ifdef DEBUG
for (auto c: counts) {
DEBUG_FUNCTION_LINE("replaced %d times", c);
}
#endif
}
template <typename U>
requires std::integral<U>
bool replace_unsigned(U *addr, U original_value, U new_value) {
if (*addr != original_value) return false;
KernelCopyData(
OSEffectiveToPhysical((uint32_t) addr),
OSEffectiveToPhysical((uint32_t) &new_value),
sizeof(new_value)
);
DCFlushRange(addr, sizeof(new_value));
DEBUG_FUNCTION_LINE_VERBOSE("%08x is now %08x", inst, *inst);
return *addr == new_value;
}
template bool replace_unsigned<uint64_t>(uint64_t *, uint64_t, uint64_t);
template bool replace_unsigned<uint32_t>(uint32_t *, uint32_t, uint32_t);
template bool replace_unsigned<uint16_t>(uint16_t *, uint16_t, uint16_t);
template bool replace_unsigned<uint8_t>(uint8_t *, uint8_t, uint8_t);
bool replace_instruction(uint32_t *inst, uint32_t original_value, uint32_t new_value) {
bool res = replace_unsigned<uint32_t>(inst, original_value, new_value);
if (!res) return res;
ICInvalidateRange(inst, sizeof(new_value));
return true;
}

View file

@ -18,7 +18,8 @@
#include <cstddef>
#include <span>
bool replace(uint32_t start, uint32_t size, const char* original_val, size_t original_val_sz, const char* new_val, size_t new_val_sz);
bool replace(uint32_t start, uint32_t size, const char *original_val, size_t original_val_sz, const char *new_val,
size_t new_val_sz);
struct replacement {
std::span<const uint8_t> orig;
@ -26,3 +27,9 @@ struct replacement {
};
void replaceBulk(uint32_t start, uint32_t size, std::span<const replacement> replacements);
template <typename U>
requires std::integral<U>
bool replace_unsigned(U *addr, U original_value, U new_value);
bool replace_instruction(uint32_t *inst, uint32_t original_value, uint32_t new_value);

64
src/utils/rpl_info.cpp Normal file
View file

@ -0,0 +1,64 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
Copyright 2024 Ash Logan <ash@heyquark.com>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "rpl_info.h"
#include <vector>
#include <algorithm>
#include <string_view>
#include <coreinit/mcp.h>
#include <coreinit/title.h>
#include "logger.h"
// if we get more than like.. two callsites for this, it should really be refactored out rather than doing a fresh
// search every time
std::optional<OSDynLoad_NotifyData> search_for_rpl(std::string_view name) {
int num_rpls = OSDynLoad_GetNumberOfRPLs();
if (!num_rpls)
return std::nullopt;
// allocates but we free it at return
std::vector<OSDynLoad_NotifyData> rpls(num_rpls);
if (!OSDynLoad_GetRPLInfo(0, num_rpls, rpls.data()))
return std::nullopt;
auto rpl = std::find_if(rpls.begin(), rpls.end(), [=](const OSDynLoad_NotifyData &rpl) {
return std::string_view(rpl.name).ends_with(name);
});
if (rpl == rpls.end())
return std::nullopt;
return *rpl;
}
std::optional<uint16_t> get_current_title_version() {
const auto mcpHandle = MCP_Open();
MCPTitleListType titleInfo;
int32_t res = -1;
const uint64_t curTitleId = OSGetTitleID();
if ((curTitleId & 0x0000000F00000000) == 0) {
res = MCP_GetTitleInfo(mcpHandle, curTitleId | 0x0000000E00000000, &titleInfo);
}
if (res != 0) {
res = MCP_GetTitleInfo(mcpHandle, curTitleId, &titleInfo);
}
MCP_Close(mcpHandle);
if (res != 0) {
DEBUG_FUNCTION_LINE("Failed to get title version of %016llX.", curTitleId);
return {};
}
MCP_Close(mcpHandle);
const auto tmp_result = titleInfo.titleVersion; // make the compiler happy because we access a packed struct
return tmp_result;
}

30
src/utils/rpl_info.h Normal file
View file

@ -0,0 +1,30 @@
/* Copyright 2024 Pretendo Network contributors <pretendo.network>
Copyright 2024 Ash Logan <ash@heyquark.com>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby
granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#pragma once
#include <optional>
#include <string_view>
#include <coreinit/dynload.h>
std::optional<OSDynLoad_NotifyData> search_for_rpl(std::string_view name);
constexpr void *rpl_addr(OSDynLoad_NotifyData rpl, uint32_t cemu_addr) {
if (cemu_addr < 0x1000'0000) {
return (void *)(rpl.textAddr + cemu_addr - 0x0200'0000);
} else {
return (void *)(rpl.dataAddr + cemu_addr - 0x1000'0000);
}
}
std::optional<uint16_t> get_current_title_version();