From 6f25e92892663d4a13366f702ef0493444b04fc0 Mon Sep 17 00:00:00 2001
From: Michael Scire <SciresM@gmail.com>
Date: Wed, 22 May 2019 12:11:40 -0700
Subject: [PATCH] set.mitm: language emulation (closes #489)

---
 stratosphere/ams_mitm/source/amsmitm_main.cpp |  10 +
 .../source/set_mitm/set_mitm_service.cpp      | 113 ++++++++++++
 .../source/set_mitm/set_mitm_service.hpp      |  68 +++++++
 .../ams_mitm/source/set_mitm/set_shim.c       |  59 ++++++
 .../ams_mitm/source/set_mitm/set_shim.h       |  19 ++
 .../ams_mitm/source/set_mitm/setmitm_main.cpp |  19 +-
 .../ams_mitm/source/set_mitm/setsys_shim.c    |   2 +-
 stratosphere/ams_mitm/source/utils.cpp        | 172 ++++++++++++------
 stratosphere/ams_mitm/source/utils.hpp        |  54 +++++-
 stratosphere/pm/source/pm_main.cpp            |   4 +-
 10 files changed, 452 insertions(+), 68 deletions(-)
 create mode 100644 stratosphere/ams_mitm/source/set_mitm/set_mitm_service.cpp
 create mode 100644 stratosphere/ams_mitm/source/set_mitm/set_mitm_service.hpp
 create mode 100644 stratosphere/ams_mitm/source/set_mitm/set_shim.c
 create mode 100644 stratosphere/ams_mitm/source/set_mitm/set_shim.h

diff --git a/stratosphere/ams_mitm/source/amsmitm_main.cpp b/stratosphere/ams_mitm/source/amsmitm_main.cpp
index 911c5e561..1ce809199 100644
--- a/stratosphere/ams_mitm/source/amsmitm_main.cpp
+++ b/stratosphere/ams_mitm/source/amsmitm_main.cpp
@@ -78,6 +78,16 @@ void __appInit(void) {
         if (R_FAILED(rc)) {
             std::abort();
         }
+
+        rc = pmdmntInitialize();
+        if (R_FAILED(rc)) {
+            std::abort();
+        }
+
+        rc = pminfoInitialize();
+        if (R_FAILED(rc)) {
+            std::abort();
+        }
     });
     
     CheckAtmosphereVersion(CURRENT_ATMOSPHERE_VERSION);
diff --git a/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.cpp b/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.cpp
new file mode 100644
index 000000000..535d13ffe
--- /dev/null
+++ b/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.cpp
@@ -0,0 +1,113 @@
+/*
+ * Copyright (c) 2018-2019 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 <mutex>
+#include <algorithm>
+#include <switch.h>
+#include "set_mitm_service.hpp"
+#include "set_shim.h"
+
+void SetMitmService::PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx) {
+    /* No commands need postprocessing. */
+}
+
+bool SetMitmService::IsValidLanguageCode(u64 lang_code) {
+    static constexpr u64 s_valid_language_codes[] = {
+        LanguageCode_Japanese,
+        LanguageCode_AmericanEnglish,
+        LanguageCode_French,
+        LanguageCode_German,
+        LanguageCode_Italian,
+        LanguageCode_Spanish,
+        LanguageCode_Chinese,
+        LanguageCode_Korean,
+        LanguageCode_Dutch,
+        LanguageCode_Portuguese,
+        LanguageCode_Russian,
+        LanguageCode_Taiwanese,
+        LanguageCode_BritishEnglish,
+        LanguageCode_CanadianFrench,
+        LanguageCode_LatinAmericanSpanish,
+        LanguageCode_SimplifiedChinese,
+        LanguageCode_TraditionalChinese,
+    };
+    size_t num_language_codes = sizeof(s_valid_language_codes) / sizeof(s_valid_language_codes[0]);
+    if (GetRuntimeFirmwareVersion() < FirmwareVersion_400) {
+        /* 4.0.0 added simplified and traditional chinese. */
+        num_language_codes -= 2;
+    }
+
+    for (size_t i = 0; i < num_language_codes; i++) {
+        if (lang_code == s_valid_language_codes[i]) {
+            return true;
+        }
+    }
+
+    return false;
+}
+
+bool SetMitmService::IsValidRegionCode(u32 region_code) {
+    return region_code < RegionCode_Max;
+}
+
+void SetMitmService::EnsureLocale() {
+    std::scoped_lock<HosMutex> lk(this->lock);
+
+    if (!this->got_locale) {
+        std::memset(&this->locale, 0xCC, sizeof(this->locale));
+        if (this->title_id == TitleId_Ns) {
+            u64 app_pid = 0;
+            u64 app_tid = 0;
+            Result rc;
+            if (R_FAILED((rc = pmdmntGetApplicationPid(&app_pid)))) {
+                return;
+            }
+            if (R_FAILED((rc = pminfoGetTitleId(&app_tid, app_pid)))) {
+                return;
+            }
+            this->locale = Utils::GetTitleOverrideLocale(app_tid);
+        } else {
+            this->locale = Utils::GetTitleOverrideLocale(this->title_id);
+            this->got_locale = true;
+        }
+    }
+}
+
+Result SetMitmService::GetLanguageCode(Out<u64> out_lang_code) {
+    this->EnsureLocale();
+
+    if (!IsValidLanguageCode(this->locale.language_code)) {
+        return ResultAtmosphereMitmShouldForwardToSession;
+    }
+
+    out_lang_code.SetValue(this->locale.language_code);
+    return ResultSuccess;
+}
+
+Result SetMitmService::GetRegionCode(Out<u32> out_region_code) {
+    this->EnsureLocale();
+
+    if (!IsValidRegionCode(this->locale.region_code)) {
+        return ResultAtmosphereMitmShouldForwardToSession;
+    }
+
+    out_region_code.SetValue(this->locale.region_code);
+    return ResultSuccess;
+}
+
+Result SetMitmService::GetAvailableLanguageCodes(OutPointerWithClientSize<u64> out_language_codes, Out<s32> out_count) {
+    return setGetAvailableLanguageCodesFwd(this->forward_service.get(), out_count.GetPointer(), out_language_codes.pointer, out_language_codes.num_elements);
+}
diff --git a/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.hpp b/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.hpp
new file mode 100644
index 000000000..b7e0341ed
--- /dev/null
+++ b/stratosphere/ams_mitm/source/set_mitm/set_mitm_service.hpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2018-2019 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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
+#include <switch.h>
+#include <stratosphere.hpp>
+
+#include "../utils.hpp"
+
+enum SetCmd : u32 {
+    SetCmd_GetLanguageCode = 0,
+    SetCmd_GetRegionCode = 4,
+
+    /* Commands for which set:sys *must* act as a passthrough. */
+    /* TODO: Solve the relevant IPC detection problem. */
+    SetCmd_GetAvailableLanguageCodes = 1,
+};
+
+class SetMitmService : public IMitmServiceObject {
+    private:
+        HosMutex lock;
+        OverrideLocale locale;
+        bool got_locale;
+    public:
+        SetMitmService(std::shared_ptr<Service> s, u64 pid) : IMitmServiceObject(s, pid) {
+            this->got_locale = false;
+        }
+
+        static bool ShouldMitm(u64 pid, u64 tid) {
+            /* Mitm all applications. */
+            return tid == TitleId_Ns || TitleIdIsApplication(tid);
+        }
+
+        static void PostProcess(IMitmServiceObject *obj, IpcResponseContext *ctx);
+
+    protected:
+        static bool IsValidLanguageCode(u64 lang_code);
+        static bool IsValidRegionCode(u32 region_code);
+
+        void EnsureLocale();
+    protected:
+        /* Overridden commands. */
+        Result GetLanguageCode(Out<u64> out_lang_code);
+        Result GetRegionCode(Out<u32> out_region_code);
+
+        /* Forced passthrough commands. */
+        Result GetAvailableLanguageCodes(OutPointerWithClientSize<u64> out_language_codes, Out<s32> out_count);
+    public:
+        DEFINE_SERVICE_DISPATCH_TABLE {
+            MakeServiceCommandMeta<SetCmd_GetLanguageCode, &SetMitmService::GetLanguageCode>(),
+            MakeServiceCommandMeta<SetCmd_GetRegionCode, &SetMitmService::GetRegionCode>(),
+
+            MakeServiceCommandMeta<SetCmd_GetAvailableLanguageCodes, &SetMitmService::GetAvailableLanguageCodes>(),
+        };
+};
diff --git a/stratosphere/ams_mitm/source/set_mitm/set_shim.c b/stratosphere/ams_mitm/source/set_mitm/set_shim.c
new file mode 100644
index 000000000..61943496a
--- /dev/null
+++ b/stratosphere/ams_mitm/source/set_mitm/set_shim.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2018-2019 Atmosphère-NX
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope 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 <string.h>
+#include <switch.h>
+#include "setsys_shim.h"
+
+/* Command forwarders. */
+Result setGetAvailableLanguageCodesFwd(Service* s, s32 *total_entries, u64 *language_codes, size_t max_entries) {
+    IpcCommand c;
+    ipcInitialize(&c);
+    ipcAddRecvStatic(&c, language_codes, max_entries * sizeof(*language_codes), 0);
+
+    struct {
+        u64 magic;
+        u64 cmd_id;
+    } *raw;
+
+    raw = serviceIpcPrepareHeader(s, &c, sizeof(*raw));
+
+    raw->magic = SFCI_MAGIC;
+    raw->cmd_id = 1;
+
+    Result rc = serviceIpcDispatch(s);
+
+    if (R_SUCCEEDED(rc)) {
+        IpcParsedCommand r;
+
+        struct {
+            u64 magic;
+            u64 result;
+            s32 total_entries;
+        } *resp;
+
+        serviceIpcParse(s, &r, sizeof(*resp));
+        resp = r.Raw;
+
+        rc = resp->result;
+
+        if (R_SUCCEEDED(rc)) {
+            *total_entries = resp->total_entries;
+        }
+    }
+
+    return rc;
+}
diff --git a/stratosphere/ams_mitm/source/set_mitm/set_shim.h b/stratosphere/ams_mitm/source/set_mitm/set_shim.h
new file mode 100644
index 000000000..bee7dcf42
--- /dev/null
+++ b/stratosphere/ams_mitm/source/set_mitm/set_shim.h
@@ -0,0 +1,19 @@
+/**
+ * @file set_shim.h
+ * @brief Settings Services (set) IPC wrapper. To be merged into libnx, eventually.
+ * @author SciresM
+ * @copyright libnx Authors
+ */
+#pragma once
+#include <switch.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Command forwarders. */
+Result setGetAvailableLanguageCodesFwd(Service* s, s32 *total_entries, u64 *language_codes, size_t max_entries);
+
+#ifdef __cplusplus
+}
+#endif
\ No newline at end of file
diff --git a/stratosphere/ams_mitm/source/set_mitm/setmitm_main.cpp b/stratosphere/ams_mitm/source/set_mitm/setmitm_main.cpp
index 4960097bb..f3e80a446 100644
--- a/stratosphere/ams_mitm/source/set_mitm/setmitm_main.cpp
+++ b/stratosphere/ams_mitm/source/set_mitm/setmitm_main.cpp
@@ -13,7 +13,7 @@
  * 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 <cstdlib>
 #include <cstdint>
 #include <cstring>
@@ -28,6 +28,8 @@
 #include "setsys_settings_items.hpp"
 #include "setsys_firmware_version.hpp"
 
+#include "set_mitm_service.hpp"
+
 #include "../utils.hpp"
 
 struct SetSysManagerOptions {
@@ -44,17 +46,20 @@ void SetMitmMain(void *arg) {
 
     /* Initialize version manager. */
     VersionManager::Initialize();
-            
+
     /* Create server manager */
-    auto server_manager = new SetMitmManager(3);
-    
+    auto server_manager = new SetMitmManager(4);
+
     /* Create set:sys mitm. */
     AddMitmServerToManager<SetSysMitmService>(server_manager, "set:sys", 60);
-    
+
+    /* Create set mitm. */
+    AddMitmServerToManager<SetMitmService>(server_manager, "set", 60);
+
     /* Loop forever, servicing our services. */
     server_manager->Process();
-    
+
     delete server_manager;
-    
+
 }
 
diff --git a/stratosphere/ams_mitm/source/set_mitm/setsys_shim.c b/stratosphere/ams_mitm/source/set_mitm/setsys_shim.c
index 1ef7b887c..d532adb9d 100644
--- a/stratosphere/ams_mitm/source/set_mitm/setsys_shim.c
+++ b/stratosphere/ams_mitm/source/set_mitm/setsys_shim.c
@@ -55,7 +55,7 @@ Result setsysGetEdidFwd(Service* s, SetSysEdid* out) {
 Result setsysGetSettingsItemValueFwd(Service *s, const char *name, const char *item_key, void *value_out, size_t value_out_size, u64 *size_out) {
     char send_name[SET_MAX_NAME_SIZE];
     char send_item_key[SET_MAX_NAME_SIZE];
-    
+
     memset(send_name, 0, SET_MAX_NAME_SIZE);
     memset(send_item_key, 0, SET_MAX_NAME_SIZE);
     strncpy(send_name, name, SET_MAX_NAME_SIZE-1);
diff --git a/stratosphere/ams_mitm/source/utils.cpp b/stratosphere/ams_mitm/source/utils.cpp
index b1e5ca315..1304c31a7 100644
--- a/stratosphere/ams_mitm/source/utils.cpp
+++ b/stratosphere/ams_mitm/source/utils.cpp
@@ -13,7 +13,7 @@
  * 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 <switch.h>
 #include <stratosphere.hpp>
 #include <atomic>
@@ -85,16 +85,16 @@ void Utils::InitializeThreadFunc(void *args) {
             if (R_FAILED(smGetServiceOriginal(&tmp_hnd, smEncodeName(required_active_services[i])))) {
                 std::abort();
             } else {
-                svcCloseHandle(tmp_hnd);   
+                svcCloseHandle(tmp_hnd);
             }
         }
     });
-    
+
     /* Mount SD. */
     while (R_FAILED(fsMountSdcard(&g_sd_filesystem))) {
         svcSleepThread(1000000ULL);
     }
-    
+
     /* Back up CAL0, if it's not backed up already. */
     fsFsCreateDirectory(&g_sd_filesystem, "/atmosphere/automatic_backups");
     {
@@ -103,18 +103,18 @@ void Utils::InitializeThreadFunc(void *args) {
             std::abort();
         }
         fsStorageClose(&cal0_storage);
-        
+
         char serial_number[0x40] = {0};
         memcpy(serial_number, g_cal0_storage_backup + 0x250, 0x18);
-        
-        
+
+
         char prodinfo_backup_path[FS_MAX_PATH] = {0};
         if (strlen(serial_number) > 0) {
             snprintf(prodinfo_backup_path, sizeof(prodinfo_backup_path) - 1, "/atmosphere/automatic_backups/%s_PRODINFO.bin", serial_number);
         } else {
             snprintf(prodinfo_backup_path, sizeof(prodinfo_backup_path) - 1, "/atmosphere/automatic_backups/PRODINFO.bin");
         }
-        
+
         fsFsCreateFile(&g_sd_filesystem, prodinfo_backup_path, ProdinfoSize, 0);
         if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, prodinfo_backup_path, FS_OPEN_READ | FS_OPEN_WRITE, &g_cal0_file))) {
             bool has_auto_backup = false;
@@ -132,19 +132,19 @@ void Utils::InitializeThreadFunc(void *args) {
                 }
                 has_auto_backup = is_cal0_valid;
             }
-            
+
             if (!has_auto_backup) {
                 fsFileSetSize(&g_cal0_file, ProdinfoSize);
                 fsFileWrite(&g_cal0_file, 0, g_cal0_storage_backup, ProdinfoSize);
                 fsFileFlush(&g_cal0_file);
             }
-            
+
             /* NOTE: g_cal0_file is intentionally not closed here. This prevents any other process from opening it. */
             memset(g_cal0_storage_backup, 0, sizeof(g_cal0_storage_backup));
             memset(g_cal0_backup, 0, sizeof(g_cal0_backup));
         }
     }
-    
+
     /* Check for MitM flags. */
     FsDir titles_dir;
     if (R_SUCCEEDED(fsFsOpenDirectory(&g_sd_filesystem, "/atmosphere/titles", FS_DIROPEN_DIRECTORY, &titles_dir))) {
@@ -172,7 +172,7 @@ void Utils::InitializeThreadFunc(void *args) {
                         fsFileClose(&f);
                     }
                 }
-                
+
                 memset(title_path, 0, sizeof(title_path));
                 strcpy(title_path, "/atmosphere/titles/");
                 strcat(title_path, dir_entry.name);
@@ -195,25 +195,25 @@ void Utils::InitializeThreadFunc(void *args) {
         }
         fsDirClose(&titles_dir);
     }
-    
+
     Utils::RefreshConfiguration();
-    
+
     /* Initialize set:sys. */
     DoWithSmSession([&]() {
         if (R_FAILED(setsysInitialize())) {
             std::abort();
         }
     });
-    
+
     /* Signal SD is initialized. */
     g_has_initialized = true;
-    
+
     /* Load custom settings configuration. */
     SettingsItemManager::LoadConfiguration();
-    
+
     /* Signal to waiters that we are ready. */
     g_sd_signal.Signal();
-    
+
     /* Initialize HID. */
     while (!g_has_hid_session) {
         DoWithSmSession([&]() {
@@ -243,7 +243,7 @@ Result Utils::OpenSdFile(const char *fn, int flags, FsFile *out) {
     if (!IsSdInitialized()) {
         return ResultFsSdCardNotPresent;
     }
-    
+
     return fsFsOpenFile(&g_sd_filesystem, fn, flags, out);
 }
 
@@ -251,7 +251,7 @@ Result Utils::OpenSdFileForAtmosphere(u64 title_id, const char *fn, int flags, F
     if (!IsSdInitialized()) {
         return ResultFsSdCardNotPresent;
     }
-    
+
     char path[FS_MAX_PATH];
     if (*fn == '/') {
         snprintf(path, sizeof(path), "/atmosphere/titles/%016lx%s", title_id, fn);
@@ -265,7 +265,7 @@ Result Utils::OpenRomFSSdFile(u64 title_id, const char *fn, int flags, FsFile *o
     if (!IsSdInitialized()) {
         return ResultFsSdCardNotPresent;
     }
-    
+
     return OpenRomFSFile(&g_sd_filesystem, title_id, fn, flags, out);
 }
 
@@ -273,7 +273,7 @@ Result Utils::OpenSdDir(const char *path, FsDir *out) {
     if (!IsSdInitialized()) {
         return ResultFsSdCardNotPresent;
     }
-    
+
     return fsFsOpenDirectory(&g_sd_filesystem, path, FS_DIROPEN_DIRECTORY | FS_DIROPEN_FILE, out);
 }
 
@@ -281,7 +281,7 @@ Result Utils::OpenSdDirForAtmosphere(u64 title_id, const char *path, FsDir *out)
     if (!IsSdInitialized()) {
         return ResultFsSdCardNotPresent;
     }
-    
+
     char safe_path[FS_MAX_PATH];
     if (*path == '/') {
         snprintf(safe_path, sizeof(safe_path), "/atmosphere/titles/%016lx%s", title_id, path);
@@ -295,7 +295,7 @@ Result Utils::OpenRomFSSdDir(u64 title_id, const char *path, FsDir *out) {
     if (!IsSdInitialized()) {
         return ResultFsSdCardNotPresent;
     }
-    
+
     return OpenRomFSDir(&g_sd_filesystem, title_id, path, out);
 }
 
@@ -327,7 +327,7 @@ bool Utils::HasSdRomfsContent(u64 title_id) {
         fsFileClose(&data_file);
         return true;
     }
-    
+
     /* Check for romfs folder with non-zero content. */
     FsDir dir;
     if (R_FAILED(Utils::OpenRomFSSdDir(title_id, "", &dir))) {
@@ -336,7 +336,7 @@ bool Utils::HasSdRomfsContent(u64 title_id) {
     ON_SCOPE_EXIT {
         fsDirClose(&dir);
     };
-    
+
     FsDirectoryEntry dir_entry;
     u64 read_entries;
     return R_SUCCEEDED(fsDirRead(&dir, 0, &read_entries, 1, &dir_entry)) && read_entries == 1;
@@ -346,40 +346,40 @@ Result Utils::SaveSdFileForAtmosphere(u64 title_id, const char *fn, void *data,
     if (!IsSdInitialized()) {
         return ResultFsSdCardNotPresent;
     }
-    
+
     Result rc = ResultSuccess;
-    
+
     char path[FS_MAX_PATH];
     if (*fn == '/') {
         snprintf(path, sizeof(path), "/atmosphere/titles/%016lx%s", title_id, fn);
     } else {
         snprintf(path, sizeof(path), "/atmosphere/titles/%016lx/%s", title_id, fn);
     }
-    
+
     /* Unconditionally create. */
     FsFile f;
     fsFsCreateFile(&g_sd_filesystem, path, size, 0);
-    
+
     /* Try to open. */
     rc = fsFsOpenFile(&g_sd_filesystem, path, FS_OPEN_READ | FS_OPEN_WRITE, &f);
     if (R_FAILED(rc)) {
         return rc;
     }
-    
+
     /* Always close, if we opened. */
     ON_SCOPE_EXIT {
         fsFileClose(&f);
     };
-    
+
     /* Try to make it big enough. */
     rc = fsFileSetSize(&f, size);
     if (R_FAILED(rc)) {
         return rc;
     }
-    
+
     /* Try to write the data. */
     rc = fsFileWrite(&f, 0, data, size);
-    
+
     return rc;
 }
 
@@ -395,14 +395,14 @@ bool Utils::HasTitleFlag(u64 tid, const char *flag) {
     if (IsSdInitialized()) {
         FsFile f;
         char flag_path[FS_MAX_PATH];
-        
+
         memset(flag_path, 0, sizeof(flag_path));
         snprintf(flag_path, sizeof(flag_path) - 1, "flags/%s.flag", flag);
         if (R_SUCCEEDED(OpenSdFileForAtmosphere(tid, flag_path, FS_OPEN_READ, &f))) {
             fsFileClose(&f);
             return true;
         }
-        
+
         /* TODO: Deprecate. */
         snprintf(flag_path, sizeof(flag_path) - 1, "%s.flag", flag);
         if (R_SUCCEEDED(OpenSdFileForAtmosphere(tid, flag_path, FS_OPEN_READ, &f))) {
@@ -440,7 +440,7 @@ bool Utils::HasSdMitMFlag(u64 tid) {
     if (IsHblTid(tid)) {
         return true;
     }
-    
+
     if (IsSdInitialized()) {
         return std::find(g_mitm_flagged_tids.begin(), g_mitm_flagged_tids.end(), tid) != g_mitm_flagged_tids.end();
     }
@@ -458,14 +458,14 @@ Result Utils::GetKeysHeld(u64 *keys) {
     if (!Utils::IsHidAvailable()) {
         return MAKERESULT(Module_Libnx, LibnxError_InitFail_HID);
     }
-    
+
     hidScanInput();
     *keys = 0;
 
     for (int controller = 0; controller < 10; controller++) {
         *keys |= hidKeysHeld((HidControllerID) controller);
     }
-    
+
     return ResultSuccess;
 }
 
@@ -481,21 +481,21 @@ bool Utils::HasOverrideButton(u64 tid) {
         /* Disable button override disable for non-applications. */
         return true;
     }
-    
+
     /* Unconditionally refresh loader.ini contents. */
     RefreshConfiguration();
-    
+
     if (IsHblTid(tid) && HasOverrideKey(&g_hbl_override_config.override_key)) {
         return true;
     }
-    
+
     OverrideKey title_cfg = GetTitleOverrideKey(tid);
     return HasOverrideKey(&title_cfg);
 }
 
 static OverrideKey ParseOverrideKey(const char *value) {
     OverrideKey cfg;
-    
+
     /* Parse on by default. */
     if (value[0] == '!') {
         cfg.override_by_default = true;
@@ -503,7 +503,7 @@ static OverrideKey ParseOverrideKey(const char *value) {
     } else {
         cfg.override_by_default = false;
     }
-    
+
     /* Parse key combination. */
     if (strcasecmp(value, "A") == 0) {
         cfg.key_combination = KEY_A;
@@ -544,7 +544,7 @@ static OverrideKey ParseOverrideKey(const char *value) {
     } else {
         cfg.key_combination = 0;
     }
-    
+
     return cfg;
 }
 
@@ -586,7 +586,7 @@ static int FsMitmIniHandler(void *user, const char *section, const char *name, c
 static int FsMitmTitleSpecificIniHandler(void *user, const char *section, const char *name, const char *value) {
     /* We'll output an override key when relevant. */
     OverrideKey *user_cfg = reinterpret_cast<OverrideKey *>(user);
-    
+
     if (strcasecmp(section, "override_config") == 0) {
         if (strcasecmp(name, "override_key") == 0) {
             *user_cfg = ParseOverrideKey(value);
@@ -600,49 +600,107 @@ static int FsMitmTitleSpecificIniHandler(void *user, const char *section, const
 OverrideKey Utils::GetTitleOverrideKey(u64 tid) {
     OverrideKey cfg = g_default_override_key;
     char path[FS_MAX_PATH+1] = {0};
-    snprintf(path, FS_MAX_PATH, "/atmosphere/titles/%016lx/config.ini", tid); 
+    snprintf(path, FS_MAX_PATH, "/atmosphere/titles/%016lx/config.ini", tid);
     FsFile cfg_file;
-    
+
     if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, path, FS_OPEN_READ, &cfg_file))) {
         ON_SCOPE_EXIT { fsFileClose(&cfg_file); };
-        
+
         size_t config_file_size = 0x20000;
         fsFileGetSize(&cfg_file, &config_file_size);
-        
+
         char *config_buf = reinterpret_cast<char *>(calloc(1, config_file_size + 1));
         if (config_buf != NULL) {
             ON_SCOPE_EXIT { free(config_buf); };
-            
+
             /* Read title ini contents. */
             fsFileRead(&cfg_file, 0, config_buf, config_file_size, &config_file_size);
-            
+
             /* Parse title ini. */
             ini_parse_string(config_buf, FsMitmTitleSpecificIniHandler, &cfg);
         }
     }
-    
+
     return cfg;
 }
 
+static int FsMitmTitleSpecificLocaleIniHandler(void *user, const char *section, const char *name, const char *value) {
+    /* We'll output an override locale when relevant. */
+    OverrideLocale *user_locale = reinterpret_cast<OverrideLocale *>(user);
+
+    if (strcasecmp(section, "override_config") == 0) {
+        if (strcasecmp(name, "override_language") == 0) {
+            user_locale->language_code = EncodeLanguageCode(value);
+        } else if (strcasecmp(name, "override_region") == 0) {
+            if (strcasecmp(value, "jpn") == 0) {
+                user_locale->region_code = RegionCode_Japan;
+            } else if (strcasecmp(value, "usa") == 0) {
+                user_locale->region_code = RegionCode_America;
+            } else if (strcasecmp(value, "eur") == 0) {
+                user_locale->region_code = RegionCode_Europe;
+            } else if (strcasecmp(value, "aus") == 0) {
+                user_locale->region_code = RegionCode_Australia;
+            } else if (strcasecmp(value, "chn") == 0) {
+                user_locale->region_code = RegionCode_China;
+            } else if (strcasecmp(value, "kor") == 0) {
+                user_locale->region_code = RegionCode_Korea;
+            } else if (strcasecmp(value, "twn") == 0) {
+                user_locale->region_code = RegionCode_Taiwan;
+            }
+        }
+    } else {
+        return 0;
+    }
+    return 1;
+}
+
+OverrideLocale Utils::GetTitleOverrideLocale(u64 tid) {
+    OverrideLocale locale;
+    std::memset(&locale, 0xCC, sizeof(locale));
+    char path[FS_MAX_PATH+1] = {0};
+    snprintf(path, FS_MAX_PATH, "/atmosphere/titles/%016lx/config.ini", tid);
+    FsFile cfg_file;
+
+    if (R_SUCCEEDED(fsFsOpenFile(&g_sd_filesystem, path, FS_OPEN_READ, &cfg_file))) {
+        ON_SCOPE_EXIT { fsFileClose(&cfg_file); };
+
+        size_t config_file_size = 0x20000;
+        fsFileGetSize(&cfg_file, &config_file_size);
+
+        char *config_buf = reinterpret_cast<char *>(calloc(1, config_file_size + 1));
+        if (config_buf != NULL) {
+            ON_SCOPE_EXIT { free(config_buf); };
+
+            /* Read title ini contents. */
+            fsFileRead(&cfg_file, 0, config_buf, config_file_size, &config_file_size);
+
+            /* Parse title ini. */
+            ini_parse_string(config_buf, FsMitmTitleSpecificLocaleIniHandler, &locale);
+        }
+    }
+
+    return locale;
+}
+
 void Utils::RefreshConfiguration() {
     FsFile config_file;
     if (R_FAILED(fsFsOpenFile(&g_sd_filesystem, "/atmosphere/loader.ini", FS_OPEN_READ, &config_file))) {
         return;
     }
-    
+
     u64 size;
     if (R_FAILED(fsFileGetSize(&config_file, &size))) {
         return;
     }
-    
+
     size = std::min(size, (decltype(size))0x7FF);
-    
+
     /* Read in string. */
     std::fill(g_config_ini_data, g_config_ini_data + 0x800, 0);
     size_t r_s;
     fsFileRead(&config_file, 0, g_config_ini_data, size, &r_s);
     fsFileClose(&config_file);
-    
+
     ini_parse_string(g_config_ini_data, FsMitmIniHandler, NULL);
 }
 
diff --git a/stratosphere/ams_mitm/source/utils.hpp b/stratosphere/ams_mitm/source/utils.hpp
index 04e1faadf..a3e9ce4ab 100644
--- a/stratosphere/ams_mitm/source/utils.hpp
+++ b/stratosphere/ams_mitm/source/utils.hpp
@@ -42,6 +42,56 @@ struct OverrideKey {
     bool override_by_default;
 };
 
+struct OverrideLocale {
+    u64 language_code;
+    u32 region_code;
+};
+
+enum RegionCode : u32 {
+    RegionCode_Japan = 0,
+    RegionCode_America = 1,
+    RegionCode_Europe = 2,
+    RegionCode_Australia = 3,
+    RegionCode_China = 4,
+    RegionCode_Korea = 5,
+    RegionCode_Taiwan = 6,
+
+    RegionCode_Max,
+};
+
+static constexpr inline u64 EncodeLanguageCode(const char *code) {
+    u64 lang_code = 0;
+    for (size_t i = 0; i < sizeof(lang_code); i++) {
+        if (code[i] == '\x00') {
+            break;
+        }
+        lang_code |= static_cast<u64>(code[i]) << (8ul * i);
+    }
+    return lang_code;
+}
+
+enum LanguageCode : u64 {
+    LanguageCode_Japanese = EncodeLanguageCode("ja"),
+    LanguageCode_AmericanEnglish = EncodeLanguageCode("en-US"),
+    LanguageCode_French = EncodeLanguageCode("fr"),
+    LanguageCode_German = EncodeLanguageCode("de"),
+    LanguageCode_Italian = EncodeLanguageCode("it"),
+    LanguageCode_Spanish = EncodeLanguageCode("es"),
+    LanguageCode_Chinese = EncodeLanguageCode("zh-CN"),
+    LanguageCode_Korean = EncodeLanguageCode("ko"),
+    LanguageCode_Dutch = EncodeLanguageCode("nl"),
+    LanguageCode_Portuguese = EncodeLanguageCode("pt"),
+    LanguageCode_Russian = EncodeLanguageCode("ru"),
+    LanguageCode_Taiwanese = EncodeLanguageCode("zh-TW"),
+    LanguageCode_BritishEnglish = EncodeLanguageCode("en-GB"),
+    LanguageCode_CanadianFrench = EncodeLanguageCode("fr-CA"),
+    LanguageCode_LatinAmericanSpanish = EncodeLanguageCode("es-419"),
+    /* 4.0.0+ */
+    LanguageCode_SimplifiedChinese = EncodeLanguageCode("zh-Hans"),
+    LanguageCode_TraditionalChinese = EncodeLanguageCode("zh-Hant"),
+};
+
+
 class Utils {
     public:
         static bool IsSdInitialized();
@@ -82,12 +132,14 @@ class Utils {
         static OverrideKey GetTitleOverrideKey(u64 tid);
         static bool HasOverrideButton(u64 tid);
 
+        static OverrideLocale GetTitleOverrideLocale(u64 tid);
+
         /* Settings! */
         static Result GetSettingsItemValueSize(const char *name, const char *key, u64 *out_size);
         static Result GetSettingsItemValue(const char *name, const char *key, void *out, size_t max_size, u64 *out_size);
 
         static Result GetSettingsItemBooleanValue(const char *name, const char *key, bool *out);
-        
+
         /* Error occurred. */
         static void RebootToFatalError(AtmosphereFatalErrorContext *ctx);
     private:
diff --git a/stratosphere/pm/source/pm_main.cpp b/stratosphere/pm/source/pm_main.cpp
index cab914dd8..484a1347e 100644
--- a/stratosphere/pm/source/pm_main.cpp
+++ b/stratosphere/pm/source/pm_main.cpp
@@ -175,9 +175,9 @@ int main(int argc, char **argv)
 
     /* TODO: Create services. */
     s_server_manager.AddWaitable(new ServiceServer<ShellService>("pm:shell", 3));
-    s_server_manager.AddWaitable(new ServiceServer<DebugMonitorService>("pm:dmnt", 2));
+    s_server_manager.AddWaitable(new ServiceServer<DebugMonitorService>("pm:dmnt", 3));
     s_server_manager.AddWaitable(new ServiceServer<BootModeService>("pm:bm", 6));
-    s_server_manager.AddWaitable(new ServiceServer<InformationService>("pm:info", 2));
+    s_server_manager.AddWaitable(new ServiceServer<InformationService>("pm:info", 3));
     
     /* Loop forever, servicing our services. */
     s_server_manager.Process();