installation guide

MysticMate
Unlock Patch

Unlock all mascots permanently — two files, one reload, done.

✓ No tools required ✓ Original files mostly intact ✓ Survives browser restarts

How It Works

The patch adds a single self-contained script (unlock-mascots.js) that runs inside MysticMate's background service worker. From there it has full access to MysticMate's own storage, probes which mascot folders actually exist in your install, and writes them all as owned — every time the browser starts.

📄
unlock-mascots.js
New file — drop it into the /assets/ folder
✏️
service-worker-loader.js
Existing file — replace with version that imports the patch (1 line added)
ℹ️ No JS logic from MysticMate is changed. The original extension code runs exactly as before — we're just adding a new file and a one-line import.

Step-by-Step

1
Find MysticMate's extension folder

Open Chrome and go to chrome://extensions. Enable Developer mode (top-right toggle). Find MysticMate and click Details, then note its ID: biaplbhoaffjahhmbkfmdoagkklhanao

💡 The folder is typically at:
Windows: %LOCALAPPDATA%\Google\Chrome\User Data\Default\Extensions\biaplbhoaffjahhmbkfmdoagkklhanao\
macOS: ~/Library/Application Support/Google/Chrome/Default/Extensions/biaplbhoaffjahhmbkfmdoagkklhanao/
2
Copy unlock-mascots.js into /assets/

Inside the versioned MysticMate folder (e.g. 1.0.0_0/), open the assets/ subfolder and drop unlock-mascots.js in there.

3
Replace service-worker-loader.js

In the root of the versioned folder (same level as manifest.json), replace service-worker-loader.js with the patched version. The only difference is one added import line at the bottom:

service-worker-loader.js
// original line — unchanged
import './assets/index.ts-BxdfawpK.js';
import './assets/unlock-mascots.js';   // ← add this
4
Reload MysticMate

Back in chrome://extensions, find MysticMate and click the ↻ reload button (the circular arrow icon under the extension card).

5
Done — open MysticMate

Click the MysticMate icon in your toolbar and open the mascot list. All mascots should now be unlocked. The patch re-applies automatically on every browser restart.

You should see all mascots unlocked in the list. The console (background service worker) will log: [unlock-mascots] Unlocked N mascots

unlock-mascots.js

Here's the complete contents of the patch file for reference:

assets/unlock-mascots.js
// Place in /assets/ and import from service-worker-loader.js

const MASCOT_IDS = [
  "blank-guy", "caicai", "jiqiren", "yeyangbaixuan", "xialinglan", "hatsune-miku", "miku",
  "reimu", "marisa", "cirno", "remilia", "sakuya", "patchouli",
  "alice", "youmu", "yuyuko", "ran", "chen", "yukari", "suika",
  "reisen", "tewi", "eirin", "kaguya", "mokou", "keine", "mystia",
  "wriggle", "rumia", "koishi", "satori", "orin", "okuu", "nitori",
  "momiji", "aya", "sanae", "kanako", "suwako", "hatate",
  "kogasa", "ichirin", "murasa", "shou", "nazrin", "byakuren", "nue",
];

function fmt(id) {
  return id.split("-").map(w => w[0].toUpperCase() + w.slice(1)).join(" ");
}

async function applyUnlock() {
  try {
    const results = await Promise.all(
      MASCOT_IDS.map(async id => {
        try {
          const res = await fetch(chrome.runtime.getURL(id + "/shime.png"), { method: "HEAD" });
          return res.ok ? id : null;
        } catch (_) { return null; }
      })
    );
    const ids = results.filter(Boolean);
    if (!ids.includes("blank-guy")) ids.unshift("blank-guy");

    const owned = ids.map(specId => ({
      specId, name: fmt(specId),
      acquiredAt: Date.now(),
      customPersonality: null, spceUrl: null,
    }));

    await chrome.storage.sync.set({ ownedMascots: JSON.stringify(owned) });
    await chrome.storage.local.set({
      encounterEnabledUserPref: true,
      clueEnabledUserPref:      true,
      bossKeyHidden:            false,
      excludedDomains:          [],
    });

    console.log(`[unlock-mascots] Unlocked ${ids.length} mascots`);
  } catch (e) {
    console.error("[unlock-mascots] Error:", e);
  }
}

applyUnlock(); // runs every time the service worker starts

self.addEventListener("install",  () => applyUnlock());
self.addEventListener("activate", () => applyUnlock());

nest-size.js — Hide / Show Home Nest

This optional patch hides the home nest icon that appears at the bottom-left of every page when a mascot is active. Press Alt+W at any time to toggle it visible or hidden.

📄
assets/nest-size.js
New file — drop into /assets/ folder
✏️
manifest.json
Existing file — replace with version that registers nest-size.js as a content script
ℹ️ The nest lives inside a Shadow DOM — normal CSS can't reach it. This script injects a style tag directly into the shadow root and listens for Alt+W to toggle visibility. Starts hidden by default.
assets/nest-size.js
// Alt+W to toggle hide/show of the nest/home zone

const HOST_ID = "shimeji-shadow-host";
let hidden = true;

function getShadow() {
  return document.getElementById(HOST_ID)?.shadowRoot ?? null;
}

function applyState() {
  const shadow = getShadow();
  if (!shadow) return;
  let style = shadow.querySelector("#nest-patch");
  if (!style) {
    style = document.createElement("style");
    style.id = "nest-patch";
    shadow.appendChild(style);
  }
  style.textContent = hidden
    ? `#shimeji-rest-zone { display: none !important; }`
    : `#shimeji-rest-zone { display: flex !important; }`;
}

document.addEventListener("keydown", (e) => {
  if (e.altKey && e.key.toLowerCase() === "w") {
    e.preventDefault();
    hidden = !hidden;
    applyState();
    console.log("[nest-patch] nest", hidden ? "hidden" : "visible");
  }
});

const mo = new MutationObserver(() => {
  if (getShadow()) {
    applyState();
    mo.disconnect();
    const mo2 = new MutationObserver(applyState);
    mo2.observe(document.getElementById(HOST_ID), { childList: true });
  }
});
mo.observe(document.documentElement, { childList: true, subtree: true });

applyState();

Then update manifest.json — merge both content scripts into one array and add nest-size.js to web_accessible_resources:

manifest.json — content_scripts section
// ⚠ JSON cannot have two "content_scripts" keys — merge into ONE array

"content_scripts": [
  {
    "js": [ "assets/index.ts-loader-xo8arMU7.js" ],
    "matches": [ "<all_urls>" ],
    "run_at": "document_end"
  },
  {
    "js": [ "assets/nest-size.js" ],   // ← add this entry
    "matches": [ "<all_urls>" ],
    "run_at": "document_end"
  }
]
⚠️ You also need to add "assets/nest-size.js" to the web_accessible_resources resources array in manifest.json, otherwise Chrome won't load the file.
manifest.json — web_accessible_resources
"web_accessible_resources": [ {
  "matches": [ "<all_urls>" ],
  "resources": [
    // ... all existing entries ...
    "assets/index.ts-BNF88MQI.js",
    "assets/nest-size.js"          // ← add this line
  ],
  "use_dynamic_url": false
} ]
Just one new line — added right after the last existing entry "assets/index.ts-BNF88MQI.js". Make sure there's a comma at the end of the line above it.

FAQ

I see "Extension context invalidated" errors on web pages
This is unrelated to the patch. It happens when MysticMate's content script (already running in an open tab) tries to call Chrome APIs after the extension was reloaded. It's harmless — just refresh any open tabs and the error disappears. This is normal Chrome behavior after any extension reload.
Mascots are still locked after reloading
Open Chrome DevTools → go to chrome://extensions → click "Service Worker" under MysticMate → check the Console tab for [unlock-mascots] messages. If you see an error there, the file path is likely wrong — make sure unlock-mascots.js is inside the assets/ subfolder, not the root.
Will this break after MysticMate updates?
Yes — Chrome extension updates replace the entire folder with a new versioned copy. After an update you'll need to re-add the two files to the new version folder. The unlock-mascots.js file itself never needs to change.
Is this safe? Does MysticMate send any data externally?
The extension was fully audited. No analytics, no telemetry, no fingerprinting. The backend API URL is hardcoded as an empty string and the code comment says "using hardcoded test data, no backend request." All data stays in your local Chrome storage and syncs only to your own Google account via chrome.storage.sync.
Why does the patch re-run every browser restart?
Chrome's chrome.storage.sync can occasionally be cleared or reset (e.g. if you sign out of Chrome). Re-running on every startup ensures mascots stay unlocked without any manual intervention.